diff --git a/src/main/java/com/loomcom/symon/Cpu.java b/src/main/java/com/loomcom/symon/Cpu.java index 755e44e..5933b61 100644 --- a/src/main/java/com/loomcom/symon/Cpu.java +++ b/src/main/java/com/loomcom/symon/Cpu.java @@ -18,7 +18,17 @@ 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; + // Reset vector + public static final int RST_VECTOR_L = 0xfffc; + public static final int RST_VECTOR_H = 0xfffd; + // IRQ vector + public static final int NMI_VECTOR_L = 0xfffe; + public static final int NMI_VECTOR_H = 0xffff; + /* The Bus */ private Bus bus; @@ -72,8 +82,8 @@ public class Cpu implements InstructionTable { // Registers sp = 0xff; - // Set the PC to the address stored in 0xfffc - pc = CpuUtils.address(bus.read(0xfffc), bus.read(0xfffd)); + // Set the PC to the address stored in the reset vector + pc = CpuUtils.address(bus.read(RST_VECTOR_L), bus.read(RST_VECTOR_H)); // Clear instruction register. ir = 0; @@ -104,18 +114,15 @@ public class Cpu implements InstructionTable { // Fetch memory location for this instruction. ir = bus.read(pc); - // TODO: The way we increment the PC may need - // to change when interrupts are implemented - // Increment PC - incProgramCounter(); + incrementPC(); // Decode the instruction and operands int size = Cpu.instructionSizes[ir]; for (int i = 0; i < size-1; i++) { operands[i] = bus.read(pc); // Increment PC after reading - incProgramCounter(); + incrementPC(); } // Execute @@ -123,12 +130,16 @@ public class Cpu implements InstructionTable { 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)); + // Set the break flag before pushing. setBreakFlag(); + // Push program counter + 2 onto the stack + stackPush((pc+2 >> 8) & 0xff); // PC high byte + stackPush(pc+2 & 0xff); // PC low byte + stackPush(getProcessorStatus()); + // 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)); } break; case 0x01: // n/a @@ -368,8 +379,14 @@ public class Cpu implements InstructionTable { setArithmeticFlags(a); break; case 0x69: // ADC - Add with Carry - Immediate - a = adc(a, operands[0]); - setArithmeticFlags(a); + if (decimalModeFlag) { + a = adcDecimal(a, operands[0]); + clearNegativeFlag(); // All results of BCD math are positive + setZeroFlag(a == 0); + } else { + a = adc(a, operands[0]); + setArithmeticFlags(a); + } break; case 0x6a: // n/a break; @@ -665,8 +682,14 @@ public class Cpu implements InstructionTable { setArithmeticFlags(x); break; case 0xe9: // SBC - Subtract with Carry (Borrow) - Immediate - a = sbc(a, operands[0]); - setArithmeticFlags(a); + if (decimalModeFlag) { + a = sbcDecimal(a, operands[0]); + clearNegativeFlag(); + setZeroFlag(a == 0); + } else { + a = sbc(a, operands[0]); + setArithmeticFlags(a); + } break; case 0xea: // NOP break; @@ -719,26 +742,73 @@ public class Cpu implements InstructionTable { /** * Add with Carry, used by all addressing mode implementations of ADC. - * As a side effect, this will set the + * As a side effect, this will set the overflow and carry flags as + * needed. * * @param acc The current value of the accumulator * @param operand The operand * @return */ public int adc(int acc, int operand) { - int result = operand + acc + (carryFlag ? 1 : 0); - int carry = (operand & 0x7f) + (acc & 0x7f) + (carryFlag ? 1 : 0); - setCarryFlag(result > 0xff); + int result = (operand & 0xff) + (acc & 0xff) + getCarryBit(); + int carry6 = (operand & 0x7f) + (acc & 0x7f) + getCarryBit(); + setCarryFlag((result & 0x100) != 0); + setOverflowFlag(carryFlag ^ ((carry6 & 0x80) != 0)); result = result & 0xff; - setOverflowFlag(carryFlag ^ ((carry & 0x80) != 0)); return result; } - + + /** + * Add with Carry (BCD). + */ + public int adcDecimal(int acc, int operand) { + // Thank you to Doug Jones for this beautiful BCD algorithm. From: + // http://www.cs.uiowa.edu/~jones/bcd/bcd.html + acc += 0x066; + int t2 = acc + operand; + int t3 = acc ^ operand; + int t4 = t2 ^ t3; + int t5 = ~t4 & 0x110; + int t6 = (t5 >> 2) | (t5 >> 3); + int result = t2 - t6; + if (result > 0xff) { + result &= 0xff; + setCarryFlag(); + } + return result; + } + + /** + * Common code for Subtract with Carry. Just calls ADC of the + * one's complement of the opeerand. This lets the N, V, C, and Z + * flags work out nicely without any additional logic. + * + * @param acc + * @param operand + * @return + */ public int sbc(int acc, int operand) { - // Equivalent to ADC of the 2's complement of the operand, minus the /carry - return adc(acc, (--operand ^ 0xff)); + return adc(acc, ~operand); + } + + /** + * Subtract with Carry, BCD mode. + * + * @param acc + * @param operand + * @return + */ + public int sbcDecimal(int acc, int operand) { + return adcDecimal(acc, tensComplement(operand)); } + /** + * Compare two values, and set carry, zero, and negative flags + * appropriately. + * + * @param reg + * @param operand + */ public void cmp(int reg, int operand) { setCarryFlag(reg >= operand); setZeroFlag(reg == operand); @@ -755,6 +825,7 @@ public class Cpu implements InstructionTable { zeroFlag = (reg == 0); negativeFlag = (reg & 0x80) != 0; } + /** * @return the negative flag */ @@ -762,10 +833,18 @@ public class Cpu implements InstructionTable { return negativeFlag; } + /** + * @return 1 if the negative flag is set, 0 if it is clear. + */ + public int getNegativeBit() { + return (negativeFlag ? 1 : 0); + } + /** * @param register the register value to test for negativity */ public void setNegativeFlag(int register) { + negativeFlag = (register < 0); } /** @@ -789,6 +868,13 @@ public class Cpu implements InstructionTable { public boolean getCarryFlag() { return carryFlag; } + + /** + * @return 1 if the carry flag is set, 0 if it is clear. + */ + public int getCarryBit() { + return (carryFlag ? 1 : 0); + } /** * @param carryFlag the carry flag to set @@ -818,6 +904,13 @@ public class Cpu implements InstructionTable { return zeroFlag; } + /** + * @return 1 if the zero flag is set, 0 if it is clear. + */ + public int getZeroBit() { + return (zeroFlag ? 1 : 0); + } + /** * @param zeroFlag the zero flag to set */ @@ -845,6 +938,13 @@ public class Cpu implements InstructionTable { public boolean getIrqDisableFlag() { return irqDisableFlag; } + + /** + * @return 1 if the interrupt disable flag is set, 0 if it is clear. + */ + public int getIrqDisableBit() { + return (irqDisableFlag ? 1 : 0); + } /** * @param irqDisableFlag the irq disable flag to set @@ -869,6 +969,13 @@ public class Cpu implements InstructionTable { return decimalModeFlag; } + /** + * @return 1 if the decimal mode flag is set, 0 if it is clear. + */ + public int getDecimalModeBit() { + return (decimalModeFlag ? 1 : 0); + } + /** * @param decimalModeFlag the decimal mode flag to set */ @@ -896,6 +1003,13 @@ public class Cpu implements InstructionTable { public boolean getBreakFlag() { return breakFlag; } + + /** + * @return 1 if the break flag is set, 0 if it is clear. + */ + public int getBreakBit() { + return (carryFlag ? 1 : 0); + } /** * @param breakFlag the break flag to set @@ -924,6 +1038,13 @@ public class Cpu implements InstructionTable { public boolean getOverflowFlag() { return overflowFlag; } + + /** + * @return 1 if the overflow flag is set, 0 if it is clear. + */ + public int getOverflowBit() { + return (overflowFlag ? 1 : 0); + } /** * @param overflowFlag the overflow flag to set @@ -1045,14 +1166,16 @@ public class Cpu implements InstructionTable { * @return A string representing the current status register state. */ public String statusRegisterString() { - StringBuffer sb = new StringBuffer(); - sb.append("(N:" + (getNegativeFlag() ? '1' : '0') + ", "); - sb.append("V:" + (getOverflowFlag() ? '1' : '0') + ", "); - sb.append("B:" + (getBreakFlag() ? '1' : '0') + ", "); - sb.append("D:" + (getDecimalModeFlag() ? '1' : '0') + ", "); - sb.append("I:" + (getIrqDisableFlag() ? '1' : '0') + ", "); - sb.append("Z:" + (getZeroFlag() ? '1' : '0') + ", "); - sb.append("C:" + (getCarryFlag() ? '1' : '0') + ")"); + StringBuffer sb = new StringBuffer("["); + sb.append(getNegativeFlag() ? 'N' : '.'); // Bit 7 + sb.append(getOverflowFlag() ? 'V' : '.'); // Bit 6 + sb.append("-"); // Bit 5 (always 1) + sb.append(getBreakFlag() ? 'B' : '.'); // Bit 4 + sb.append(getDecimalModeFlag() ? 'D' : '.'); // Bit 3 + sb.append(getIrqDisableFlag() ? 'I' : '.'); // Bit 2 + sb.append(getZeroFlag() ? 'Z' : '.'); // Bit 1 + sb.append(getCarryFlag() ? 'C' : '.'); // Bit 0 + sb.append("]"); return sb.toString(); } @@ -1110,7 +1233,7 @@ public class Cpu implements InstructionTable { /* * Increment the PC, rolling over if necessary. */ - protected void incProgramCounter() { + protected void incrementPC() { if (pc == 0xffff) { pc = 0; } else { @@ -1118,4 +1241,19 @@ public class Cpu implements InstructionTable { } } + /* + * Calculate the 10's Complement of a byte. Used in BCD mode SBC. + */ + private int tensComplement(int val) { + // Thank you to Doug Jones for this truly beautiful and obscure algorithm. + // http://www.cs.uiowa.edu/~jones/bcd/bcd.html + int t1 = 0xfff - val; + int t2 = -val; + int t3 = t1 ^ 0x001; + int t4 = t2 ^ t3; + int t5 = ~t4 & 0x110; + int t6 = (t5 >> 2) | (t5 >> 3); + return t2 - t6; + } + } \ No newline at end of file diff --git a/src/test/java/com/loomcom/symon/CpuImmediateModeTest.java b/src/test/java/com/loomcom/symon/CpuImmediateModeTest.java index 2247099..513e2dc 100644 --- a/src/test/java/com/loomcom/symon/CpuImmediateModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuImmediateModeTest.java @@ -252,6 +252,83 @@ public class CpuImmediateModeTest extends TestCase { assertFalse(cpu.getCarryFlag()); } + public void test_ADC_DecimalMode() { + bus.loadProgram(0xf8, // SED + 0xa9, 0x01, // LDA #$01 + 0x69, 0x01); // ADC #$01 + cpu.step(3); + assertEquals(0x02, cpu.getAccumulator()); + assertFalse(cpu.getNegativeFlag()); + assertFalse(cpu.getOverflowFlag()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getCarryFlag()); + + cpu.reset(); + bus.loadProgram(0xf8, // SED + 0xa9, 0x49, // LDA #$49 + 0x69, 0x01); // ADC #$01 + cpu.step(3); + assertEquals(0x50, cpu.getAccumulator()); + assertFalse(cpu.getNegativeFlag()); + assertFalse(cpu.getOverflowFlag()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getCarryFlag()); + + cpu.reset(); + bus.loadProgram(0xf8, // SED + 0xa9, 0x50, // LDA #$50 + 0x69, 0x01); // ADC #$01 + cpu.step(3); + assertEquals(0x51, cpu.getAccumulator()); + assertFalse(cpu.getNegativeFlag()); + assertFalse(cpu.getOverflowFlag()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getCarryFlag()); + + cpu.reset(); + bus.loadProgram(0xf8, // SED + 0xa9, 0x99, // LDA #$99 + 0x69, 0x01); // ADC #$01 + cpu.step(3); + assertEquals(0x00, cpu.getAccumulator()); + assertFalse(cpu.getNegativeFlag()); + assertFalse(cpu.getOverflowFlag()); + assertTrue(cpu.getZeroFlag()); + assertTrue(cpu.getCarryFlag()); + + cpu.reset(); + bus.loadProgram(0xf8, // SED + 0xa9, 0x00, // LDA #$00 + 0x69, 0x99); // ADC #$01 + cpu.step(3); + assertEquals(0x99, cpu.getAccumulator()); + assertFalse(cpu.getNegativeFlag()); + assertFalse(cpu.getOverflowFlag()); + assertFalse(cpu.getZeroFlag()); + assertFalse(cpu.getCarryFlag()); + + cpu.reset(); + bus.loadProgram(0xf8, // SED + 0xa9, 0x49, // LDA #$49 + 0x69, 0x99); // ADC #$99 + cpu.step(3); + assertEquals(0x48, cpu.getAccumulator()); + assertFalse(cpu.getNegativeFlag()); + assertFalse(cpu.getOverflowFlag()); + assertFalse(cpu.getZeroFlag()); + assertTrue(cpu.getCarryFlag()); + + cpu.reset(); + bus.loadProgram(0xf8, // SED + 0xa9, 0x50, // LDA #$59 + 0x69, 0x99); // ADC #$99 + cpu.step(3); + assertEquals(0x49, cpu.getAccumulator()); + assertFalse(cpu.getNegativeFlag()); + assertFalse(cpu.getOverflowFlag()); + assertFalse(cpu.getZeroFlag()); + assertTrue(cpu.getCarryFlag()); + } /* LDY Immediate Mode Tests - 0xa0 */ @@ -616,11 +693,43 @@ public class CpuImmediateModeTest extends TestCase { } public void test_SBC_IncludesNotOfCarry() { + // Subtrace with Carry Flag cleared + 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 + 0xe9, 0x01); // SBC #$01 + 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 0xe9, 0x01); // SBC #$01 cpu.step(3); assertEquals(0xff, cpu.getAccumulator()); + assertFalse(cpu.getCarryFlag()); + } } \ No newline at end of file diff --git a/src/test/java/com/loomcom/symon/CpuImpliedModeTest.java b/src/test/java/com/loomcom/symon/CpuImpliedModeTest.java index f749703..e999e35 100644 --- a/src/test/java/com/loomcom/symon/CpuImpliedModeTest.java +++ b/src/test/java/com/loomcom/symon/CpuImpliedModeTest.java @@ -78,10 +78,16 @@ public class CpuImpliedModeTest extends TestCase { assertEquals(0x0200, cpu.getProgramCounter()); assertEquals(0xff, cpu.getStackPointer()); + // Set the IRQ vector + bus.write(Cpu.IRQ_VECTOR_H, 0x12); + bus.write(Cpu.IRQ_VECTOR_L, 0x34); + bus.loadProgram(0xea, 0xea, 0xea, - 0x00); + 0x00, + 0xea, + 0xea); cpu.step(3); // Three NOP instructions @@ -89,19 +95,20 @@ public class CpuImpliedModeTest extends TestCase { cpu.step(); // Triggers the BRK - // Was at PC = 0x204, which should now be on the stack + // Was at PC = 0x204. PC+2 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(0x06, bus.read(0x1fe)); // PC low byte assertEquals(0x20|Cpu.P_CARRY|Cpu.P_OVERFLOW|Cpu.P_BREAK, + bus.read(0x1fd)); // Processor Status, with B set + + // Interrupt vector held 0x1234, so we should be there. + assertEquals(0x1234, cpu.getProgramCounter()); + assertEquals(0xfc, cpu.getStackPointer()); + + // B and I flags should have been set on P + assertEquals(0x20|Cpu.P_CARRY|Cpu.P_OVERFLOW|Cpu.P_BREAK| + Cpu.P_IRQ_DISABLE, cpu.getProcessorStatus()); - assertEquals(0x20|Cpu.P_CARRY|Cpu.P_OVERFLOW, - cpu.stackPeek()); } public void test_BRK_HonorsIrqDisableFlag() {