From 473b47e8322a202f20c97fe536e79b08b439a3cd Mon Sep 17 00:00:00 2001 From: "Seth J. Morabito" Date: Tue, 23 Dec 2008 02:21:27 -0800 Subject: [PATCH] At long last, implemented BRK. Lots more unit tests for stack processing, as well. --- src/main/java/com/loomcom/symon/Cpu.java | 129 +++++++- .../com/loomcom/symon/CpuImpliedModeTest.java | 130 ++++++++ src/test/java/com/loomcom/symon/CpuTest.java | 305 +++++++++++++++++- 3 files changed, 546 insertions(+), 18 deletions(-) create mode 100644 src/test/java/com/loomcom/symon/CpuImpliedModeTest.java diff --git a/src/main/java/com/loomcom/symon/Cpu.java b/src/main/java/com/loomcom/symon/Cpu.java index 4697144..3ffac94 100644 --- a/src/main/java/com/loomcom/symon/Cpu.java +++ b/src/main/java/com/loomcom/symon/Cpu.java @@ -9,6 +9,16 @@ public class Cpu implements InstructionTable { public static final int DEFAULT_BASE_ADDRESS = 0x200; + /* Process status register mnemonics */ + public static final int P_CARRY = 0x01; + public static final int P_ZERO = 0x02; + public static final int P_IRQ_DISABLE = 0x04; + public static final int P_DECIMAL = 0x08; + public static final int P_BREAK = 0x10; + // Bit 5 is always '1' + public static final int P_OVERFLOW = 0x40; + public static final int P_NEGATIVE = 0x80; + /* The Bus */ private Bus bus; @@ -19,7 +29,7 @@ public class Cpu implements InstructionTable { /* Internal Registers */ private int pc; // Program Counter register - private int sp; // Stack Pointer register + private int sp; // Stack Pointer register, offset into page 1 private int ir; // Instruction register /* Operands for the current instruction */ @@ -35,7 +45,6 @@ public class Cpu implements InstructionTable { private boolean decimalModeFlag; private boolean breakFlag; private boolean overflowFlag; - // Note: Zero Flag and Negative Flag are read directly from Accumulator. /** * Construct a new CPU. @@ -61,7 +70,7 @@ public class Cpu implements InstructionTable { */ public void reset() { // Registers - sp = 0x01ff; + sp = 0xff; // Set the PC to the address stored in 0xfffc pc = CpuUtils.address(bus.read(0xfffc), bus.read(0xfffd)); @@ -110,8 +119,15 @@ public class Cpu implements InstructionTable { // Execute switch(ir) { - case 0x00: // HLT - // TODO: Halt! + case 0x00: // BRK - Force Interrupt, Implied + if (!getIrqDisableFlag()) { + stackPush((pc >> 8) & 0xff); // PC high byte + stackPush(pc & 0xff); // PC low byte + stackPush(getProcessorStatus()); + // Load interrupt vector address into PC + pc = CpuUtils.address(bus.read(0xfffc), bus.read(0xfffd)); + setBreakFlag(); + } break; case 0x01: // n/a break; @@ -726,6 +742,14 @@ public class Cpu implements InstructionTable { this.negativeFlag = negativeFlag; } + public void setNegativeFlag() { + this.negativeFlag = true; + } + + public void clearNegativeFlag() { + this.negativeFlag = false; + } + /** * @return the carry flag */ @@ -921,6 +945,69 @@ public class Cpu implements InstructionTable { this.pc = addr; } + public int getStackPointer() { + return sp; + } + + public void setStackPointer(int offset) { + this.sp = offset; + } + + /** + * @value The value of the Process Status Register bits to be set. + */ + public void setProcessorStatus(int value) { + if ((value&P_CARRY) != 0) + setCarryFlag(); + else + clearCarryFlag(); + + if ((value&P_ZERO) != 0) + setZeroFlag(); + else + clearZeroFlag(); + + if ((value&P_IRQ_DISABLE) != 0) + setIrqDisableFlag(); + else + clearIrqDisableFlag(); + + if ((value&P_DECIMAL) != 0) + setDecimalModeFlag(); + else + clearDecimalModeFlag(); + + if ((value&P_BREAK) != 0) + setBreakFlag(); + else + clearBreakFlag(); + + if ((value&P_OVERFLOW) != 0) + setOverflowFlag(); + else + clearOverflowFlag(); + + if ((value&P_NEGATIVE) != 0) + setNegativeFlag(); + else + clearNegativeFlag(); + } + + /** + * @returns The value of the Process Status Register, as a byte. + */ + public int getProcessorStatus() { + int status = 0x20; + if (getCarryFlag()) { status |= P_CARRY; } + if (getZeroFlag()) { status |= P_ZERO; } + if (getIrqDisableFlag()) { status |= P_IRQ_DISABLE; } + if (getDecimalModeFlag()) { status |= P_DECIMAL; } + if (getBreakFlag()) { status |= P_BREAK; } + if (getOverflowFlag()) { status |= P_OVERFLOW; } + if (getNegativeFlag()) { status |= P_NEGATIVE; } + return status; + } + /** * @return A string representing the current status register state. */ @@ -955,24 +1042,38 @@ public class Cpu implements InstructionTable { /** * Push an item onto the stack, and decrement the stack counter. * Silently fails to push onto the stack if SP is - * TODO: Unit tests. */ - protected void push(int data) { - bus.write(sp, data); - if (sp > 0x100) { sp--; } + void stackPush(int data) { + bus.write(0x100+sp, data); + + if (sp == 0) + sp = 0xff; + else + --sp; } /** - * Pop a byte off the user stack, and increment the stack counter. - * TODO: Unit tests. + * Pre-increment the stack pointer, and return the top of the stack. */ - protected int pop() { - int data = bus.read(sp); - if (sp < 0x1ff) { sp++; } + int stackPop() { + if (sp == 0xff) + sp = 0x00; + else + ++sp; + + int data = bus.read(0x100+sp); + return data; } + /** + * Peek at the value currently at the top of the stack + */ + int stackPeek() { + return bus.read(0x100+sp+1); + } + /* * Increment the PC, rolling over if necessary. */ diff --git a/src/test/java/com/loomcom/symon/CpuImpliedModeTest.java b/src/test/java/com/loomcom/symon/CpuImpliedModeTest.java new file mode 100644 index 0000000..094ba90 --- /dev/null +++ b/src/test/java/com/loomcom/symon/CpuImpliedModeTest.java @@ -0,0 +1,130 @@ +package com.loomcom.symon; + +import com.loomcom.symon.devices.Memory; +import com.loomcom.symon.exceptions.MemoryRangeException; +import junit.framework.*; + +public class CpuImpliedModeTest extends TestCase { + + protected Cpu cpu; + protected Bus bus; + protected Memory mem; + + public void setUp() throws MemoryRangeException { + this.cpu = new Cpu(); + this.bus = new Bus(0x0000, 0xffff); + this.mem = new Memory(0x0000, 0x10000); + bus.addCpu(cpu); + bus.addDevice(mem); + + // Load the reset vector. + bus.write(0xfffc, Cpu.DEFAULT_BASE_ADDRESS & 0x00ff); + bus.write(0xfffd, (Cpu.DEFAULT_BASE_ADDRESS & 0xff00)>>>8); + + cpu.reset(); + } + + /* + * The following opcodes are tested for correctness in this file: + * + * BRK - $00 + * CLC - $18 + * CLD - $d8 + * CLI - $58 + * CLV - $B8 + * + * DEX - $ca + * DEY - $88 + * INX - $e8 + * INY - $c8 + * NOP - $ea + * + * PHA - $48 + * PHP - $08 + * PLA - $68 + * PLP - $28 + * RTI - $40 + * + * RTS - $60 + * SEC - $38 + * SED - $f8 + * SEI - $78 + * TAX - $aa + * + * TAY - $a8 + * TSX - $ba + * TXA - $8a + * TXS - $9a + * TYA - $98 + */ + + /* BRK Tests - 0x00 */ + + public void test_BRK() { + cpu.setCarryFlag(); + cpu.setOverflowFlag(); + assertEquals(0x20|Cpu.P_CARRY|Cpu.P_OVERFLOW, + cpu.getProcessorStatus()); + assertEquals(0xff, cpu.stackPeek()); + assertFalse(cpu.getBreakFlag()); + assertEquals(0x0200, cpu.getProgramCounter()); + assertEquals(0xff, cpu.getStackPointer()); + + bus.loadProgram(0xea, + 0xea, + 0xea, + 0x00); + + cpu.step(3); // Three NOP instructions + + assertEquals(0x203, cpu.getProgramCounter()); + + cpu.step(); // Triggers the BRK + + // Was at PC = 0x204, which should now be on the stack + assertEquals(0x02, bus.read(0x1ff)); // PC high byte + assertEquals(0x04, bus.read(0x1fe)); // PC low byte + assertEquals(0x20|Cpu.P_CARRY|Cpu.P_OVERFLOW, + bus.read(0x1fd)); // Processor Status + + // Reset to original contents of PC + assertEquals(0x0200, cpu.getProgramCounter()); + assertEquals(0xfc, cpu.getStackPointer()); + assertEquals(0x20|Cpu.P_CARRY|Cpu.P_OVERFLOW|Cpu.P_BREAK, + cpu.getProcessorStatus()); + assertEquals(0x20|Cpu.P_CARRY|Cpu.P_OVERFLOW, + cpu.stackPeek()); + } + + public void test_BRK_HonorsIrqDisableFlag() { + cpu.setIrqDisableFlag(); + + bus.loadProgram(0xea, + 0xea, + 0xea, + 0x00, + 0xea, + 0xea); + + cpu.step(3); // Three NOP instructions + + assertEquals(0x203, cpu.getProgramCounter()); + + // Triggers the BRK, which should do nothing because + // of the Interrupt Disable flag + cpu.step(); + + // Reset to original contents of PC + assertEquals(0x0204, cpu.getProgramCounter()); + // Empty stack + assertEquals(0xff, cpu.getStackPointer()); + + cpu.step(2); // Two more NOPs + + // Reset to original contents of PC + assertEquals(0x0206, cpu.getProgramCounter()); + // Empty stack + assertEquals(0xff, cpu.getStackPointer()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/loomcom/symon/CpuTest.java b/src/test/java/com/loomcom/symon/CpuTest.java index 763f72f..26113d4 100644 --- a/src/test/java/com/loomcom/symon/CpuTest.java +++ b/src/test/java/com/loomcom/symon/CpuTest.java @@ -9,11 +9,11 @@ import com.loomcom.symon.exceptions.*; * */ public class CpuTest extends TestCase { - + private Cpu cpu; private Bus bus; private Memory mem; - + public CpuTest(String testName) { super(testName); } @@ -21,7 +21,7 @@ public class CpuTest extends TestCase { public static Test suite() { return new TestSuite(CpuTest.class); } - + public void setUp() throws MemoryRangeException { this.cpu = new Cpu(); this.bus = new Bus(0x0000, 0xffff); @@ -35,7 +35,7 @@ public class CpuTest extends TestCase { cpu.reset(); } - + public void testReset() { assertEquals(0, cpu.getAccumulator()); assertEquals(0, cpu.getXRegister()); @@ -49,4 +49,301 @@ public class CpuTest extends TestCase { assertFalse(cpu.getOverflowFlag()); assertFalse(cpu.getNegativeFlag()); } + + public void testStack() { + + cpu.stackPush(0x13); + assertEquals(0x13, cpu.stackPop()); + + cpu.stackPush(0x12); + assertEquals(0x12, cpu.stackPop()); + + for (int i = 0x00; i <= 0xff; i++) { + cpu.stackPush(i); + } + + for (int i = 0xff; i >= 0x00; i--) { + assertEquals(i, cpu.stackPop()); + } + + } + + public void testStackPush() { + assertEquals(0xff, cpu.getStackPointer()); + assertEquals(0xff, bus.read(0x1ff)); + + cpu.stackPush(0x06); + assertEquals(0xfe, cpu.getStackPointer()); + assertEquals(0x06, bus.read(0x1ff)); + + cpu.stackPush(0x05); + assertEquals(0xfd, cpu.getStackPointer()); + assertEquals(0x06, bus.read(0x1ff)); + assertEquals(0x05, bus.read(0x1fe)); + + cpu.stackPush(0x04); + assertEquals(0xfc, cpu.getStackPointer()); + assertEquals(0x06, bus.read(0x1ff)); + assertEquals(0x05, bus.read(0x1fe)); + assertEquals(0x04, bus.read(0x1fd)); + + cpu.stackPush(0x03); + assertEquals(0xfb, cpu.getStackPointer()); + assertEquals(0x06, bus.read(0x1ff)); + assertEquals(0x05, bus.read(0x1fe)); + assertEquals(0x04, bus.read(0x1fd)); + assertEquals(0x03, bus.read(0x1fc)); + + cpu.stackPush(0x02); + assertEquals(0xfa, cpu.getStackPointer()); + assertEquals(0x06, bus.read(0x1ff)); + assertEquals(0x05, bus.read(0x1fe)); + assertEquals(0x04, bus.read(0x1fd)); + assertEquals(0x03, bus.read(0x1fc)); + assertEquals(0x02, bus.read(0x1fb)); + + cpu.stackPush(0x01); + assertEquals(0xf9, cpu.getStackPointer()); + assertEquals(0x06, bus.read(0x1ff)); + assertEquals(0x05, bus.read(0x1fe)); + assertEquals(0x04, bus.read(0x1fd)); + assertEquals(0x03, bus.read(0x1fc)); + assertEquals(0x02, bus.read(0x1fb)); + assertEquals(0x01, bus.read(0x1fa)); + } + + public void testStackPushWrapsAroundToStackTop() { + cpu.setStackPointer(0x01); + + cpu.stackPush(0x01); + assertEquals(0x01, bus.read(0x101)); + assertEquals(0x00, cpu.getStackPointer()); + + cpu.stackPush(0x02); + assertEquals(0x02, bus.read(0x100)); + assertEquals(0xff, cpu.getStackPointer()); + + cpu.stackPush(0x03); + assertEquals(0x03, bus.read(0x1ff)); + assertEquals(0xfe, cpu.getStackPointer()); + } + + + public void testStackPop() { + bus.write(0x1ff, 0x06); + bus.write(0x1fe, 0x05); + bus.write(0x1fd, 0x04); + bus.write(0x1fc, 0x03); + bus.write(0x1fb, 0x02); + bus.write(0x1fa, 0x01); + cpu.setStackPointer(0xf9); + + assertEquals(0x01, cpu.stackPop()); + assertEquals(0xfa, cpu.getStackPointer()); + + assertEquals(0x02, cpu.stackPop()); + assertEquals(0xfb, cpu.getStackPointer()); + + assertEquals(0x03, cpu.stackPop()); + assertEquals(0xfc, cpu.getStackPointer()); + + assertEquals(0x04, cpu.stackPop()); + assertEquals(0xfd, cpu.getStackPointer()); + + assertEquals(0x05, cpu.stackPop()); + assertEquals(0xfe, cpu.getStackPointer()); + + assertEquals(0x06, cpu.stackPop()); + assertEquals(0xff, cpu.getStackPointer()); + } + + public void testStackPopWrapsAroundToStackBottom() { + bus.write(0x1ff, 0x0f); // top of stack + bus.write(0x100, 0xf0); // bottom of stack + bus.write(0x101, 0xf1); + bus.write(0x102, 0xf2); + + cpu.setStackPointer(0xfe); + + assertEquals(0x0f, cpu.stackPop()); + assertEquals(0xff, cpu.getStackPointer()); + + assertEquals(0xf0, cpu.stackPop()); + assertEquals(0x00, cpu.getStackPointer()); + + assertEquals(0xf1, cpu.stackPop()); + assertEquals(0x01, cpu.getStackPointer()); + + assertEquals(0xf2, cpu.stackPop()); + assertEquals(0x02, cpu.getStackPointer()); + } + + public void testStackPeekDoesNotAlterStackPointer() { + int val = 0; + + assertEquals(0xff, cpu.stackPeek()); + assertEquals(0xff, cpu.getStackPointer()); + + cpu.stackPush(0x01); + assertEquals(0x01, cpu.stackPeek()); + assertEquals(0xfe, cpu.getStackPointer()); + + cpu.stackPush(0x02); + assertEquals(0x02, cpu.stackPeek()); + assertEquals(0xfd, cpu.getStackPointer()); + + cpu.stackPush(0x03); + assertEquals(0x03, cpu.stackPeek()); + assertEquals(0xfc, cpu.getStackPointer()); + + cpu.stackPush(0x04); + assertEquals(0x04, cpu.stackPeek()); + assertEquals(0xfb, cpu.getStackPointer()); + assertEquals(0x04, cpu.stackPeek()); + assertEquals(0xfb, cpu.getStackPointer()); + assertEquals(0x04, cpu.stackPeek()); + assertEquals(0xfb, cpu.getStackPointer()); + } + + public void testGetProcessorStatus() { + // By default, no flags are set. Remember, bit 5 + // is always '1'. + assertEquals(0x20, cpu.getProcessorStatus()); + cpu.setCarryFlag(); + assertEquals(0x21, cpu.getProcessorStatus()); + cpu.setZeroFlag(); + assertEquals(0x23, cpu.getProcessorStatus()); + cpu.setIrqDisableFlag(); + assertEquals(0x27, cpu.getProcessorStatus()); + cpu.setDecimalModeFlag(); + assertEquals(0x2f, cpu.getProcessorStatus()); + cpu.setBreakFlag(); + assertEquals(0x3f, cpu.getProcessorStatus()); + cpu.setOverflowFlag(); + assertEquals(0x7f, cpu.getProcessorStatus()); + cpu.setNegativeFlag(); + assertEquals(0xff, cpu.getProcessorStatus()); + + cpu.clearCarryFlag(); + assertEquals(0xfe, cpu.getProcessorStatus()); + cpu.clearZeroFlag(); + assertEquals(0xfc, cpu.getProcessorStatus()); + cpu.clearIrqDisableFlag(); + assertEquals(0xf8, cpu.getProcessorStatus()); + cpu.clearDecimalModeFlag(); + assertEquals(0xf0, cpu.getProcessorStatus()); + cpu.clearBreakFlag(); + assertEquals(0xe0, cpu.getProcessorStatus()); + cpu.clearOverflowFlag(); + assertEquals(0xa0, cpu.getProcessorStatus()); + cpu.clearNegativeFlag(); + assertEquals(0x20, cpu.getProcessorStatus()); + } + + public void testSetProcessorStatus() { + // Default + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertFalse(cpu.getBreakFlag()); + assertFalse(cpu.getOverflowFlag()); + assertFalse(cpu.getNegativeFlag()); + + cpu.setProcessorStatus(0x20|Cpu.P_CARRY); + + assertTrue(cpu.getCarryFlag()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertFalse(cpu.getBreakFlag()); + assertFalse(cpu.getOverflowFlag()); + assertFalse(cpu.getNegativeFlag()); + + cpu.setProcessorStatus(0x20|Cpu.P_CARRY|Cpu.P_NEGATIVE); + + assertTrue(cpu.getCarryFlag()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertFalse(cpu.getBreakFlag()); + assertFalse(cpu.getOverflowFlag()); + assertTrue(cpu.getNegativeFlag()); + + cpu.setProcessorStatus(0x20|Cpu.P_CARRY|Cpu.P_NEGATIVE|Cpu.P_ZERO); + + assertTrue(cpu.getCarryFlag()); + assertTrue(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertFalse(cpu.getBreakFlag()); + assertFalse(cpu.getOverflowFlag()); + assertTrue(cpu.getNegativeFlag()); + + cpu.setProcessorStatus(0x20|Cpu.P_CARRY|Cpu.P_NEGATIVE|Cpu.P_ZERO| + Cpu.P_OVERFLOW); + + assertTrue(cpu.getCarryFlag()); + assertTrue(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertFalse(cpu.getBreakFlag()); + assertTrue(cpu.getOverflowFlag()); + assertTrue(cpu.getNegativeFlag()); + + cpu.setProcessorStatus(0x20|Cpu.P_CARRY|Cpu.P_NEGATIVE|Cpu.P_ZERO| + Cpu.P_OVERFLOW|Cpu.P_BREAK); + + assertTrue(cpu.getCarryFlag()); + assertTrue(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertTrue(cpu.getBreakFlag()); + assertTrue(cpu.getOverflowFlag()); + assertTrue(cpu.getNegativeFlag()); + + + cpu.setProcessorStatus(0x20|Cpu.P_CARRY|Cpu.P_NEGATIVE|Cpu.P_ZERO| + Cpu.P_OVERFLOW|Cpu.P_BREAK|Cpu.P_DECIMAL); + + assertTrue(cpu.getCarryFlag()); + assertTrue(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertTrue(cpu.getDecimalModeFlag()); + assertTrue(cpu.getBreakFlag()); + assertTrue(cpu.getOverflowFlag()); + assertTrue(cpu.getNegativeFlag()); + + cpu.setProcessorStatus(0x20|Cpu.P_CARRY|Cpu.P_NEGATIVE|Cpu.P_ZERO| + Cpu.P_OVERFLOW|Cpu.P_BREAK|Cpu.P_DECIMAL| + Cpu.P_IRQ_DISABLE); + + assertTrue(cpu.getCarryFlag()); + assertTrue(cpu.getZeroFlag()); + assertTrue(cpu.getIrqDisableFlag()); + assertTrue(cpu.getDecimalModeFlag()); + assertTrue(cpu.getBreakFlag()); + assertTrue(cpu.getOverflowFlag()); + assertTrue(cpu.getNegativeFlag()); + + cpu.setProcessorStatus(0x20); + + assertFalse(cpu.getCarryFlag()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertFalse(cpu.getBreakFlag()); + assertFalse(cpu.getOverflowFlag()); + assertFalse(cpu.getNegativeFlag()); + + cpu.setProcessorStatus(0x00); + + assertFalse(cpu.getCarryFlag()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getIrqDisableFlag()); + assertFalse(cpu.getDecimalModeFlag()); + assertFalse(cpu.getBreakFlag()); + assertFalse(cpu.getOverflowFlag()); + assertFalse(cpu.getNegativeFlag()); + } } \ No newline at end of file