symon/src/test/java/com/loomcom/symon/CpuTest.java

674 lines
23 KiB
Java

package com.loomcom.symon;
import com.loomcom.symon.util.Utils;
import junit.framework.*;
import com.loomcom.symon.devices.*;
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);
}
public static Test suite() {
return new TestSuite(CpuTest.class);
}
public void setUp() throws MemoryRangeException, MemoryAccessException {
this.cpu = new Cpu();
this.bus = new Bus(0x0000, 0xffff);
this.mem = new Memory(0x0000, 0xffff);
bus.addCpu(cpu);
bus.addDevice(mem);
// All test programs start at 0x0200;
bus.write(0xfffc, 0x00);
bus.write(0xfffd, 0x02);
cpu.reset();
}
public void testReset() {
assertEquals(0, cpu.getAccumulator());
assertEquals(0, cpu.getXRegister());
assertEquals(0, cpu.getYRegister());
assertEquals(0x0200, cpu.getProgramCounter());
assertFalse(cpu.getCarryFlag());
assertFalse(cpu.getZeroFlag());
assertTrue(cpu.getIrqDisableFlag());
assertFalse(cpu.getDecimalModeFlag());
assertFalse(cpu.getBreakFlag());
assertFalse(cpu.getOverflowFlag());
assertFalse(cpu.getNegativeFlag());
}
public void testStack() throws MemoryAccessException {
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() throws MemoryAccessException {
assertEquals(0xff, cpu.getStackPointer());
assertEquals(0x00, bus.read(0x1ff, true));
cpu.stackPush(0x06);
assertEquals(0xfe, cpu.getStackPointer());
assertEquals(0x06, bus.read(0x1ff, true));
cpu.stackPush(0x05);
assertEquals(0xfd, cpu.getStackPointer());
assertEquals(0x06, bus.read(0x1ff, true));
assertEquals(0x05, bus.read(0x1fe, true));
cpu.stackPush(0x04);
assertEquals(0xfc, cpu.getStackPointer());
assertEquals(0x06, bus.read(0x1ff, true));
assertEquals(0x05, bus.read(0x1fe, true));
assertEquals(0x04, bus.read(0x1fd, true));
cpu.stackPush(0x03);
assertEquals(0xfb, cpu.getStackPointer());
assertEquals(0x06, bus.read(0x1ff, true));
assertEquals(0x05, bus.read(0x1fe, true));
assertEquals(0x04, bus.read(0x1fd, true));
assertEquals(0x03, bus.read(0x1fc, true));
cpu.stackPush(0x02);
assertEquals(0xfa, cpu.getStackPointer());
assertEquals(0x06, bus.read(0x1ff, true));
assertEquals(0x05, bus.read(0x1fe, true));
assertEquals(0x04, bus.read(0x1fd, true));
assertEquals(0x03, bus.read(0x1fc, true));
assertEquals(0x02, bus.read(0x1fb, true));
cpu.stackPush(0x01);
assertEquals(0xf9, cpu.getStackPointer());
assertEquals(0x06, bus.read(0x1ff, true));
assertEquals(0x05, bus.read(0x1fe, true));
assertEquals(0x04, bus.read(0x1fd, true));
assertEquals(0x03, bus.read(0x1fc, true));
assertEquals(0x02, bus.read(0x1fb, true));
assertEquals(0x01, bus.read(0x1fa, true));
}
public void testStackPushWrapsAroundToStackTop() throws MemoryAccessException {
cpu.setStackPointer(0x01);
cpu.stackPush(0x01);
assertEquals(0x01, bus.read(0x101, true));
assertEquals(0x00, cpu.getStackPointer());
cpu.stackPush(0x02);
assertEquals(0x02, bus.read(0x100, true));
assertEquals(0xff, cpu.getStackPointer());
cpu.stackPush(0x03);
assertEquals(0x03, bus.read(0x1ff, true));
assertEquals(0xfe, cpu.getStackPointer());
}
public void testStackPop() throws MemoryAccessException {
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() throws MemoryAccessException {
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() throws MemoryAccessException {
assertEquals(0x00, 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, only "interrupt disable" is set. Remember, bit 5
// is always '1'.
assertEquals(0x24, cpu.getProcessorStatus());
cpu.setCarryFlag();
assertEquals(0x25, cpu.getProcessorStatus());
cpu.setZeroFlag();
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());
cpu.setIrqDisableFlag();
assertEquals(0x24, cpu.getProcessorStatus());
}
public void testSetProcessorStatus() {
// Default
assertFalse(cpu.getZeroFlag());
assertFalse(cpu.getZeroFlag());
assertTrue(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());
}
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 testIrqDoesNotSetBRK() 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
assertFalse(cpu.getBreakFlag());
}
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 testNmiDoesNotSetBRK() 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();
assertFalse(cpu.getBreakFlag());
}
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() {
assertEquals(0xf1ea, Utils.address(0xea, 0xf1));
assertEquals(0x00ea, Utils.address(0xea, 0x00));
assertEquals(0xf100, Utils.address(0x00, 0xf1));
assertEquals(0x1234, Utils.address(0x34, 0x12));
assertEquals(0xffff, Utils.address(0xff, 0xff));
}
public void testZpxAddress() {
cpu.setXRegister(0x00);
assertEquals(0x10, cpu.zpxAddress(0x10));
cpu.setXRegister(0x10);
assertEquals(0x20, cpu.zpxAddress(0x10));
cpu.setXRegister(0x25);
assertEquals(0x35, cpu.zpxAddress(0x10));
cpu.setXRegister(0xf5);
assertEquals(0x05, cpu.zpxAddress(0x10));
cpu.setXRegister(0x00);
assertEquals(0x80, cpu.zpxAddress(0x80));
cpu.setXRegister(0x10);
assertEquals(0x90, cpu.zpxAddress(0x80));
cpu.setXRegister(0x25);
assertEquals(0xa5, cpu.zpxAddress(0x80));
cpu.setXRegister(0x95);
assertEquals(0x15, cpu.zpxAddress(0x80));
}
public void testZpyAddress() {
cpu.setYRegister(0x00);
assertEquals(0x10, cpu.zpyAddress(0x10));
cpu.setYRegister(0x10);
assertEquals(0x20, cpu.zpyAddress(0x10));
cpu.setYRegister(0x25);
assertEquals(0x35, cpu.zpyAddress(0x10));
cpu.setYRegister(0xf5);
assertEquals(0x05, cpu.zpyAddress(0x10));
cpu.setYRegister(0x00);
assertEquals(0x80, cpu.zpyAddress(0x80));
cpu.setYRegister(0x10);
assertEquals(0x90, cpu.zpyAddress(0x80));
cpu.setYRegister(0x25);
assertEquals(0xa5, cpu.zpyAddress(0x80));
cpu.setYRegister(0x95);
assertEquals(0x15, cpu.zpyAddress(0x80));
}
// Test for GitHub symon issue #9, "LSR can yield wrong result"
public void testRightShiftMasksBitsCorrectly() throws Exception {
// Illegal value, the accumulator should only care about the low 8 bytes.
// I'm a little uncomfortable with this test because really, setAccumulator should
// defensively mask the value, but does not. Is this relying on a bug to test another bug?
cpu.setAccumulator(0xff8);
// Sanity check, in case I ever change my mind on setAccumulator's behavior
assertEquals(0xff8, cpu.getAccumulator());
bus.loadProgram(0x4a, // LSR
0x4a); // LSR
cpu.step();
assertEquals(0x7C, cpu.getAccumulator());
cpu.step();
assertEquals(0x3E, cpu.getAccumulator());
}
}