diff --git a/src/main/java/com/loomcom/symon/Cpu.java b/src/main/java/com/loomcom/symon/Cpu.java index 1bfa10e..a6489e1 100644 --- a/src/main/java/com/loomcom/symon/Cpu.java +++ b/src/main/java/com/loomcom/symon/Cpu.java @@ -18,7 +18,7 @@ public class Cpu implements InstructionTable { // Bit 5 is always '1' public static final int P_OVERFLOW = 0x40; public static final int P_NEGATIVE = 0x80; - + // NMI vector public static final int IRQ_VECTOR_L = 0xfffa; public static final int IRQ_VECTOR_H = 0xfffb; @@ -28,7 +28,7 @@ public class Cpu implements InstructionTable { // IRQ vector public static final int NMI_VECTOR_L = 0xfffe; public static final int NMI_VECTOR_H = 0xffff; - + /* The Bus */ private Bus bus; @@ -42,6 +42,11 @@ public class Cpu implements InstructionTable { private int sp; // Stack Pointer register, offset into page 1 private int ir; // Instruction register + /* Internal scratch space */ + private int lo = 0, hi = 0; // Used in address calculation + private int j = 0, k = 0; // Used for temporary storage + + /* Operands for the current instruction */ private int[] operands = new int[2]; private int addr; // The address the most recent instruction @@ -83,7 +88,7 @@ public class Cpu implements InstructionTable { sp = 0xff; // Set the PC to the address stored in the reset vector - pc = CpuUtils.address(bus.read(RST_VECTOR_L), bus.read(RST_VECTOR_H)); + pc = address(bus.read(RST_VECTOR_L), bus.read(RST_VECTOR_H)); // Clear instruction register. ir = 0; @@ -106,8 +111,6 @@ public class Cpu implements InstructionTable { * Performs an individual machine cycle. */ public void step() { - int lo, hi; // Used in address calculation - // Store the address from which the IR was read, for debugging addr = pc; @@ -139,7 +142,7 @@ public class Cpu implements InstructionTable { // Set the Interrupt Disabled flag. RTI will clear it. setIrqDisableFlag(); // Load interrupt vector address into PC - pc = CpuUtils.address(bus.read(IRQ_VECTOR_L), bus.read(IRQ_VECTOR_H)); + pc = address(bus.read(IRQ_VECTOR_L), bus.read(IRQ_VECTOR_H)); } break; case 0x01: // n/a @@ -150,9 +153,13 @@ public class Cpu implements InstructionTable { break; case 0x04: // n/a break; - case 0x05: // n/a + case 0x05: // ORA - Logical Inclusive OR - Zero Page + a |= bus.read(operands[0]); + setArithmeticFlags(a); break; - case 0x06: // n/a + case 0x06: // ASL - Arithmetic Shift Left - Zero Page + bus.write(operands[0], asl(bus.read(operands[0]))); + setArithmeticFlags(bus.read(operands[0])); break; case 0x07: // n/a break; @@ -218,7 +225,12 @@ public class Cpu implements InstructionTable { break; case 0x23: // n/a break; - case 0x24: // n/a + case 0x24: // BIT - Bit Test - Zero Page + j = bus.read(operands[0]); + k = a & j; + setZeroFlag(k == 0); + setNegativeFlag((k & 0x80) != 0); + setOverflowFlag((k & 0x40) != 0); break; case 0x25: // n/a break; @@ -284,7 +296,7 @@ public class Cpu implements InstructionTable { setProcessorStatus(stackPop()); lo = stackPop(); hi = stackPop(); - setProgramCounter(CpuUtils.address(lo, hi)); + setProgramCounter(address(lo, hi)); break; case 0x41: // n/a break; @@ -312,7 +324,7 @@ public class Cpu implements InstructionTable { case 0x4b: // n/a break; case 0x4c: // JMP - Jump - Absolute - pc = CpuUtils.address(operands[0], operands[1]); + pc = address(operands[0], operands[1]); break; case 0x4d: // n/a break; @@ -358,7 +370,7 @@ public class Cpu implements InstructionTable { case 0x60: // RTS - Return from Subroutine - Implied lo = stackPop(); hi = stackPop(); - setProgramCounter((CpuUtils.address(lo, hi) + 1) & 0xffff); + setProgramCounter((address(lo, hi) + 1) & 0xffff); break; case 0x61: // n/a break; @@ -753,17 +765,17 @@ public class Cpu implements InstructionTable { setArithmeticFlags(result); return result; } - + /** * Add with Carry (BCD). */ - + public int adcDecimal(int acc, int operand) { int l, h, result; l = (acc & 0x0f) + (operand & 0x0f) + getCarryBit(); if ((l & 0xff) > 9) l += 6; h = (acc >> 4) + (operand >> 4) + (l > 15 ? 1 : 0); - if ((h & 0xff) > 9) h += 6; + if ((h & 0xff) > 9) h += 6; result = (l & 0x0f) | (h << 4); result &= 0xff; setCarryFlag(h > 15); @@ -771,13 +783,13 @@ public class Cpu implements InstructionTable { setNegativeFlag(false); // BCD is never negative setOverflowFlag(false); // BCD never sets overflow flag return result; - } - + } + /** * Common code for Subtract with Carry. Just calls ADC of the * one's complement of the operand. This lets the N, V, C, and Z * flags work out nicely without any additional logic. - * + * * @param acc * @param operand * @return @@ -788,10 +800,10 @@ public class Cpu implements InstructionTable { setArithmeticFlags(result); return result; } - + /** * Subtract with Carry, BCD mode. - * + * * @param acc * @param operand * @return @@ -813,7 +825,7 @@ public class Cpu implements InstructionTable { /** * Compare two values, and set carry, zero, and negative flags * appropriately. - * + * * @param reg * @param operand */ @@ -833,7 +845,28 @@ public class Cpu implements InstructionTable { zeroFlag = (reg == 0); negativeFlag = (reg & 0x80) != 0; } - + + /** + * Shifts the given value left by one bit, and sets the carry + * flag to the high bit of the initial value. + * + * @param m The value to shift left. + * @return the left shifted value (m * 2). + */ + private int asl(int m) { + setCarryFlag((m & 0x80) != 0); + return (m << 1) & 0xff; + } + + /** + * Shifts the given value right by one bit, filling with zeros, + * and sets the carry flag to the low bit of the initial value. + */ + private int lsr(int m) { + setCarryFlag((m & 0x01) != 0); + return (m >>> 1) & 0xff; + } + /** * @return the negative flag */ @@ -876,7 +909,7 @@ public class Cpu implements InstructionTable { public boolean getCarryFlag() { return carryFlag; } - + /** * @return 1 if the carry flag is set, 0 if it is clear. */ @@ -946,7 +979,7 @@ public class Cpu implements InstructionTable { public boolean getIrqDisableFlag() { return irqDisableFlag; } - + /** * @return 1 if the interrupt disable flag is set, 0 if it is clear. */ @@ -1011,7 +1044,7 @@ public class Cpu implements InstructionTable { public boolean getBreakFlag() { return breakFlag; } - + /** * @return 1 if the break flag is set, 0 if it is clear. */ @@ -1046,7 +1079,7 @@ public class Cpu implements InstructionTable { public boolean getOverflowFlag() { return overflowFlag; } - + /** * @return 1 if the overflow flag is set, 0 if it is clear. */ @@ -1191,7 +1224,7 @@ public class Cpu implements InstructionTable { * Returns a string representing the CPU state. */ public String toString() { - String opcode = CpuUtils.opcode(ir, operands[0], operands[1]); + String opcode = opcode(ir, operands[0], operands[1]); StringBuffer sb = new StringBuffer(String.format("$%04X", addr) + " "); sb.append(String.format("%-14s", opcode)); @@ -1241,7 +1274,7 @@ public class Cpu implements InstructionTable { /* * Increment the PC, rolling over if necessary. */ - protected void incrementPC() { + void incrementPC() { if (pc == 0xffff) { pc = 0; } else { @@ -1249,4 +1282,34 @@ public class Cpu implements InstructionTable { } } + /** + * Given two bytes, return an address. + */ + int address(int lowByte, int hiByte) { + return ((hiByte<<8)|lowByte); + } + + /** + * Given an opcode and its operands, return a formatted name. + * + * @param opcode + * @param operands + * @return + */ + String opcode(int opcode, int op1, int op2) { + String opcodeName = Cpu.opcodeNames[opcode]; + if (opcodeName == null) { return "???"; } + + StringBuffer sb = new StringBuffer(opcodeName); + + switch (Cpu.instructionModes[opcode]) { + case ABS: + sb.append(String.format(" $%04X", address(op1, op2))); + break; + case IMM: + sb.append(String.format(" #$%02X", op1)); + } + + return sb.toString(); + } } \ No newline at end of file diff --git a/src/main/java/com/loomcom/symon/CpuUtils.java b/src/main/java/com/loomcom/symon/CpuUtils.java deleted file mode 100644 index 8d44421..0000000 --- a/src/main/java/com/loomcom/symon/CpuUtils.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.loomcom.symon; - -import com.loomcom.symon.InstructionTable.Mode; - -public class CpuUtils { - - /** - * Given two bytes, return an address. - */ - public static int address(int lowByte, int hiByte) { - return ((hiByte<<8)|lowByte); - } - - /** - * Given an opcode and its operands, return a formatted name. - * - * @param opcode - * @param operands - * @return - */ - public static String opcode(int opcode, int op1, int op2) { - String opcodeName = Cpu.opcodeNames[opcode]; - if (opcodeName == null) { return "???"; } - - StringBuffer sb = new StringBuffer(opcodeName); - - switch (Cpu.instructionModes[opcode]) { - case ABS: - sb.append(String.format(" $%04X", address(op1, op2))); - break; - case IMM: - sb.append(String.format(" #$%02X", op1)); - } - - return sb.toString(); - } -} diff --git a/src/test/java/com/loomcom/symon/CpuImmediateModeTest.java b/src/test/java/com/loomcom/symon/CpuImmediateModeTest.java index f411e6c..2f9f1ce 100644 --- a/src/test/java/com/loomcom/symon/CpuImmediateModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuImmediateModeTest.java @@ -30,7 +30,7 @@ public class CpuImmediateModeTest extends TestCase { assertEquals(0xff, cpu.getStackPointer()); assertEquals(0x20, cpu.getProcessorStatus()); } - + /* * The following opcodes are tested for correctness in this file: * @@ -39,19 +39,19 @@ public class CpuImmediateModeTest extends TestCase { * EOR - $49 * ADC - $69 * LDY - $a0 - * + * * LDX - $a2 * LDA - $a9 * CPY - $c0 * CMP - $c9 * CPX - $e0 - * + * * SBC - $e9 */ /* ORA Immediate Mode Tests - 0x09 */ - public void test_ORA_SetsAccumulator() { + public void test_ORA() { bus.loadProgram(0x09, 0x00, // ORA #$00 0x09, 0x11, // ORA #$11 0x09, 0x22, // ORA #$22 @@ -722,22 +722,22 @@ public class CpuImmediateModeTest extends TestCase { bus.loadProgram(0x18, // CLC 0xa9, 0x05, // LDA #$00 0xe9, 0x01); // SBC #$01 - + cpu.step(3); assertEquals(0x03, cpu.getAccumulator()); - + cpu.reset(); // Subtrace with Carry Flag cleared bus.loadProgram(0x18, // CLC 0xa9, 0x00, // LDA #$00 0xe9, 0x01); // SBC #$01 - + cpu.step(3); assertEquals(0xfe, cpu.getAccumulator()); - + cpu.reset(); - + // Subtract with Carry Flag set bus.loadProgram(0x38, // SEC 0xa9, 0x05, // LDA #$00 @@ -745,9 +745,9 @@ public class CpuImmediateModeTest extends TestCase { cpu.step(3); assertEquals(0x04, cpu.getAccumulator()); assertTrue(cpu.getCarryFlag()); - + cpu.reset(); - + // Subtract with Carry Flag set bus.loadProgram(0x38, // SEC 0xa9, 0x00, // LDA #$00 @@ -757,7 +757,7 @@ public class CpuImmediateModeTest extends TestCase { assertFalse(cpu.getCarryFlag()); } - + public void test_SBC_DecimalMode() { bus.loadProgram(0xf8, 0xa9, 0x00, @@ -771,7 +771,7 @@ public class CpuImmediateModeTest extends TestCase { assertTrue(cpu.getDecimalModeFlag()); cpu.reset(); - + bus.loadProgram(0xf8, 0xa9, 0x99, 0xe9, 0x01); @@ -784,7 +784,7 @@ public class CpuImmediateModeTest extends TestCase { assertTrue(cpu.getDecimalModeFlag()); cpu.reset(); - + bus.loadProgram(0xf8, 0xa9, 0x50, 0xe9, 0x01); @@ -796,9 +796,9 @@ public class CpuImmediateModeTest extends TestCase { assertFalse(cpu.getZeroFlag()); assertTrue(cpu.getDecimalModeFlag()); - + cpu.reset(); - + bus.loadProgram(0xf8, // SED 0xa9, 0x02, // LDA #$02 0xe9, 0x01); // SBC #$01 @@ -809,9 +809,9 @@ public class CpuImmediateModeTest extends TestCase { assertFalse(cpu.getOverflowFlag()); assertTrue(cpu.getZeroFlag()); assertTrue(cpu.getDecimalModeFlag()); - + cpu.reset(); - + bus.loadProgram(0xf8, // SED 0xa9, 0x10, // LDA #$10 0xe9, 0x11); // SBC #$11 @@ -822,7 +822,7 @@ public class CpuImmediateModeTest extends TestCase { assertFalse(cpu.getOverflowFlag()); assertFalse(cpu.getZeroFlag()); assertTrue(cpu.getDecimalModeFlag()); - + cpu.reset(); bus.loadProgram(0x38, // SEC @@ -838,7 +838,7 @@ public class CpuImmediateModeTest extends TestCase { assertTrue(cpu.getDecimalModeFlag()); cpu.reset(); - + bus.loadProgram(0x38, // SEC 0xf8, // SED 0xa9, 0x00, // LDA #$00 diff --git a/src/test/java/com/loomcom/symon/CpuTest.java b/src/test/java/com/loomcom/symon/CpuTest.java index 26113d4..9d818df 100644 --- a/src/test/java/com/loomcom/symon/CpuTest.java +++ b/src/test/java/com/loomcom/symon/CpuTest.java @@ -346,4 +346,13 @@ public class CpuTest extends TestCase { assertFalse(cpu.getOverflowFlag()); assertFalse(cpu.getNegativeFlag()); } + + public void testAddress() { + assertEquals(0xf1ea, cpu.address(0xea, 0xf1)); + assertEquals(0x00ea, cpu.address(0xea, 0x00)); + assertEquals(0xf100, cpu.address(0x00, 0xf1)); + assertEquals(0x1234, cpu.address(0x34, 0x12)); + assertEquals(0xffff, cpu.address(0xff, 0xff)); + } + } \ No newline at end of file diff --git a/src/test/java/com/loomcom/symon/CpuUtilsTest.java b/src/test/java/com/loomcom/symon/CpuUtilsTest.java deleted file mode 100644 index 1e13aa2..0000000 --- a/src/test/java/com/loomcom/symon/CpuUtilsTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.loomcom.symon; - -import junit.framework.TestCase; - -public class CpuUtilsTest extends TestCase { - - public void testAddress() { - assertEquals(0xf1ea, CpuUtils.address(0xea, 0xf1)); - assertEquals(0x00ea, CpuUtils.address(0xea, 0x00)); - assertEquals(0xf100, CpuUtils.address(0x00, 0xf1)); - assertEquals(0x1234, CpuUtils.address(0x34, 0x12)); - assertEquals(0xffff, CpuUtils.address(0xff, 0xff)); - } - -} diff --git a/src/test/java/com/loomcom/symon/CpuZeroPageModeTest.java b/src/test/java/com/loomcom/symon/CpuZeroPageModeTest.java index f0835e1..43b2ed1 100644 --- a/src/test/java/com/loomcom/symon/CpuZeroPageModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuZeroPageModeTest.java @@ -34,41 +34,241 @@ public class CpuZeroPageModeTest extends TestCase { /* * The following opcodes are tested for correctness in this file: * - * ADC - $65 - * AND - $25 + * ORA - $05 * ASL - $06 * BIT - $24 - * CMP - $c5 - * - * CPX - $e4 - * CPY - $c4 - * DEC - $c6 - * EOR - $45 - * INC - $e6 - * - * LDA - $a5 - * LDX - $a6 - * LDY - $a4 - * LSR - $46 - * ORA - $05 - * + * AND - $25 * ROL - $26 + * + * EOR - $45 + * LSR - $46 + * ADC - $65 * ROR - $66 - * SBC - $e5 + * STY - $84 + * * STA - $85 * STX - $86 - * - * STY - $84 - * + * LDY - $a4 + * LDA - $a5 + * LDX - $a6 + * + * CPY - $c4 + * CMP - $c5 + * DEC - $c6 + * CPX - $e4 + * SBC - $e5 + * + * INC - $e6 */ - - /* ADC - Add with Carry - $88 */ - public void test_ADC() { - + + /* ORA - Logical Inclusive OR - $05 */ + public void test_ORA() { + // Set some initial values in zero page. + bus.write(0x0000, 0x00); + bus.write(0x0002, 0x11); + bus.write(0x0004, 0x22); + bus.write(0x0008, 0x44); + bus.write(0x0010, 0x88); + + bus.loadProgram(0x05, 0x00, // ORA $00 + 0x05, 0x02, // ORA $02 + 0x05, 0x04, // ORA $04 + 0x05, 0x08, // ORA $08 + 0x05, 0x10); // ORA $10 + cpu.step(); + assertEquals(0x00, cpu.getAccumulator()); + assertTrue(cpu.getZeroFlag()); + assertFalse(cpu.getNegativeFlag()); + + cpu.step(); + assertEquals(0x11, cpu.getAccumulator()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getNegativeFlag()); + + cpu.step(); + assertEquals(0x33, cpu.getAccumulator()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getNegativeFlag()); + + cpu.step(); + assertEquals(0x77, cpu.getAccumulator()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getNegativeFlag()); + + cpu.step(); + assertEquals(0xff, cpu.getAccumulator()); + assertFalse(cpu.getZeroFlag()); + assertTrue(cpu.getNegativeFlag()); } - + + /* ASL - Arithmetic Shift Left - $06 */ + public void test_ASL() { + bus.write(0x0000, 0x00); + bus.write(0x0001, 0x01); + bus.write(0x0002, 0x02); + bus.write(0x0003, 0x44); + bus.write(0x0004, 0x80); + + bus.loadProgram(0x06, 0x00, + 0x06, 0x01, + 0x06, 0x02, + 0x06, 0x03, + 0x06, 0x04); + + cpu.step(); + assertEquals(0x00, bus.read(0x0000)); + assertTrue(cpu.getZeroFlag()); + assertFalse(cpu.getNegativeFlag()); + assertFalse(cpu.getCarryFlag()); + + cpu.step(); + assertEquals(0x02, bus.read(0x0001)); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getNegativeFlag()); + assertFalse(cpu.getCarryFlag()); + + cpu.step(); + assertEquals(0x04, bus.read(0x0002)); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getNegativeFlag()); + assertFalse(cpu.getCarryFlag()); + + cpu.step(); + assertEquals(0x88, bus.read(0x0003)); + assertFalse(cpu.getZeroFlag()); + assertTrue(cpu.getNegativeFlag()); + assertFalse(cpu.getCarryFlag()); + + cpu.step(); + assertEquals(0x00, bus.read(0x0004)); + assertTrue(cpu.getZeroFlag()); + assertFalse(cpu.getNegativeFlag()); + assertTrue(cpu.getCarryFlag()); + } + + /* BIT - Bit Test - $24 */ + public void test_BIT() { + bus.write(0x0000, 0xc0); + + bus.loadProgram(0xa9, 0x01, // LDA #$01 + 0x24, 0x00, // BIT $00 + + 0xa9, 0x0f, // LDA #$0f + 0x24, 0x00, // BIT $00 + + 0xa9, 0x40, // LDA #$40 + 0x24, 0x00, // BIT $00 + + 0xa9, 0x80, // LDA #$80 + 0x24, 0x00, // BIT $00 + + 0xa9, 0xc0, // LDA #$c0 + 0x24, 0x00, // BIT $00 + + 0xa9, 0xff, // LDA #$ff + 0x24, 0x00); // BIT $00 + + cpu.step(2); + assertTrue(cpu.getZeroFlag()); + assertFalse(cpu.getNegativeFlag()); + assertFalse(cpu.getOverflowFlag()); + + cpu.step(2); + assertTrue(cpu.getZeroFlag()); + assertFalse(cpu.getNegativeFlag()); + assertFalse(cpu.getOverflowFlag()); + + cpu.step(2); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getNegativeFlag()); + assertTrue(cpu.getOverflowFlag()); + + cpu.step(2); + assertFalse(cpu.getZeroFlag()); + assertTrue(cpu.getNegativeFlag()); + assertFalse(cpu.getOverflowFlag()); + + cpu.step(2); + assertFalse(cpu.getZeroFlag()); + assertTrue(cpu.getNegativeFlag()); + assertTrue(cpu.getOverflowFlag()); + + cpu.step(2); + assertFalse(cpu.getZeroFlag()); + assertTrue(cpu.getNegativeFlag()); + assertTrue(cpu.getOverflowFlag()); + } + /* AND - Logical AND - $25 */ public void test_AND() { - } + + /* ROL - Rotate Left - $26 */ + public void test_ROL() { + } + + /* EOR - Exclusive OR - $45 */ + public void test_EOR() { + } + + /* LSR - Logical Shift Right - $46 */ + public void test_LSR() { + } + + /* ADC - Add with Carry - $65 */ + public void test_ADC() { + } + + /* ROR - Rotate Right - $66 */ + public void test_ROR() { + } + + /* STY - Store Y Register - $84 */ + public void test_STY() { + } + + /* STA - Store Accumulator - $85 */ + public void test_STA() { + } + + /* STX - Store X Register - $86 */ + public void test_STX() { + } + + /* LDY - Load Y Register - $a4 */ + public void test_LDY() { + } + + /* LDA - Load Accumulator - $a5 */ + public void test_LDA() { + } + + /* LDX - Load X Register - $a6 */ + public void test_LDX() { + } + + /* CPY - Compare Y Register - $c4 */ + public void test_CPY() { + } + + /* CMP - Compare Accumulator - $c5 */ + public void test_CMP() { + } + + /* DEC - Decrement Memory Location - $c6 */ + public void test_DEC() { + } + + /* CPX - Compare X Register - $e4 */ + public void test_CPX() { + } + + /* SBC - Subtract with Carry - $e5 */ + public void test_SBC() { + } + + /* INC - Increment Memory Location - $e6 */ + public void test_INC() { + } + }