mirror of
https://codeberg.org/sethm/symon.git
synced 2026-11-08 03:12:23 +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:
1 parent
070e7380fc
commit
d8a72e0ff8
5 files changed
+413
-85
No files matched your search
@@ -3,7 +3,7 @@ SYMON - A 6502 System Simulator
|
|||||||
|
|
||||||
**NOTE: THIS SOFTWARE IS UNDER ACTIVE DEVELOPMENT. Feedback is welcome!**
|
**NOTE: THIS SOFTWARE IS UNDER ACTIVE DEVELOPMENT. Feedback is welcome!**
|
||||||
|
|
||||||
**Version:** 0.9.0
|
**Version:** 0.9.0.1
|
||||||
|
|
||||||
**Last Updated:** 29 December, 2013
|
**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.
|
documentation, and extensive test suites for validating correctness.
|
||||||
|
|
||||||
Symon simulates a complete system with a 1 MHz NMOS 6502, 32KB of RAM,
|
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
|
Symon has extensive unit tests to verify correctness, and fully passes
|
||||||
Klaus Dormann's 6502 Functional Test Suite as of version 0.8.2
|
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
|
- `$0000`--`$7FFF`: 32KB RAM
|
||||||
- `$8000`--`$800F`: 6522 VIA
|
- `$8000`--`$800F`: 6522 VIA
|
||||||
- `$8800`--`$8803`: 6551 ACIA (Serial Console)
|
- `$8800`--`$8803`: 6551 ACIA (Serial Console)
|
||||||
|
- `$9000`--`$9001`: 6545 CRTC
|
||||||
- `$C000`--`$FFFF`: 16KB ROM
|
- `$C000`--`$FFFF`: 16KB ROM
|
||||||
|
|
||||||
|
The CRT Controller uses memory address `$7000` as the start of Video memory.
|
||||||
|
|
||||||
### 3.2 Serial Console and CPU Status
|
### 3.2 Serial Console and CPU Status
|
||||||
|
|
||||||
![Serial Console] (https://github.com/sethm/symon/raw/master/screenshots/console.png)
|
![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)
|
This means that the memory from address `$7000` (28672 decimal) to `$73E8` (29672 decimal)
|
||||||
is directly mapped to video.
|
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`)
|
- Address Register (at address `$9000`)
|
||||||
- R1: Horizontal Displayed Columns
|
- R1: Horizontal Displayed Columns
|
||||||
- R6: Vertical Displayed Rows
|
- R6: Vertical Displayed Rows
|
||||||
@@ -106,10 +106,21 @@ are supported:
|
|||||||
- R11: Cursor End Scan Line
|
- R11: Cursor End Scan Line
|
||||||
- R12: Display Start Address (High Byte)
|
- R12: Display Start Address (High Byte)
|
||||||
- R13: Display Start Address (Low Byte)
|
- R13: Display Start Address (Low Byte)
|
||||||
- R14: Cursor Position (High Byte) [Read Only]
|
- R14: Cursor Position (High Byte)
|
||||||
- R15: Cursor Position (Low Byte) [Read Only]
|
- 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
|
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)
|
- [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.0 Usage
|
||||||
|
|
||||||
### 4.1 Building
|
### 4.1 Building
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<groupId>com.loomcom.symon</groupId>
|
<groupId>com.loomcom.symon</groupId>
|
||||||
<artifactId>symon</artifactId>
|
<artifactId>symon</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<version>0.9.0</version>
|
<version>0.9.0.1</version>
|
||||||
<name>symon</name>
|
<name>symon</name>
|
||||||
<url>http://www.loomcom.com/symon</url>
|
<url>http://www.loomcom.com/symon</url>
|
||||||
<properties>
|
<properties>
|
||||||
@@ -44,7 +44,13 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.grahamedgecombe.jterminal</groupId>
|
<groupId>com.grahamedgecombe.jterminal</groupId>
|
||||||
<artifactId>jterminal</artifactId>
|
<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>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
@@ -125,47 +131,6 @@
|
|||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</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>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>exec-maven-plugin</artifactId>
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
@@ -183,15 +148,4 @@
|
|||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
<reporting>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
|
||||||
<artifactId>cobertura-maven-plugin</artifactId>
|
|
||||||
<version>2.6</version>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</reporting>
|
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@@ -23,10 +23,12 @@
|
|||||||
|
|
||||||
package com.loomcom.symon;
|
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 java.util.SortedSet;
|
||||||
import com.loomcom.symon.exceptions.*;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Bus ties the whole thing together, man.
|
* The Bus ties the whole thing together, man.
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class Crtc extends Device {
|
|||||||
|
|
||||||
// Memory locations in the CRTC address space
|
// Memory locations in the CRTC address space
|
||||||
public static final int REGISTER_SELECT = 0;
|
public static final int REGISTER_SELECT = 0;
|
||||||
public static final int REGISTER_WRITE = 1;
|
public static final int REGISTER_RW = 1;
|
||||||
|
|
||||||
// Registers
|
// Registers
|
||||||
public static final int HORIZONTAL_DISPLAYED = 1;
|
public static final int HORIZONTAL_DISPLAYED = 1;
|
||||||
@@ -90,25 +90,26 @@ public class Crtc extends Device {
|
|||||||
case REGISTER_SELECT:
|
case REGISTER_SELECT:
|
||||||
setCurrentRegister(data);
|
setCurrentRegister(data);
|
||||||
break;
|
break;
|
||||||
case REGISTER_WRITE:
|
case REGISTER_RW:
|
||||||
writeRegisterValue(data);
|
writeRegisterValue(data);
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
throw new MemoryAccessException("No such address.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(int address) throws MemoryAccessException {
|
public int read(int address) throws MemoryAccessException {
|
||||||
switch (address) {
|
switch (address) {
|
||||||
case REGISTER_SELECT:
|
case REGISTER_RW:
|
||||||
return status();
|
switch (currentRegister) {
|
||||||
case REGISTER_WRITE:
|
case CURSOR_POSITION_LOW:
|
||||||
return 0;
|
return cursorPosition & 0xff;
|
||||||
|
case CURSOR_POSITION_HIGH:
|
||||||
|
return cursorPosition >> 8;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new MemoryAccessException("No such address.");
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,10 +122,6 @@ public class Crtc extends Device {
|
|||||||
return memory.getDmaAccess();
|
return memory.getDmaAccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int status() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getHorizontalDisplayed() {
|
public int getHorizontalDisplayed() {
|
||||||
return horizontalDisplayed;
|
return horizontalDisplayed;
|
||||||
}
|
}
|
||||||
@@ -169,7 +166,10 @@ public class Crtc extends Device {
|
|||||||
this.currentRegister = registerNumber;
|
this.currentRegister = registerNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeRegisterValue(int data) {
|
private void writeRegisterValue(int data) throws MemoryAccessException {
|
||||||
|
int oldStartAddress = startAddress;
|
||||||
|
int oldCursorPosition = cursorPosition;
|
||||||
|
|
||||||
switch (currentRegister) {
|
switch (currentRegister) {
|
||||||
case HORIZONTAL_DISPLAYED:
|
case HORIZONTAL_DISPLAYED:
|
||||||
horizontalDisplayed = data;
|
horizontalDisplayed = data;
|
||||||
@@ -213,24 +213,31 @@ public class Crtc extends Device {
|
|||||||
break;
|
break;
|
||||||
case DISPLAY_START_HIGH:
|
case DISPLAY_START_HIGH:
|
||||||
startAddress = ((data & 0xff) << 8) | (startAddress & 0x00ff);
|
startAddress = ((data & 0xff) << 8) | (startAddress & 0x00ff);
|
||||||
// TODO: bounds checking.
|
|
||||||
break;
|
break;
|
||||||
case DISPLAY_START_LOW:
|
case DISPLAY_START_LOW:
|
||||||
startAddress = ((data & 0xff) | (startAddress & 0xff00));
|
startAddress = ((data & 0xff) | (startAddress & 0xff00));
|
||||||
// TODO: bounds checking.
|
|
||||||
break;
|
break;
|
||||||
case CURSOR_POSITION_HIGH:
|
case CURSOR_POSITION_HIGH:
|
||||||
cursorPosition = ((data & 0xff) << 8) | (cursorPosition & 0x00ff);
|
cursorPosition = ((data & 0xff) << 8) | (cursorPosition & 0x00ff);
|
||||||
// TODO: bounds checking.
|
|
||||||
break;
|
break;
|
||||||
case CURSOR_POSITION_LOW:
|
case CURSOR_POSITION_LOW:
|
||||||
// TODO: bounds checking.
|
|
||||||
cursorPosition = (data & 0xff) | (cursorPosition & 0xff00);
|
cursorPosition = (data & 0xff) | (cursorPosition & 0xff00);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
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();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in new issue
Block a user