6 Commits
Author SHA1 Message Date
Seth Morabito a7ea60d250 Bump version to 0.9.1.0 2014-01-26 01:29:09 -08:00
Seth Morabito 1bc8eda38d ACIA interrupt handling 2014-01-26 01:25:12 -08:00
Seth Morabito 76dcbd712a IRQ and NMI handling at CPU level. 2014-01-25 20:45:39 -08:00
Seth Morabito b6cc480919 Add NMI flag to CPU 2014-01-25 20:02:17 -08:00
Seth Morabito 0c5035fc56 Add interrupt flag to CPU 2014-01-25 19:53:53 -08:00
Seth Morabito 3d36cd5460 Improved screenshots 2013-12-29 18:43:29 -08:00
20 changed files with 860 additions and 373 deletions

No files matched your search

+4 -4
View File
@@ -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.1 **Version:** 0.9.1.0
**Last Updated:** 29 December, 2013 **Last Updated:** 29 December, 2013
@@ -154,7 +154,7 @@ Maven will build Symon, run unit tests, and produce a jar file in the
Symon is meant to be invoked directly from the jar file. To run with Symon is meant to be invoked directly from the jar file. To run with
Java 1.5 or greater, just type: Java 1.5 or greater, just type:
$ java -jar symon-0.9.0.jar $ java -jar symon-0.9.1.0.jar
When Symon is running, you should be presented with a simple graphical When Symon is running, you should be presented with a simple graphical
interface. interface.
@@ -196,6 +196,8 @@ running.
## 5.0 Revision History ## 5.0 Revision History
- **0.9.1:** 26 January, 2014 - Support for IRQ and NMI handling.
- **0.9.0:** 29 December, 2013 - First pass at a 6545 CRTC simulation. - **0.9.0:** 29 December, 2013 - First pass at a 6545 CRTC simulation.
- **0.8.5:** 30 March, 2013 - ASCII display for memory window. - **0.8.5:** 30 March, 2013 - ASCII display for memory window.
@@ -234,8 +236,6 @@ running.
- More accurate timing. - More accurate timing.
- Smarter interrupt handling.
- UI needs a ton more polish. - UI needs a ton more polish.
- More extensive testing. - More extensive testing.
+1 -1
View File
@@ -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.1</version> <version>0.9.1.0</version>
<name>symon</name> <name>symon</name>
<url>http://www.loomcom.com/symon</url> <url>http://www.loomcom.com/symon</url>
<properties> <properties>
+7 -3
View File
@@ -6,18 +6,22 @@ Sample Programs
When loaded at address $0300, this program will print "Hello, 6502 World!" in When loaded at address $0300, this program will print "Hello, 6502 World!" in
infinite loop. infinite loop.
2. echo.prg 2. echo_poll.prg
When loaded at address $0300, this program will echo back to the console When loaded at address $0300, this program will echo back to the console
anything typed. anything typed.
Both hello.prg and echo.prg were assembled with the Ophis assembler: Both hello.prg and echo_poll.prg were assembled with the Ophis assembler:
https://hkn.eecs.berkeley.edu/~mcmartin/ophis/ https://hkn.eecs.berkeley.edu/~mcmartin/ophis/
3. echo_irq.rom
3. ehbasic.rom This is another echo program, and behaves identically to echo_poll.prg,
except it is interrupt-driven.
4. ehbasic.rom
This is Lee Davison's Enhanced 6502 BASIC. This is Lee Davison's Enhanced 6502 BASIC.
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
*.rom
*.lst
*.map
*.o
+13
View File
@@ -0,0 +1,13 @@
CA=ca65
LD=ld65
all: echo_irq
echo_irq: echo_irq.o
$(LD) -C symon.config -vm -m echo_irq.map -o echo_irq.rom echo_irq.o
echo_irq.o:
$(CA) --listing -o echo_irq.o echo_irq.asm
clean:
rm -f *.o *.rom *.map *.lst
+48
View File
@@ -0,0 +1,48 @@
;;
;; Read input from the keyboard, and echo to console.
;;
IOBASE = $8800
IOSTATUS = IOBASE + 1
IOCMD = IOBASE + 2
IOCTRL = IOBASE + 3
.segment "CODE"
.org $C000
START: CLI
LDA #$09
STA IOCMD ; Set command status
LDA #$1A
STA IOCTRL ; 0 stop bits, 8 bit word, 2400 baud
;;
;; Infinite idle loop, waiting for interrupt.
;;
IDLE: LDA #$FF
TAX
IDLE1: DEX
BNE IDLE1
JMP IDLE
;;
;; IRQ handler
;;
IRQ: LDA IOBASE ; Get the character in the ACIA.
PHA ; Save accumulator
ECHO1: LDA IOSTATUS ; Read the ACIA status
AND #$10 ; Is the tx register empty?
BEQ ECHO1 ; No, wait for it to empty
PLA ; Otherwise, load saved accumulator,
STA IOBASE ; write to output,
RTI ; and return
; system vectors
.segment "VECTORS"
.org $FFFA
.word IRQ ; NMI vector
.word START ; RESET vector
.word IRQ ; IRQ vector
+16
View File
@@ -0,0 +1,16 @@
MEMORY {
RAM1: start = $0000, size = $8000;
ROM1: start = $C000, size = $3FFA, fill = yes;
ROMV: start = $FFFA, size = $6, file = %O, fill = yes;
}
SEGMENTS {
CODE: load = ROM1, type = ro;
DATA: load = ROM1, type = ro;
VECTORS: load = ROMV, type = ro;
}
SYMBOLS {
__STACKSIZE__ = $0300;
}
File renamed without changes.
File renamed without changes.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 73 KiB

+24
View File
@@ -151,6 +151,30 @@ public class Bus {
throw new MemoryAccessException("Bus write failed. No device at address " + String.format("$%04X", address)); throw new MemoryAccessException("Bus write failed. No device at address " + String.format("$%04X", address));
} }
public void assertIrq() {
if (cpu != null) {
cpu.assertIrq();
}
}
public void clearIrq() {
if (cpu != null) {
cpu.clearIrq();
}
}
public void assertNmi() {
if (cpu != null) {
cpu.assertNmi();
}
}
public void clearNmi() {
if (cpu != null) {
cpu.clearNmi();
}
}
public SortedSet<Device> getDevices() { public SortedSet<Device> getDevices() {
// Expose a copy of the device list, not the original // Expose a copy of the device list, not the original
return new TreeSet<Device>(devices); return new TreeSet<Device>(devices);
+75 -12
View File
@@ -113,7 +113,9 @@ public class Cpu implements InstructionTable {
* Reset the CPU to known initial values. * Reset the CPU to known initial values.
*/ */
public void reset() throws MemoryAccessException { public void reset() throws MemoryAccessException {
// Registers /* TODO: In reality, the stack pointer could be anywhere
on the stack after reset. This non-deterministic behavior might be
worth while to simulate. */
state.sp = 0xff; state.sp = 0xff;
// Set the PC to the address stored in the reset vector // Set the PC to the address stored in the reset vector
@@ -131,6 +133,8 @@ public class Cpu implements InstructionTable {
state.overflowFlag = false; state.overflowFlag = false;
state.negativeFlag = false; state.negativeFlag = false;
state.irqAsserted = false;
// Clear illegal opcode trap. // Clear illegal opcode trap.
state.opTrap = false; state.opTrap = false;
@@ -150,12 +154,20 @@ public class Cpu implements InstructionTable {
} }
/** /**
* Performs an individual machine cycle. * Performs an individual instruction cycle.
*/ */
public void step() throws MemoryAccessException { public void step() throws MemoryAccessException {
// Store the address from which the IR was read, for debugging // Store the address from which the IR was read, for debugging
state.lastPc = state.pc; state.lastPc = state.pc;
// Check for Interrupts before doing anything else.
// This will set the PC and jump to the interrupt vector.
if (state.nmiAsserted) {
handleNmi();
} else if (state.irqAsserted && !getIrqDisableFlag()) {
handleIrq(state.pc);
}
// Fetch memory location for this instruction. // Fetch memory location for this instruction.
state.ir = bus.read(state.pc); state.ir = bus.read(state.pc);
irAddressMode = (state.ir >> 2) & 0x07; irAddressMode = (state.ir >> 2) & 0x07;
@@ -247,16 +259,7 @@ public class Cpu implements InstructionTable {
/** Single Byte Instructions; Implied and Relative **/ /** Single Byte Instructions; Implied and Relative **/
case 0x00: // BRK - Force Interrupt - Implied case 0x00: // BRK - Force Interrupt - Implied
if (!getIrqDisableFlag()) { if (!getIrqDisableFlag()) {
// Set the break flag before pushing. handleIrq(state.pc + 1);
setBreakFlag();
// Push program counter + 1 onto the stack
stackPush((state.pc + 1 >> 8) & 0xff); // PC high byte
stackPush(state.pc + 1 & 0xff); // PC low byte
stackPush(state.getStatusFlag());
// Set the Interrupt Disabled flag. RTI will clear it.
setIrqDisableFlag();
// Load interrupt vector address into PC
state.pc = address(bus.read(IRQ_VECTOR_L), bus.read(IRQ_VECTOR_H));
} }
break; break;
case 0x08: // PHP - Push Processor Status - Implied case 0x08: // PHP - Push Processor Status - Implied
@@ -729,6 +732,35 @@ public class Cpu implements InstructionTable {
delayLoop(state.ir); delayLoop(state.ir);
} }
private void handleIrq(int returnPc) throws MemoryAccessException {
handleInterrupt(returnPc, IRQ_VECTOR_L, IRQ_VECTOR_H);
clearIrq();
}
private void handleNmi() throws MemoryAccessException {
handleInterrupt(state.pc, NMI_VECTOR_L, NMI_VECTOR_H);
clearNmi();
}
/**
* Handle the common behavior of BRK, /IRQ, and /NMI
*
* @throws MemoryAccessException
*/
private void handleInterrupt(int returnPc, int vectorLow, int vectorHigh) throws MemoryAccessException {
// Set the break flag before pushing.
setBreakFlag();
// Push program counter + 1 onto the stack
stackPush((returnPc >> 8) & 0xff); // PC high byte
stackPush(returnPc & 0xff); // PC low byte
stackPush(state.getStatusFlag());
// Set the Interrupt Disabled flag. RTI will clear it.
setIrqDisableFlag();
// Load interrupt vector address into PC
state.pc = address(bus.read(vectorLow), bus.read(vectorHigh));
}
/** /**
* Add with Carry, used by all addressing mode implementations of ADC. * Add with Carry, used by all addressing mode implementations of ADC.
* As a side effect, this will set the overflow and carry flags as * As a side effect, this will set the overflow and carry flags as
@@ -1161,6 +1193,34 @@ public class Cpu implements InstructionTable {
return state.getStatusFlag(); return state.getStatusFlag();
} }
/**
* Simulate transition from logic-high to logic-low on the INT line.
*/
public void assertIrq() {
state.irqAsserted = true;
}
/**
* Simulate transition from logic-low to logic-high of the INT line.
*/
public void clearIrq() {
state.irqAsserted = false;
}
/**
* Simulate transition from logic-high to logic-low on the NMI line.
*/
public void assertNmi() {
state.nmiAsserted = true;
}
/**
* Simulate transition from logic-low to logic-high of the NMI line.
*/
public void clearNmi() {
state.nmiAsserted = false;
}
/** /**
* Push an item onto the stack, and decrement the stack counter. * Push an item onto the stack, and decrement the stack counter.
* Will wrap-around if already at the bottom of the stack (This * Will wrap-around if already at the bottom of the stack (This
@@ -1305,6 +1365,8 @@ public class Cpu implements InstructionTable {
public int[] args = new int[2]; public int[] args = new int[2];
public int instSize; public int instSize;
public boolean opTrap; public boolean opTrap;
public boolean irqAsserted;
public boolean nmiAsserted;
/* Status Flag Register bits */ /* Status Flag Register bits */
public boolean carryFlag; public boolean carryFlag;
@@ -1341,6 +1403,7 @@ public class Cpu implements InstructionTable {
this.args[1] = s.args[1]; this.args[1] = s.args[1];
this.instSize = s.instSize; this.instSize = s.instSize;
this.opTrap = s.opTrap; this.opTrap = s.opTrap;
this.irqAsserted = s.irqAsserted;
this.carryFlag = s.carryFlag; this.carryFlag = s.carryFlag;
this.negativeFlag = s.negativeFlag; this.negativeFlag = s.negativeFlag;
this.zeroFlag = s.zeroFlag; this.zeroFlag = s.zeroFlag;
@@ -23,10 +23,8 @@
package com.loomcom.symon.devices; package com.loomcom.symon.devices;
import com.loomcom.symon.exceptions.*; import com.loomcom.symon.exceptions.MemoryAccessException;
import com.loomcom.symon.exceptions.MemoryRangeException;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
@@ -58,6 +56,8 @@ public class Acia extends Device {
private int commandRegister; private int commandRegister;
private int controlRegister; private int controlRegister;
private boolean receiveIrqEnabled = false;
private boolean transmitIrqEnabled = false;
private boolean overrun = false; private boolean overrun = false;
private long lastTxWrite = 0; private long lastTxWrite = 0;
@@ -105,7 +105,7 @@ public class Acia extends Device {
reset(); reset();
break; break;
case 2: case 2:
commandRegister = data; setCommandRegister(data);
break; break;
case 3: case 3:
setControlRegister(data); setControlRegister(data);
@@ -115,13 +115,23 @@ public class Acia extends Device {
} }
} }
private void setCommandRegister(int data) {
commandRegister = data;
// Bit 1 controls receiver IRQ behavior
receiveIrqEnabled = (commandRegister & 0x02) == 0;
// Bits 2 & 3 controls transmit IRQ behavior
transmitIrqEnabled = (commandRegister & 0x08) == 0 && (commandRegister & 0x04) != 0;
}
/** /**
* Set the control register and associated state. * Set the control register and associated state.
* *
* @param data * @param data
*/ */
public void setControlRegister(int data) { private void setControlRegister(int data) {
this.controlRegister = data; controlRegister = data;
// If the value of the data is 0, this is a request to reset, // If the value of the data is 0, this is a request to reset,
// otherwise it's a control update. // otherwise it's a control update.
@@ -245,18 +255,28 @@ public class Acia extends Device {
public synchronized void rxWrite(int data) { public synchronized void rxWrite(int data) {
rxFull = true; rxFull = true;
if (receiveIrqEnabled) {
getBus().assertIrq();
}
rxChar = data; rxChar = data;
} }
public synchronized int txRead() { public synchronized int txRead() {
txEmpty = true; txEmpty = true;
if (transmitIrqEnabled) {
getBus().assertIrq();
}
return txChar; return txChar;
} }
public synchronized void txWrite(int data) { public synchronized void txWrite(int data) {
lastTxWrite = System.nanoTime(); lastTxWrite = System.nanoTime();
txEmpty = false;
txChar = data; txChar = data;
txEmpty = false;
} }
/** /**
@@ -278,6 +298,8 @@ public class Acia extends Device {
txEmpty = true; txEmpty = true;
rxChar = 0; rxChar = 0;
rxFull = false; rxFull = false;
receiveIrqEnabled = false;
transmitIrqEnabled = false;
} }
} }
+74 -4
View File
@@ -1,13 +1,83 @@
package com.loomcom.symon; package com.loomcom.symon;
import org.junit.*;
import com.loomcom.symon.devices.Acia; import com.loomcom.symon.devices.Acia;
import com.loomcom.symon.exceptions.FifoUnderrunException; import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
public class AciaTest { public class AciaTest {
@Test
public void shouldTriggerInterruptOnRxFullIfRxIrqEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia(0x000);
acia.setBus(mockBus);
// Disable TX IRQ, Enable RX IRQ
acia.write(2, 0x00);
acia.rxWrite('a');
verify(mockBus, atLeastOnce()).assertIrq();
}
@Test
public void shouldNotTriggerInterruptOnRxFullIfRxIrqNotEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia(0x000);
acia.setBus(mockBus);
// Disable TX IRQ, Disable RX IRQ
acia.write(2, 0x02);
acia.rxWrite('a');
verify(mockBus, never()).assertIrq();
}
@Test
public void shouldTriggerInterruptOnTxEmptyIfTxIrqEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia(0x000);
acia.setBus(mockBus);
// Enable TX IRQ, Disable RX IRQ
acia.write(2, 0x06);
// Write data
acia.write(0, 'a');
verify(mockBus, never()).assertIrq();
// Transmission should cause IRQ
acia.txRead();
verify(mockBus, atLeastOnce()).assertIrq();
}
@Test
public void shouldNotTriggerInterruptOnTxEmptyIfTxIrqNotEnabled() throws Exception {
Bus mockBus = mock(Bus.class);
Acia acia = new Acia(0x000);
acia.setBus(mockBus);
// Disable TX IRQ, Disable RX IRQ
acia.write(2, 0x02);
// Write data
acia.write(0, 'a');
// Transmission should cause IRQ
acia.txRead();
verify(mockBus, never()).assertIrq();
}
@Test @Test
public void newAciaShouldHaveTxEmptyStatus() throws Exception { public void newAciaShouldHaveTxEmptyStatus() throws Exception {
Acia acia = new Acia(0x000); Acia acia = new Acia(0x000);
+132 -100
View File
@@ -10,113 +10,145 @@ import com.loomcom.symon.exceptions.*;
*/ */
public class BusTest extends TestCase { public class BusTest extends TestCase {
public BusTest(String testName) { public BusTest(String testName) {
super(testName); super(testName);
}
public static Test suite() {
return new TestSuite(BusTest.class);
}
public void testCreatingWithStartAndEndAddresses() {
Bus b = null;
b = new Bus(0x00, 0xff);
assertEquals(0x00, b.startAddress());
assertEquals(0xff, b.endAddress());
b = new Bus(0x20, 0xea);
assertEquals(0x20, b.startAddress());
assertEquals(0xea, b.endAddress());
}
public void testCreatingWithSize() {
Bus b = null;
b = new Bus(256);
assertEquals(0x00, b.startAddress());
assertEquals(0xff, b.endAddress());
b = new Bus(4096);
assertEquals(0x000, b.startAddress());
assertEquals(0xfff, b.endAddress());
b = new Bus(65536);
assertEquals(0x0000, b.startAddress());
assertEquals(0xffff, b.endAddress());
}
public void testAddDevice() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0x00ff, true);
Device rom = new Memory(0x0100, 0x02ff, false);
Bus b = new Bus(0x0000, 0xffff);
assertEquals(0, b.getDevices().size());
b.addDevice(memory);
assertEquals(1, b.getDevices().size());
b.addDevice(rom);
assertEquals(2, b.getDevices().size());
}
public void testOverlappingDevicesShouldFail() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0x0100, true);
Device rom = new Memory(0x00ff, 0x0200, false);
Bus b = new Bus(0x0000, 0xffff);
b.addDevice(memory);
try {
b.addDevice(rom);
fail("Should have thrown a MemoryRangeException.");
} catch (MemoryRangeException ex) {
// expected
} }
}
public void testIsCompleteWithFirstDeviceNotStartingAtStartAddress() throws MemoryRangeException { public static Test suite() {
Device memory = new Memory(0x00ff, 0xff00, true); return new TestSuite(BusTest.class);
}
Bus b = new Bus(0x0000, 0xffff); public void testCreatingWithStartAndEndAddresses() {
assertFalse("Address space was unexpectedly complete!", b.isComplete()); Bus b = null;
b.addDevice(memory);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
}
public void testIsCompleteWithOneDevice() throws MemoryRangeException { b = new Bus(0x00, 0xff);
Device memory = new Memory(0x0000, 0xffff, true); assertEquals(0x00, b.startAddress());
Bus b = new Bus(0x0000, 0xffff); assertEquals(0xff, b.endAddress());
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(memory);
assertTrue("Address space should have been complete!", b.isComplete());
}
public void testIsCompleteWithTwoDevices() throws MemoryRangeException { b = new Bus(0x20, 0xea);
Device memory = new Memory(0x0000, 0x7fff, true); assertEquals(0x20, b.startAddress());
Device rom = new Memory(0x8000, 0xffff, false); assertEquals(0xea, b.endAddress());
}
Bus b = new Bus(0x0000, 0xffff); public void testCreatingWithSize() {
assertFalse("Address space was unexpectedly complete!", b.isComplete()); Bus b = null;
b.addDevice(memory);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(rom);
assertTrue("Address space should have been complete!", b.isComplete());
}
public void testIsCompleteWithThreeDevices() throws MemoryRangeException { b = new Bus(256);
Device memory = new Memory(0x0000, 0x7fff, true); assertEquals(0x00, b.startAddress());
Device rom1 = new Memory(0x8000, 0xBfff, false); assertEquals(0xff, b.endAddress());
Device rom2 = new Memory(0xC000, 0xffff, false);
Bus b = new Bus(0x0000, 0xffff); b = new Bus(4096);
assertFalse("Address space was unexpectedly complete!", b.isComplete()); assertEquals(0x000, b.startAddress());
b.addDevice(memory); assertEquals(0xfff, b.endAddress());
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(rom1); b = new Bus(65536);
assertFalse("Address space was unexpectedly complete!", b.isComplete()); assertEquals(0x0000, b.startAddress());
b.addDevice(rom2); assertEquals(0xffff, b.endAddress());
assertTrue("Address space should have been complete!", b.isComplete()); }
}
public void testAddDevice() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0x00ff, true);
Device rom = new Memory(0x0100, 0x02ff, false);
Bus b = new Bus(0x0000, 0xffff);
assertEquals(0, b.getDevices().size());
b.addDevice(memory);
assertEquals(1, b.getDevices().size());
b.addDevice(rom);
assertEquals(2, b.getDevices().size());
}
public void testOverlappingDevicesShouldFail() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0x0100, true);
Device rom = new Memory(0x00ff, 0x0200, false);
Bus b = new Bus(0x0000, 0xffff);
b.addDevice(memory);
try {
b.addDevice(rom);
fail("Should have thrown a MemoryRangeException.");
} catch (MemoryRangeException ex) {
// expected
}
}
public void testIsCompleteWithFirstDeviceNotStartingAtStartAddress() throws MemoryRangeException {
Device memory = new Memory(0x00ff, 0xff00, true);
Bus b = new Bus(0x0000, 0xffff);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(memory);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
}
public void testIsCompleteWithOneDevice() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0xffff, true);
Bus b = new Bus(0x0000, 0xffff);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(memory);
assertTrue("Address space should have been complete!", b.isComplete());
}
public void testIsCompleteWithTwoDevices() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0x7fff, true);
Device rom = new Memory(0x8000, 0xffff, false);
Bus b = new Bus(0x0000, 0xffff);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(memory);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(rom);
assertTrue("Address space should have been complete!", b.isComplete());
}
public void testIsCompleteWithThreeDevices() throws MemoryRangeException {
Device memory = new Memory(0x0000, 0x7fff, true);
Device rom1 = new Memory(0x8000, 0xBfff, false);
Device rom2 = new Memory(0xC000, 0xffff, false);
Bus b = new Bus(0x0000, 0xffff);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(memory);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(rom1);
assertFalse("Address space was unexpectedly complete!", b.isComplete());
b.addDevice(rom2);
assertTrue("Address space should have been complete!", b.isComplete());
}
public void testSetAndClearIrq() throws Exception {
Bus b = new Bus(0x0000, 0xffff);
Cpu c = new Cpu();
b.addCpu(c);
assertFalse(c.getCpuState().irqAsserted);
b.assertIrq();
assertTrue(c.getCpuState().irqAsserted);
b.clearIrq();
assertFalse(c.getCpuState().irqAsserted);
}
public void testSetAndClearNmi() throws Exception {
Bus b = new Bus(0x0000, 0xffff);
Cpu c = new Cpu();
b.addCpu(c);
assertFalse(c.getCpuState().nmiAsserted);
b.assertNmi();
assertTrue(c.getCpuState().nmiAsserted);
b.clearNmi();
assertFalse(c.getCpuState().nmiAsserted);
}
} }
@@ -7,303 +7,303 @@ import junit.framework.*;
public class CpuAccumulatorModeTest extends TestCase { public class CpuAccumulatorModeTest extends TestCase {
protected Cpu cpu; protected Cpu cpu;
protected Bus bus; protected Bus bus;
protected Memory mem; protected Memory mem;
public void setUp() throws MemoryRangeException, MemoryAccessException { public void setUp() throws MemoryRangeException, MemoryAccessException {
this.cpu = new Cpu(); this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff); this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0x10000); this.mem = new Memory(0x0000, 0x10000);
bus.addCpu(cpu); bus.addCpu(cpu);
bus.addDevice(mem); bus.addDevice(mem);
// Load the reset vector. // Load the reset vector.
bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff); bus.write(0xfffc, Bus.DEFAULT_LOAD_ADDRESS & 0x00ff);
bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00)>>>8); bus.write(0xfffd, (Bus.DEFAULT_LOAD_ADDRESS & 0xff00) >>> 8);
cpu.reset(); cpu.reset();
// Assert initial state // Assert initial state
assertEquals(0, cpu.getAccumulator()); assertEquals(0, cpu.getAccumulator());
assertEquals(0, cpu.getXRegister()); assertEquals(0, cpu.getXRegister());
assertEquals(0, cpu.getYRegister()); assertEquals(0, cpu.getYRegister());
assertEquals(0x200, cpu.getProgramCounter()); assertEquals(0x200, cpu.getProgramCounter());
assertEquals(0xff, cpu.getStackPointer()); assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x20, cpu.getProcessorStatus()); assertEquals(0x20, cpu.getProcessorStatus());
} }
/* /*
* The following opcodes are tested for correctness in this file: * The following opcodes are tested for correctness in this file:
* *
* ASL - $0a * ASL - $0a
* ROL - $2a * ROL - $2a
* LSR - $4a * LSR - $4a
* ROR - $6a * ROR - $6a
*/ */
/* ASL - Arithmetic Shift Left - $0a */ /* ASL - Arithmetic Shift Left - $0a */
public void test_ASL() throws MemoryAccessException { public void test_ASL() throws MemoryAccessException {
bus.loadProgram(0xa9, 0x00, // LDA #$00 bus.loadProgram(0xa9, 0x00, // LDA #$00
0x0a, // ASL A 0x0a, // ASL A
0xa9, 0x01, // LDA #$01 0xa9, 0x01, // LDA #$01
0x0a, // ASL A 0x0a, // ASL A
0xa9, 0x02, // LDA #$02 0xa9, 0x02, // LDA #$02
0x0a, // ASL A 0x0a, // ASL A
0xa9, 0x44, // LDA #$44 0xa9, 0x44, // LDA #$44
0x0a, // ASL A 0x0a, // ASL A
0xa9, 0x80, // LDA #$80 0xa9, 0x80, // LDA #$80
0x0a); // ASL A 0x0a); // ASL A
cpu.step(2); cpu.step(2);
assertEquals(0x00, cpu.getAccumulator()); assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag()); assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(2); cpu.step(2);
assertEquals(0x02, cpu.getAccumulator()); assertEquals(0x02, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(2); cpu.step(2);
assertEquals(0x04, cpu.getAccumulator()); assertEquals(0x04, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(2); cpu.step(2);
assertEquals(0x88, cpu.getAccumulator()); assertEquals(0x88, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag()); assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(2); cpu.step(2);
assertEquals(0x00, cpu.getAccumulator()); assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag()); assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag()); assertTrue(cpu.getCarryFlag());
} }
/* ROL - Rotate Left - $2a */ /* ROL - Rotate Left - $2a */
public void test_ROL() throws MemoryAccessException { public void test_ROL() throws MemoryAccessException {
bus.loadProgram(0xa9, 0x00, // LDA #$00 bus.loadProgram(0xa9, 0x00, // LDA #$00
0x2a, // ROL A (m=%00000000, c=0) 0x2a, // ROL A (m=%00000000, c=0)
0xa9, 0x01, // LDA #$01 0xa9, 0x01, // LDA #$01
0x2a, // ROL A (m=%00000010, c=0) 0x2a, // ROL A (m=%00000010, c=0)
0x38, // SEC (m=%00000010, c=1) 0x38, // SEC (m=%00000010, c=1)
0x2a, // ROL A (m=%00000101, c=0) 0x2a, // ROL A (m=%00000101, c=0)
0x2a, // ROL A (m=%00001010, c=0) 0x2a, // ROL A (m=%00001010, c=0)
0x2a, // ROL A (m=%00010100, c=0) 0x2a, // ROL A (m=%00010100, c=0)
0x2a, // ROL A (m=%00101000, c=0) 0x2a, // ROL A (m=%00101000, c=0)
0x2a, // ROL A (m=%01010000, c=0) 0x2a, // ROL A (m=%01010000, c=0)
0x2a, // ROL A (m=%10100000, c=0) 0x2a, // ROL A (m=%10100000, c=0)
0x2a, // ROL A (m=%01000000, c=1) 0x2a, // ROL A (m=%01000000, c=1)
0x2a); // ROL A (m=%10000001, c=0) 0x2a); // ROL A (m=%10000001, c=0)
cpu.step(2); cpu.step(2);
assertEquals(0x00, cpu.getAccumulator()); assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag()); assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(2); cpu.step(2);
assertEquals(0x02, cpu.getAccumulator()); assertEquals(0x02, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(2); cpu.step(2);
assertEquals(0x05, cpu.getAccumulator()); assertEquals(0x05, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(); cpu.step();
assertEquals(0x0a, cpu.getAccumulator()); assertEquals(0x0a, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(); cpu.step();
assertEquals(0x14, cpu.getAccumulator()); assertEquals(0x14, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(); cpu.step();
assertEquals(0x28, cpu.getAccumulator()); assertEquals(0x28, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(); cpu.step();
assertEquals(0x50, cpu.getAccumulator()); assertEquals(0x50, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(); cpu.step();
assertEquals(0xa0, cpu.getAccumulator()); assertEquals(0xa0, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag()); assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(); cpu.step();
assertEquals(0x40, cpu.getAccumulator()); assertEquals(0x40, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag()); assertTrue(cpu.getCarryFlag());
cpu.step(); cpu.step();
assertEquals(0x81, cpu.getAccumulator()); assertEquals(0x81, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag()); assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
} }
/* LSR - Logical Shift Right - $4a */ /* LSR - Logical Shift Right - $4a */
public void test_LSR() throws MemoryAccessException { public void test_LSR() throws MemoryAccessException {
bus.loadProgram(0xa9, 0x00, // LDA #$00 bus.loadProgram(0xa9, 0x00, // LDA #$00
0x4a, // LSR A 0x4a, // LSR A
0xa9, 0x01, // LDA #$01 0xa9, 0x01, // LDA #$01
0x4a, // LSR A 0x4a, // LSR A
0xa9, 0x02, // LDA #$02 0xa9, 0x02, // LDA #$02
0x4a, // LSR A 0x4a, // LSR A
0xa9, 0x44, // LDA #$44 0xa9, 0x44, // LDA #$44
0x4a, // LSR A 0x4a, // LSR A
0xa9, 0x80, // LDA #$80 0xa9, 0x80, // LDA #$80
0x4a, // LSR A 0x4a, // LSR A
0x38, // SEC 0x38, // SEC
0xa9, 0x02, // LDA #$02 0xa9, 0x02, // LDA #$02
0x4a); // LSR $05 0x4a); // LSR $05
cpu.step(2); cpu.step(2);
assertEquals(0x00, cpu.getAccumulator()); assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag()); assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(2); cpu.step(2);
assertEquals(0x00, cpu.getAccumulator()); assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag()); assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag()); assertTrue(cpu.getCarryFlag());
cpu.step(2); cpu.step(2);
assertEquals(0x01, cpu.getAccumulator()); assertEquals(0x01, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(2); cpu.step(2);
assertEquals(0x22, cpu.getAccumulator()); assertEquals(0x22, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(2); cpu.step(2);
assertEquals(0x40, cpu.getAccumulator()); assertEquals(0x40, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
// Setting Carry should not affect the result. // Setting Carry should not affect the result.
cpu.step(3); cpu.step(3);
assertEquals(0x01, cpu.getAccumulator()); assertEquals(0x01, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
} }
/* ROR - Rotate Right - $6a */ /* ROR - Rotate Right - $6a */
public void test_ROR() throws MemoryAccessException { public void test_ROR() throws MemoryAccessException {
bus.loadProgram(0xa9, 0x00, // LDA #$00 bus.loadProgram(0xa9, 0x00, // LDA #$00
0x6a, // ROR A (m=%00000000, c=0) 0x6a, // ROR A (m=%00000000, c=0)
0xa9, 0x10, // LDA #$10 0xa9, 0x10, // LDA #$10
0x6a, // ROR A (m=%00001000, c=0) 0x6a, // ROR A (m=%00001000, c=0)
0x6a, // ROR A (m=%00000100, c=0) 0x6a, // ROR A (m=%00000100, c=0)
0x6a, // ROR A (m=%00000010, c=0) 0x6a, // ROR A (m=%00000010, c=0)
0x6a, // ROR A (m=%00000001, c=0) 0x6a, // ROR A (m=%00000001, c=0)
0x6a, // ROR A (m=%00000000, c=1) 0x6a, // ROR A (m=%00000000, c=1)
0x6a, // ROR A (m=%10000000, c=0) 0x6a, // ROR A (m=%10000000, c=0)
0x6a, // ROR A (m=%01000000, c=0) 0x6a, // ROR A (m=%01000000, c=0)
0x6a, // ROR A (m=%00100000, c=0) 0x6a, // ROR A (m=%00100000, c=0)
0x6a); // ROR A (m=%00010000, c=0) 0x6a); // ROR A (m=%00010000, c=0)
cpu.step(2); cpu.step(2);
assertEquals(0x00, cpu.getAccumulator()); assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag()); assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(2); cpu.step(2);
assertEquals(0x08, cpu.getAccumulator()); assertEquals(0x08, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(); cpu.step();
assertEquals(0x04, cpu.getAccumulator()); assertEquals(0x04, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(); cpu.step();
assertEquals(0x02, cpu.getAccumulator()); assertEquals(0x02, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(); cpu.step();
assertEquals(0x01, cpu.getAccumulator()); assertEquals(0x01, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(); cpu.step();
assertEquals(0x00, cpu.getAccumulator()); assertEquals(0x00, cpu.getAccumulator());
assertTrue(cpu.getZeroFlag()); assertTrue(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertTrue(cpu.getCarryFlag()); assertTrue(cpu.getCarryFlag());
cpu.step(); cpu.step();
assertEquals(0x80, cpu.getAccumulator()); assertEquals(0x80, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getNegativeFlag()); assertTrue(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(); cpu.step();
assertEquals(0x40, cpu.getAccumulator()); assertEquals(0x40, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(); cpu.step();
assertEquals(0x20, cpu.getAccumulator()); assertEquals(0x20, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
cpu.step(); cpu.step();
assertEquals(0x10, cpu.getAccumulator()); assertEquals(0x10, cpu.getAccumulator());
assertFalse(cpu.getZeroFlag()); assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
assertFalse(cpu.getCarryFlag()); assertFalse(cpu.getCarryFlag());
} }
} }
@@ -345,6 +345,197 @@ public class CpuTest extends TestCase {
assertFalse(cpu.getNegativeFlag()); assertFalse(cpu.getNegativeFlag());
} }
public void testIrq() throws Exception {
// Ensure the IRQ disable flag is cleared
cpu.clearIrqDisableFlag();
// Set the IRQ vector
bus.write(0xffff, 0x12);
bus.write(0xfffe, 0x34);
// Create an IRQ handler at 0x1234
cpu.setProgramCounter(0x1234);
bus.loadProgram(0xa9, 0x33, // LDA #$33
0x69, 0x01); // ADC #$01
cpu.setProgramCounter(0x0200);
// Create a little program at 0x0200
bus.loadProgram(0x18, // CLC
0xa9, 0x01, // LDA #$00
0x69, 0x01); // ADC #$01
cpu.step();
assertEquals(0x0201, cpu.getProgramCounter()); // First instruction executed.
assertEquals(0x00, cpu.getAccumulator());
cpu.step();
assertEquals(0x0203, cpu.getProgramCounter());
assertEquals(0x01, cpu.getAccumulator());
cpu.assertIrq();
cpu.step();
assertTrue(cpu.getIrqDisableFlag()); // Should have been set by the IRQ
assertEquals(0x1236, cpu.getProgramCounter());
assertEquals(0x33, cpu.getAccumulator());
// Be sure that the IRQ line is no longer held low
assertFalse(cpu.getCpuState().irqAsserted);
}
public void testIrqHonorsIrqDisabledFlag() throws Exception {
// Ensure the IRQ disable flag is set
cpu.setIrqDisableFlag();
// Set the IRQ vector
bus.write(0xffff, 0x12);
bus.write(0xfffe, 0x34);
// Create an IRQ handler at 0x1234
cpu.setProgramCounter(0x1234);
bus.loadProgram(0xa9, 0x33, // LDA #$33
0x69, 0x01); // ADC #$01
cpu.setProgramCounter(0x0200);
// Create a little program at 0x0200
bus.loadProgram(0x18, // CLC
0xa9, 0x01, // LDA #$00
0x69, 0x01); // ADC #$01
cpu.step();
assertEquals(0x0201, cpu.getProgramCounter()); // First instruction executed.
assertEquals(0x00, cpu.getAccumulator());
cpu.step();
assertEquals(0x0203, cpu.getProgramCounter());
assertEquals(0x01, cpu.getAccumulator());
cpu.assertIrq(); // Should be ignored, because the disable flag is set.
cpu.step();
assertTrue(cpu.getIrqDisableFlag()); // Should have been left alone.
assertEquals(0x0205, cpu.getProgramCounter());
assertEquals(0x02, cpu.getAccumulator());
}
public void testIrqPushesCorrectReturnAddressOntoStack() throws Exception {
// Ensure the IRQs are enabled
cpu.clearIrqDisableFlag();
// Set the IRQ vector
bus.write(0xffff, 0x10);
bus.write(0xfffe, 0x00);
// Create an IRQ handler at 0x1000 that just RTIs.
cpu.setProgramCounter(0x1000);
bus.loadProgram(0xea, 0xea, 0x40); // NOP, NOP, RTI
cpu.setProgramCounter(0x0200);
// Create a little program at 0x0200 with three instruction sizes.
bus.loadProgram(0x18, // CLC
0xa9, 0x01, // LDA #$01
0x6d, 0x06, 0x02, // ADC $0207
0x00, // BRK
0x03); // $03 (data @ $0206)
cpu.step(); // CLC
assertEquals(0x0201, cpu.getProgramCounter()); // First instruction executed.
assertEquals(0x00, cpu.getAccumulator());
cpu.assertIrq();
cpu.step(); // NOP
assertEquals(0x1001, cpu.getProgramCounter());
cpu.step(); // NOP
assertEquals(0x1002, cpu.getProgramCounter());
cpu.step(); // RTI: PC -> 0x0201
assertEquals(0x0201, cpu.getProgramCounter());
cpu.step(); // LDA $#01
assertEquals(0x0203, cpu.getProgramCounter());
cpu.assertIrq();
cpu.step(); // NOP
assertEquals(0x1001, cpu.getProgramCounter());
cpu.step(); // NOP
assertEquals(0x1002, cpu.getProgramCounter());
cpu.step(); // RTI: PC -> 0x0203
assertEquals(0x0203, cpu.getProgramCounter());
}
public void testNmi() throws Exception {
// Set the NMI vector to 0x1000
bus.write(0xfffb, 0x10);
bus.write(0xfffa, 0x00);
// Create an NMI handler at 0x1000
cpu.setProgramCounter(0x1000);
bus.loadProgram(0xa9, 0x33, // LDA #$33
0x69, 0x01); // ADC #$01
// Create a little program at 0x0200
cpu.setProgramCounter(0x0200);
bus.loadProgram(0x18, // CLC
0xa9, 0x01, // LDA #$00
0x69, 0x01); // ADC #$01
cpu.step();
assertEquals(0x0201, cpu.getProgramCounter()); // First instruction executed.
assertEquals(0x00, cpu.getAccumulator());
cpu.step();
assertEquals(0x0203, cpu.getProgramCounter());
assertEquals(0x01, cpu.getAccumulator());
cpu.assertNmi();
cpu.step();
assertTrue(cpu.getIrqDisableFlag()); // Should have been set by the NMI
assertEquals(0x1002, cpu.getProgramCounter());
assertEquals(0x33, cpu.getAccumulator());
// Be sure that the NMI line is no longer held low
assertFalse(cpu.getCpuState().nmiAsserted);
}
public void testNmiIgnoresIrqDisableFlag() throws Exception {
// Set the IRQ disable flag, which should be ignored by the NMI
cpu.setIrqDisableFlag();
// Set the NMI vector to 0x1000
bus.write(0xfffb, 0x10);
bus.write(0xfffa, 0x00);
// Create an NMI handler at 0x1000
cpu.setProgramCounter(0x1000);
bus.loadProgram(0xa9, 0x33, // LDA #$33
0x69, 0x01); // ADC #$01
// Create a little program at 0x0200
cpu.setProgramCounter(0x0200);
bus.loadProgram(0x18, // CLC
0xa9, 0x01, // LDA #$00
0x69, 0x01); // ADC #$01
cpu.step();
assertEquals(0x0201, cpu.getProgramCounter()); // First instruction executed.
assertEquals(0x00, cpu.getAccumulator());
cpu.step();
assertEquals(0x0203, cpu.getProgramCounter());
assertEquals(0x01, cpu.getAccumulator());
cpu.assertNmi();
cpu.step();
assertTrue(cpu.getIrqDisableFlag()); // Should have been set by the NMI
assertEquals(0x1002, cpu.getProgramCounter());
assertEquals(0x33, cpu.getAccumulator());
// Be sure that the NMI line is no longer held low
assertFalse(cpu.getCpuState().nmiAsserted);
}
public void testAddress() { public void testAddress() {
assertEquals(0xf1ea, cpu.address(0xea, 0xf1)); assertEquals(0xf1ea, cpu.address(0xea, 0xf1));
assertEquals(0x00ea, cpu.address(0xea, 0x00)); assertEquals(0x00ea, cpu.address(0xea, 0x00));