1
0
mirror of https://github.com/sethm/symon.git synced 2024-06-01 08:41:32 +00:00

README updates, CRTC tests.

- Added more CRTC information to the README file.
- Added unit tests for the CRTC.
- Implemented register read for cursor position in the CRTC.
- Bundling a new version of jterminal that has correct backspace
  behavior.
This commit is contained in:
Seth Morabito 2013-12-29 18:18:25 -08:00
parent 070e7380fc
commit 4a510b635e
5 changed files with 413 additions and 85 deletions

View File

@ -3,7 +3,7 @@ SYMON - A 6502 System Simulator
**NOTE: THIS SOFTWARE IS UNDER ACTIVE DEVELOPMENT. Feedback is welcome!**
**Version:** 0.9.0
**Version:** 0.9.0.1
**Last Updated:** 29 December, 2013
@ -22,7 +22,7 @@ in Java. Its core goals are accuracy, ease of development, clear
documentation, and extensive test suites for validating correctness.
Symon simulates a complete system with a 1 MHz NMOS 6502, 32KB of RAM,
16KB of ROM, a 6551 ACIA, and a 6522 VIA.
16KB of ROM, a 6551 ACIA, a 6522 VIA, and a 6545 CRTC.
Symon has extensive unit tests to verify correctness, and fully passes
Klaus Dormann's 6502 Functional Test Suite as of version 0.8.2
@ -42,8 +42,11 @@ for more information about this functional test suite).
- `$0000`--`$7FFF`: 32KB RAM
- `$8000`--`$800F`: 6522 VIA
- `$8800`--`$8803`: 6551 ACIA (Serial Console)
- `$9000`--`$9001`: 6545 CRTC
- `$C000`--`$FFFF`: 16KB ROM
The CRT Controller uses memory address `$7000` as the start of Video memory.
### 3.2 Serial Console and CPU Status
![Serial Console] (https://github.com/sethm/symon/raw/master/screenshots/console.png)
@ -95,9 +98,6 @@ By default, the 40 x 25 character display uses video memory located at base addr
This means that the memory from address `$7000` (28672 decimal) to `$73E8` (29672 decimal)
is directly mapped to video.
The CRTC emulation is very rough around the edges at the moment. Only the following registers
are supported:
- Address Register (at address `$9000`)
- R1: Horizontal Displayed Columns
- R6: Vertical Displayed Rows
@ -106,10 +106,21 @@ are supported:
- R11: Cursor End Scan Line
- R12: Display Start Address (High Byte)
- R13: Display Start Address (Low Byte)
- R14: Cursor Position (High Byte) [Read Only]
- R15: Cursor Position (Low Byte) [Read Only]
- R14: Cursor Position (High Byte)
- R15: Cursor Position (Low Byte)
In particular, please note that the Status register is not implemented yet. Still, the feature is ready for some testing and playing with.
Although the simulation is pretty good, there are a few key differences between
the simulated 6545 and a real 6545:
- The simulated 6545 supports only the straight binary addressing mode of the real 6545,
and not the Row/Column addressing mode.
- The simulated 6545 has full 16 bit addressing, where the real 6545 has only
a 14-bit address bus.
- The simulation is done at a whole-frame level, meaning that lots of
6545 programming tricks that were achieved by updating the frame address
during vertical and horizontal sync times are not achievable. There is no way
(for example) to change the Display Start Address (R12 and R13) while a
frame is being drawn.
For more information on the 6545 CRTC and its programming model, please see the following resources
@ -118,6 +129,17 @@ For more information on the 6545 CRTC and its programming model, please see the
- [MOS 6545 Datasheet (PDF)] (http://www.6502.org/users/andre/hwinfo/crtc/crtc.html)
#### 3.6.1 Example BASIC Program to test Video
This program will fill the video screen with all printable characters.
10 J = 0
20 FOR I = 28672 TO 29672
30 POKE I,J
40 IF J < 255 THEN J = J + 1 ELSE J = 0
50 NEXT I
60 END
## 4.0 Usage
### 4.1 Building

62
pom.xml
View File

@ -4,7 +4,7 @@
<groupId>com.loomcom.symon</groupId>
<artifactId>symon</artifactId>
<packaging>jar</packaging>
<version>0.9.0</version>
<version>0.9.0.1</version>
<name>symon</name>
<url>http://www.loomcom.com/symon</url>
<properties>
@ -44,7 +44,13 @@
<dependency>
<groupId>com.grahamedgecombe.jterminal</groupId>
<artifactId>jterminal</artifactId>
<version>1.0.2.2-loomcom</version>
<version>1.0.2.3-loomcom</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
</dependencies>
@ -125,47 +131,6 @@
</configuration>
</plugin>
<!-- Cobertura is essential -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.6</version>
<configuration>
<check>
<haltOnFailure>false</haltOnFailure>
<regexes>
<regex>
<pattern>com.loomcom.symon.*</pattern>
<branchRate>90</branchRate>
<lineRate>90</lineRate>
</regex>
</regexes>
</check>
<instrumentation>
<includes>
<include>com/loomcom/symon/*.class</include>
</includes>
</instrumentation>
</configuration>
<executions>
<execution>
<id>clean</id>
<phase>pre-site</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
<execution>
<id>instrument</id>
<phase>site</phase>
<goals>
<goal>instrument</goal>
<goal>cobertura</goal>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
@ -183,15 +148,4 @@
</plugin>
</plugins>
</build>
<reporting>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.6</version>
</plugin>
</plugins>
</reporting>
</project>

View File

@ -23,10 +23,12 @@
package com.loomcom.symon;
import java.util.*;
import com.loomcom.symon.devices.Device;
import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import com.loomcom.symon.devices.*;
import com.loomcom.symon.exceptions.*;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* The Bus ties the whole thing together, man.

View File

@ -17,7 +17,7 @@ public class Crtc extends Device {
// Memory locations in the CRTC address space
public static final int REGISTER_SELECT = 0;
public static final int REGISTER_WRITE = 1;
public static final int REGISTER_RW = 1;
// Registers
public static final int HORIZONTAL_DISPLAYED = 1;
@ -90,25 +90,26 @@ public class Crtc extends Device {
case REGISTER_SELECT:
setCurrentRegister(data);
break;
case REGISTER_WRITE:
case REGISTER_RW:
writeRegisterValue(data);
break;
default:
throw new MemoryAccessException("No such address.");
}
notifyListeners();
}
@Override
public int read(int address) throws MemoryAccessException {
switch (address) {
case REGISTER_SELECT:
return status();
case REGISTER_WRITE:
return 0;
case REGISTER_RW:
switch (currentRegister) {
case CURSOR_POSITION_LOW:
return cursorPosition & 0xff;
case CURSOR_POSITION_HIGH:
return cursorPosition >> 8;
default:
return 0;
}
default:
throw new MemoryAccessException("No such address.");
return 0;
}
}
@ -121,10 +122,6 @@ public class Crtc extends Device {
return memory.getDmaAccess();
}
private int status() {
return 0;
}
public int getHorizontalDisplayed() {
return horizontalDisplayed;
}
@ -169,7 +166,10 @@ public class Crtc extends Device {
this.currentRegister = registerNumber;
}
private void writeRegisterValue(int data) {
private void writeRegisterValue(int data) throws MemoryAccessException {
int oldStartAddress = startAddress;
int oldCursorPosition = cursorPosition;
switch (currentRegister) {
case HORIZONTAL_DISPLAYED:
horizontalDisplayed = data;
@ -213,24 +213,31 @@ public class Crtc extends Device {
break;
case DISPLAY_START_HIGH:
startAddress = ((data & 0xff) << 8) | (startAddress & 0x00ff);
// TODO: bounds checking.
break;
case DISPLAY_START_LOW:
startAddress = ((data & 0xff) | (startAddress & 0xff00));
// TODO: bounds checking.
break;
case CURSOR_POSITION_HIGH:
cursorPosition = ((data & 0xff) << 8) | (cursorPosition & 0x00ff);
// TODO: bounds checking.
break;
case CURSOR_POSITION_LOW:
// TODO: bounds checking.
cursorPosition = (data & 0xff) | (cursorPosition & 0xff00);
break;
default:
break;
}
if (startAddress + pageSize > memory.endAddress()) {
startAddress = oldStartAddress;
throw new MemoryAccessException("Cannot draw screen starting at selected address.");
}
if (cursorPosition > memory.endAddress()) {
cursorPosition = oldCursorPosition;
throw new MemoryAccessException("Cannot position cursor past end of memory.");
}
notifyListeners();
}
}

View File

@ -0,0 +1,343 @@
package com.loomcom.symon;
import com.loomcom.symon.devices.Crtc;
import com.loomcom.symon.devices.DeviceChangeListener;
import com.loomcom.symon.devices.Memory;
import com.loomcom.symon.exceptions.MemoryAccessException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class CrtcTest {
Crtc crtc;
@Mock
DeviceChangeListener changeListener;
@Mock
Memory memory;
@Before
public void createDevices() throws Exception {
crtc = new Crtc(0x9000, memory);
crtc.registerListener(changeListener);
when(memory.startAddress()).thenReturn(0);
when(memory.endAddress()).thenReturn(0x7fff);
}
@Test
public void selectingRegisterDoesNotTriggedrCallback() throws Exception {
crtc.write(0, 1);
verify(changeListener, never()).deviceStateChanged();
}
@Test
public void shouldChangeHorizontalDisplayed() throws Exception {
crtc.write(0, 1);
crtc.write(1, 80);
assertEquals(80, crtc.getHorizontalDisplayed());
crtc.write(1, 40);
assertEquals(40, crtc.getHorizontalDisplayed());
}
@Test
public void changeHorizontalDisplayedShouldTriggerCallback() throws Exception {
crtc.write(0, 1);
crtc.write(1, 80);
verify(changeListener, times(1)).deviceStateChanged();
}
@Test
public void shouldChangeVerticalDisplayed() throws Exception {
crtc.write(0, 6);
crtc.write(1, 23);
assertEquals(23, crtc.getVerticalDisplayed());
crtc.write(1, 26);
assertEquals(26, crtc.getVerticalDisplayed());
}
@Test
public void changeVerticalDisplayedShouldTriggerCallback() throws Exception {
crtc.write(0, 6);
crtc.write(1, 23);
verify(changeListener, times(1)).deviceStateChanged();
}
@Test
public void shouldChangeScanLinesPerRow() throws Exception {
crtc.write(0, 9); // Select register 9, Scan Line
crtc.write(1, 3);
assertEquals(3, crtc.getScanLinesPerRow());
crtc.write(1, 5);
assertEquals(5, crtc.getScanLinesPerRow());
crtc.write(1, 9);
assertEquals(9, crtc.getScanLinesPerRow());
}
@Test
public void changeScanLinesPerRowShouldTriggerCallback() throws Exception {
crtc.write(0, 9); // Select register 9, Scan Line
crtc.write(1, 3);
verify(changeListener, times(1)).deviceStateChanged();
}
@Test
public void shouldChangeCursorStartLine() throws Exception {
crtc.write(0, 10);
crtc.write(1, 0);
assertEquals(0, crtc.getCursorStartLine());
crtc.write(1, 1);
assertEquals(1, crtc.getCursorStartLine());
crtc.write(1, 4);
assertEquals(4, crtc.getCursorStartLine());
}
@Test
public void changeCursorStartLineShouldTriggerCallback() throws Exception {
crtc.write(0, 10);
crtc.write(1, 5);
verify(changeListener, times(1)).deviceStateChanged();
}
@Test
public void cursorStartLineRegisterChangesCursorVisibility() throws Exception {
crtc.write(0, 10);
crtc.write(1, 0x00); // Start line 0, no blinking.
assertEquals(0, crtc.getCursorStartLine());
assertEquals(0, crtc.getCursorBlinkRate());
assertTrue(crtc.isCursorEnabled());
crtc.write(1, 0x23); // Start line 3, no cursor.
assertEquals(3, crtc.getCursorStartLine());
assertEquals(0, crtc.getCursorBlinkRate());
assertFalse(crtc.isCursorEnabled());
}
@Test
public void cursorStartLineRegisterChangesCursorBlinkRate() throws Exception {
crtc.write(0, 10);
crtc.write(1, 0x40); // Start line 0, 500ms blink delay
assertEquals(0, crtc.getCursorStartLine());
assertEquals(500, crtc.getCursorBlinkRate());
assertTrue(crtc.isCursorEnabled());
crtc.write(1, 0x62); // Start line 3, 1000ms blink delay
assertEquals(2, crtc.getCursorStartLine());
assertEquals(1000, crtc.getCursorBlinkRate());
assertTrue(crtc.isCursorEnabled());
}
@Test
public void shouldChangeCursorStopLine() throws Exception {
crtc.write(0, 11);
crtc.write(1, 0);
assertEquals(0, crtc.getCursorStopLine());
crtc.write(1, 3);
assertEquals(3, crtc.getCursorStopLine());
crtc.write(1, 6);
assertEquals(6, crtc.getCursorStopLine());
}
@Test
public void changeCursorStopLineShouldTriggerCallback() throws Exception {
crtc.write(0, 11);
crtc.write(1, 7);
verify(changeListener, times(1)).deviceStateChanged();
}
@Test
public void shouldChangeScreenStartAddressHighByte() throws Exception {
crtc.write(0, 12);
crtc.write(1, 0x00);
assertEquals(0x00, crtc.getStartAddress() >> 8);
crtc.write(1, 0x30);
assertEquals(0x30, crtc.getStartAddress() >> 8);
crtc.write(1, 0x6f);
assertEquals(0x6f, crtc.getStartAddress() >> 8);
}
@Test
public void changeScreenStartAddressHighByteShouldTriggerCallback() throws Exception {
crtc.write(0, 12);
crtc.write(1, 0x30);
verify(changeListener, times(1)).deviceStateChanged();
}
@Test
public void shouldChangeScreenStartAddressLowByte() throws Exception {
crtc.write(0, 13);
crtc.write(1, 0x00);
assertEquals(0x00, crtc.getStartAddress() & 0xff);
crtc.write(1, 0x11);
assertEquals(0x11, crtc.getStartAddress() & 0xff);
crtc.write(1, 0xff);
assertEquals(0xff, crtc.getStartAddress() & 0xff);
}
@Test
public void changeScreenStartAddressLowByteShouldTriggerCallback() throws Exception {
crtc.write(0, 13);
crtc.write(1, 0xff);
verify(changeListener, times(1)).deviceStateChanged();
}
@Test(expected = MemoryAccessException.class)
public void shouldThrowMemoryAccessExceptionIfPageOutOfRange() throws Exception {
crtc.write(0, 12);
crtc.write(1, 0x7f); // Page of text will extend beyond 0x7fff
}
@Test
public void readingStartAddressShouldDoNothing() throws Exception {
crtc.write(0, 12); // High byte
crtc.write(1, 0x03);
assertEquals(0, crtc.read(1));
crtc.write(1, 0x70);
assertEquals(0, crtc.read(1));
crtc.write(0, 13); // Low byte
crtc.write(1, 0xff);
assertEquals(0, crtc.read(1));
crtc.write(1, 0x0e);
assertEquals(0, crtc.read(1));
}
@Test
public void shouldChangeCursorPositionHighByte() throws Exception {
crtc.write(0, 14); // Select register 14
crtc.write(1, 0x73); // Set high cursor byte to $73
assertEquals(0x73, crtc.getCursorPosition() >> 8);
crtc.write(1, 0x3f);
assertEquals(0x3f, crtc.getCursorPosition() >> 8);
crtc.write(1, 0x7f);
assertEquals(0x7f, crtc.getCursorPosition() >> 8);
}
@Test
public void shouldBeAbleToReadCursorPositionHighByte() throws Exception {
crtc.write(0, 14);
crtc.write(1, 0x3f);
assertEquals(0x3f, crtc.read(1));
crtc.write(1, 0x70);
assertEquals(0x70, crtc.read(1));
}
@Test
public void changeCursorPositionHighByteShouldTriggerCallback() throws Exception {
crtc.write(0, 14);
crtc.write(1, 0x73);
verify(changeListener, times(1)).deviceStateChanged();
}
@Test
public void shouldChangeCursorPositionLowByte() throws Exception {
crtc.write(0, 15); // Select register 15
crtc.write(1, 0x00); // Set low cursor byte to $00
assertEquals(0x00, crtc.getCursorPosition() & 0xff);
crtc.write(1, 0x1f); // Set low cursor byte to $1f
assertEquals(0x1f, crtc.getCursorPosition() & 0xff);
crtc.write(1, 0xff); // Set low cursor byte to $ff
assertEquals(0xff, crtc.getCursorPosition() & 0xff);
}
@Test
public void shouldBeAbleToReadCursorPositionLowByte() throws Exception {
crtc.write(0, 15);
crtc.write(1, 0x00);
assertEquals(0x00, crtc.read(1));
crtc.write(1, 0x1f);
assertEquals(0x1f, crtc.read(1));
crtc.write(1, 0xff);
assertEquals(0xff, crtc.read(1));
}
@Test
public void changeCursorPositionLowByteShouldTriggerCallback() throws Exception {
crtc.write(0, 15);
crtc.write(1, 0x01);
verify(changeListener, times(1)).deviceStateChanged();
}
@Test(expected = MemoryAccessException.class)
public void shouldThrowMemoryAccessExceptionIfCursorGoesOutOfRange() throws Exception {
crtc.write(0, 14); // Select register 14
crtc.write(1, 0x80); // Can't position cursor
}
}