diff --git a/cpu.go b/cpu.go index 340fa29..e5f5268 100644 --- a/cpu.go +++ b/cpu.go @@ -16,6 +16,7 @@ type Cpu struct { const ( ResetVector = 0xFFFC // 0xFFFC-FFFD + IrqVector = 0xFFFE // 0xFFFE-FFFF ) // Create an new Cpu instance with the specified AddressBus @@ -23,6 +24,11 @@ func NewCpu(bus *AddressBus) (*Cpu, error) { return &Cpu{bus: bus}, nil } +func (c *Cpu) String() string { + str := ">>> CPU [ A ] [ X ] [ Y ] [ SP ] [ PC ] NVxBDIZC\n>>> 0x%02X 0x%02X 0x%02X 0x%02X 0x%04X %08b\n" + return fmt.Sprintf(str, c.A, c.X, c.Y, c.SP, c.PC, c.P) +} + func (c *Cpu) HasAddressBus() bool { return c.bus != nil } @@ -31,6 +37,27 @@ func (c *Cpu) HasAddressBus() bool { func (c *Cpu) Reset() { c.PC = c.bus.Read16(ResetVector) c.P = 0x34 + + // Not specified, but let's clean up + c.A = 0x00 + c.X = 0x00 + c.Y = 0x00 + c.SP = 0xFF +} + +// Simulate the IRQ pin +func (c *Cpu) Interrupt() { + c.handleIrq(c.PC) +} + +func (c *Cpu) handleIrq(PC uint16) { + c.stackPush(byte(PC >> 8)) + c.stackPush(byte(PC)) + c.stackPush(c.P) + + c.setIrqDisable(true) + + c.PC = c.bus.Read16(IrqVector) } // Load the specified program data at the given memory location @@ -45,6 +72,7 @@ func (c *Cpu) LoadProgram(data []byte, location uint16) { // Execute the instruction pointed to by the Program Counter (PC) func (c *Cpu) Step() { + // fmt.Println(c) instruction := c.readNextInstruction() c.PC += uint16(instruction.Size) // fmt.Println(instruction) @@ -64,13 +92,13 @@ func (c *Cpu) execute(instruction Instruction) { case sed: c.setDecimal(true) case sei: - c.setInterrupt(true) + c.setIrqDisable(true) case clc: c.setCarry(false) case cld: c.setDecimal(false) case cli: - c.setInterrupt(false) + c.setIrqDisable(false) case clv: c.setOverflow(false) case inx: @@ -114,16 +142,89 @@ func (c *Cpu) execute(instruction Instruction) { case tsx: c.setX(c.SP) case txs: - c.setSP(c.X) + c.SP = c.X case asl: c.ASL(instruction) case lsr: c.LSR(instruction) + case rol: + c.ROL(instruction) + case ror: + c.ROR(instruction) + case cmp: + c.CMP(instruction) + case cpx: + c.CPX(instruction) + case cpy: + c.CPY(instruction) + case brk: + c.BRK() + case bcc: + if !c.getCarry() { + c.branch(instruction) + } + case bcs: + if c.getCarry() { + c.branch(instruction) + } + case bne: + if !c.getZero() { + c.branch(instruction) + } + case beq: + if c.getZero() { + c.branch(instruction) + } + case bpl: + if !c.getNegative() { + c.branch(instruction) + } + case bmi: + if c.getNegative() { + c.branch(instruction) + } + case bvc: + if !c.getOverflow() { + c.branch(instruction) + } + case bvs: + if c.getOverflow() { + c.branch(instruction) + } + case bit: + c.BIT(instruction) + case php: + c.stackPush(c.P | 0x30) + case plp: + c.setP(c.stackPop()) + case pha: + c.stackPush(c.A) + case pla: + value := c.stackPop() + c.setA(value) + case jmp: + c.JMP(instruction) + case jsr: + c.JSR(instruction) + case rts: + c.PC = (uint16(c.stackPop()) | uint16(c.stackPop())<<8) + 1 + case rti: + c.setP(c.stackPop()) + c.PC = uint16(c.stackPop()) | uint16(c.stackPop())<<8 default: panic(fmt.Errorf("Unimplemented instruction: %s", instruction)) } } +func (c *Cpu) branch(in Instruction) { + relative := int8(in.Op8) // Signed! + if relative >= 0 { + c.PC += uint16(relative) + } else { + c.PC -= -uint16(relative) + } +} + func (c *Cpu) resolveOperand(in Instruction) uint8 { switch in.addressingId { case immediate: diff --git a/cpu_instructions.go b/cpu_instructions.go index 6ec39a5..11dcb9e 100644 --- a/cpu_instructions.go +++ b/cpu_instructions.go @@ -17,6 +17,8 @@ func (c *Cpu) SBC(in Instruction) { operand := c.resolveOperand(in) carryIn := c.getCarryInt() + // fmt.Printf("SBC: A: 0x%02X V: 0x%02X C: %b D: %v\n", c.A, operand, carryIn, c.getDecimal()) + if c.getDecimal() { c.sbcDecimal(c.A, operand, carryIn) } else { @@ -117,6 +119,83 @@ func (c *Cpu) LSR(in Instruction) { } } +func (c *Cpu) ROL(in Instruction) { + carry := c.getCarryInt() + + switch in.addressingId { + case accumulator: + c.setCarry((c.A & 0x80) != 0) + c.A = c.A<<1 | carry + c.setArithmeticFlags(c.A) + default: + address := c.memoryAddress(in) + value := c.bus.Read(address) + c.setCarry((value & 0x80) != 0) + value = value<<1 | carry + c.bus.Write(address, value) + c.setArithmeticFlags(value) + } +} + +func (c *Cpu) ROR(in Instruction) { + carry := c.getCarryInt() + + switch in.addressingId { + case accumulator: + c.setCarry(c.A&0x01 == 1) + c.A = c.A>>1 | carry<<7 + c.setArithmeticFlags(c.A) + default: + address := c.memoryAddress(in) + value := c.bus.Read(address) + c.setCarry(value&0x01 == 1) + value = value>>1 | carry<<7 + c.bus.Write(address, value) + c.setArithmeticFlags(value) + } +} + +func (c *Cpu) CMP(in Instruction) { + value := c.resolveOperand(in) + // fmt.Printf("CMP A: 0x%02X V: 0x%02X\n", c.A, value) + c.setCarry(c.A >= value) + c.setArithmeticFlags(c.A - value) +} + +func (c *Cpu) CPX(in Instruction) { + value := c.resolveOperand(in) + c.setCarry(c.X >= value) + c.setArithmeticFlags(c.X - value) +} + +func (c *Cpu) CPY(in Instruction) { + value := c.resolveOperand(in) + c.setCarry(c.Y >= value) + c.setArithmeticFlags(c.Y - value) +} + +func (c *Cpu) BRK() { + c.setBreak(true) + c.handleIrq(c.PC + 1) +} + +func (c *Cpu) BIT(in Instruction) { + value := c.resolveOperand(in) + c.setNegative((value & 0x80) != 0) + c.setOverflow((value & 0x40) != 0) + c.setZero((c.A & value) == 0) +} + +func (c *Cpu) JMP(in Instruction) { + c.PC = c.memoryAddress(in) +} + +func (c *Cpu) JSR(in Instruction) { + c.stackPush(byte((c.PC - 1) >> 8)) + c.stackPush(byte(c.PC - 1)) + c.PC = c.memoryAddress(in) +} + // Performs regular, 8-bit addition func (c *Cpu) adcNormal(a uint8, b uint8, carryIn uint8) { result16 := uint16(a) + uint16(b) + uint16(carryIn) @@ -162,7 +241,11 @@ func (c *Cpu) adcDecimal(a uint8, b uint8, carryIn uint8) { func (c *Cpu) sbcDecimal(a uint8, b uint8, carryIn uint8) { var carryB uint8 = 0 - carryIn = (carryIn + 1) % 1 + if carryIn == 0 { + carryIn = 1 + } else { + carryIn = 0 + } low := (a & 0x0F) - (b & 0x0F) - carryIn if (low & 0x10) != 0 { diff --git a/cpu_stack.go b/cpu_stack.go new file mode 100644 index 0000000..35d12cd --- /dev/null +++ b/cpu_stack.go @@ -0,0 +1,19 @@ +package i6502 + +const ( + StackBase = 0x0100 +) + +func (c *Cpu) stackPush(data byte) { + c.bus.Write(StackBase+uint16(c.SP), data) + c.SP -= 1 +} + +func (c *Cpu) stackPeek() byte { + return c.bus.Read(StackBase + uint16(c.SP+1)) +} + +func (c *Cpu) stackPop() byte { + c.SP += 1 + return c.bus.Read(StackBase + uint16(c.SP)) +} diff --git a/cpu_stack_test.go b/cpu_stack_test.go new file mode 100644 index 0000000..c9c5834 --- /dev/null +++ b/cpu_stack_test.go @@ -0,0 +1,28 @@ +package i6502 + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestStackPushPopPeek(t *testing.T) { + assert := assert.New(t) + cpu, _, _ := NewRamMachine() + + assert.Equal(0xFF, cpu.SP) + + cpu.stackPush(0x42) + cpu.stackPush(0xA0) + + assert.Equal(0xFD, cpu.SP) + assert.Equal(0x42, cpu.bus.Read(0x1FF)) + assert.Equal(0xA0, cpu.bus.Read(0x1FE)) + + peekValue := cpu.stackPeek() + assert.Equal(0xFD, cpu.SP) + assert.Equal(0xA0, peekValue) + + popValue := cpu.stackPop() + assert.Equal(0xFE, cpu.SP) + assert.Equal(0xA0, popValue) +} diff --git a/cpu_status.go b/cpu_status.go index f9097ad..251ab12 100644 --- a/cpu_status.go +++ b/cpu_status.go @@ -4,7 +4,7 @@ package i6502 const ( sCarry = iota sZero - sInterrupt + sIrqDisable sDecimal sBreak _ @@ -36,8 +36,8 @@ func (c *Cpu) setZero(state bool) { c.setStatus(sZero, state) } -func (c *Cpu) setInterrupt(state bool) { - c.setStatus(sInterrupt, state) +func (c *Cpu) setIrqDisable(state bool) { + c.setStatus(sIrqDisable, state) } func (c *Cpu) setDecimal(state bool) { @@ -68,8 +68,8 @@ func (c *Cpu) getZero() bool { return c.getStatus(sZero) } -func (c *Cpu) getInterrupt() bool { - return c.getStatus(sInterrupt) +func (c *Cpu) getIrqDisable() bool { + return c.getStatus(sIrqDisable) } func (c *Cpu) getDecimal() bool { @@ -117,3 +117,8 @@ func (c *Cpu) setSP(value byte) { c.SP = value c.setArithmeticFlags(c.SP) } + +func (c *Cpu) setP(value byte) { + c.P = value + c.P |= 0x20 +} diff --git a/cpu_test.go b/cpu_test.go index 3801a33..e1ba116 100644 --- a/cpu_test.go +++ b/cpu_test.go @@ -1,7 +1,9 @@ package i6502 import ( + "fmt" "github.com/stretchr/testify/assert" + "io/ioutil" "testing" ) @@ -17,6 +19,39 @@ func NewRamMachine() (*Cpu, *AddressBus, *Ram) { return cpu, bus, ram } +func loadProgram(path string) []byte { + data, err := ioutil.ReadFile(path) + if err != nil { + panic(fmt.Sprintf("Cannot open program file '%s'\n.", path)) + } + + return data +} + +func TestKlausDormann6502(t *testing.T) { + fmt.Println("Running Klaus Dormann' 6502 functional tests. This may take some time...") + cpu, _, _ := NewRamMachine() + cpu.LoadProgram(loadProgram("test/6502_functional_test.bin"), 0x0000) + cpu.PC = 0x0400 + prevPC := uint16(0x0400) + + for { + cpu.Step() + + if cpu.PC == prevPC { + if cpu.PC != 0x3399 { + str := "Looping PC detected at PC 0x%04X. We've hit a failing Klaus Dormann test." + panic(fmt.Sprintf(str, cpu.PC)) + } else { + fmt.Println("Klaus Dormann's 6502 functional tests passed.") + break + } + } + + prevPC = cpu.PC + } +} + func TestNewCpu(t *testing.T) { cpu, err := NewCpu(nil) @@ -35,9 +70,7 @@ func TestCpuReset(t *testing.T) { assert := assert.New(t) cpu, _, _ := NewRamMachine() - - cpu.bus.Write(0xFFFC, 0x34) - cpu.bus.Write(0xFFFD, 0x12) + cpu.bus.Write16(0xFFFC, 0x1234) cpu.Reset() @@ -49,6 +82,28 @@ func TestCpuReset(t *testing.T) { assert.Equal(0x1234, cpu.PC) } +func TestCpuInterrupt(t *testing.T) { + cpu, _, _ := NewRamMachine() + + cpu.bus.Write16(0xFFFE, 0x1234) // Write the IRQ vector + cpu.setIrqDisable(false) // Enable interrupts + cpu.SP = 0xFF // Set the stack pointer + cpu.PC = 0x0380 // Some fake point of execution + + assert.Equal(t, 0xFF, cpu.SP) + + status := cpu.P + + // Trigger IRQ + cpu.Interrupt() + + assert.Equal(t, 0x1234, cpu.PC) + assert.Equal(t, 0x03, cpu.bus.Read(0x01FF)) + assert.Equal(t, 0x80, cpu.bus.Read(0x01FE)) + assert.Equal(t, status, cpu.bus.Read(0x01FD)) + assert.True(t, cpu.getIrqDisable()) +} + func TestProgramLoading(t *testing.T) { assert := assert.New(t) @@ -94,11 +149,11 @@ func TestSED(t *testing.T) { func TestSEI(t *testing.T) { cpu, _, _ := NewRamMachine() cpu.LoadProgram([]byte{0x78}, 0x0300) - cpu.setInterrupt(false) + cpu.setIrqDisable(false) - assert.False(t, cpu.getInterrupt()) + assert.False(t, cpu.getIrqDisable()) cpu.Step() - assert.True(t, cpu.getInterrupt()) + assert.True(t, cpu.getIrqDisable()) } func TestCLC(t *testing.T) { @@ -124,11 +179,11 @@ func TestCLD(t *testing.T) { func TestCLI(t *testing.T) { cpu, _, _ := NewRamMachine() cpu.LoadProgram([]byte{0x58}, 0x0300) - cpu.setInterrupt(true) + cpu.setIrqDisable(true) - assert.True(t, cpu.getInterrupt()) + assert.True(t, cpu.getIrqDisable()) cpu.Step() - assert.False(t, cpu.getInterrupt()) + assert.False(t, cpu.getIrqDisable()) } func TestCLV(t *testing.T) { @@ -376,7 +431,7 @@ func TestSBCDecimal(t *testing.T) { cpu.Step() assert.Equal(t, 0x0302, cpu.PC) - assert.Equal(t, 0x29, cpu.A) + assert.Equal(t, 0x28, cpu.A) } func TestSBCZero(t *testing.T) { @@ -925,10 +980,10 @@ func TestLDYZeropage(t *testing.T) { assert.Equal(t, 0xF8, cpu.Y) } -func TestLDYZeropageY(t *testing.T) { +func TestLDYZeropageX(t *testing.T) { cpu, _, _ := NewRamMachine() cpu.LoadProgram([]byte{0xB4, 0x41}, 0x0300) - cpu.Y = 0x01 + cpu.X = 0x01 cpu.bus.Write(0x42, 0xF8) cpu.Step() @@ -948,10 +1003,10 @@ func TestLDYAbsolute(t *testing.T) { assert.Equal(t, 0xF8, cpu.Y) } -func TestLDYAbsoluteY(t *testing.T) { +func TestLDYAbsoluteX(t *testing.T) { cpu, _, _ := NewRamMachine() cpu.LoadProgram([]byte{0xBC, 0x00, 0x80}, 0x0300) - cpu.Y = 0x02 + cpu.X = 0x02 cpu.bus.Write16(0x8002, 0xF8) cpu.Step() @@ -1701,30 +1756,6 @@ func TestTXS(t *testing.T) { assert.Equal(t, 0x42, cpu.SP) } -func TestTXSNegative(t *testing.T) { - cpu, _, _ := NewRamMachine() - cpu.LoadProgram([]byte{0x9A}, 0x0300) - cpu.X = 0xE0 - - cpu.Step() - - assert.Equal(t, 0x0301, cpu.PC) - assert.Equal(t, 0xE0, cpu.SP) - assert.True(t, cpu.getNegative()) -} - -func TestTXSZero(t *testing.T) { - cpu, _, _ := NewRamMachine() - cpu.LoadProgram([]byte{0x9A}, 0x0300) - cpu.X = 0x00 - - cpu.Step() - - assert.Equal(t, 0x0301, cpu.PC) - assert.Equal(t, 0x00, cpu.SP) - assert.True(t, cpu.getZero()) -} - //// ASL func TestASLaccumulator(t *testing.T) { @@ -1962,3 +1993,1239 @@ func TestLSRabsoluteX(t *testing.T) { assert.Equal(t, 0x0303, cpu.PC) assert.Equal(t, 0x02, cpu.bus.Read(0x8002)) } + +//// ROL + +func TestROLAccumulator(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x2A}, 0x0300) + cpu.A = 0x01 + + cpu.Step() + + assert.Equal(t, 0x0301, cpu.PC) + assert.Equal(t, 0x02, cpu.A) +} + +func TestROLAccumulatorZeroAndCarry(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x2A}, 0x0300) + + cpu.setCarry(false) + cpu.A = 0x80 + + cpu.Step() + + assert.Equal(t, 0x0301, cpu.PC) + assert.Equal(t, 0x00, cpu.A) + assert.True(t, cpu.getZero()) + assert.True(t, cpu.getCarry()) +} + +func TestROLAccumulatorNegative(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x2A}, 0x0300) + + cpu.setCarry(false) + cpu.A = 0x40 + + cpu.Step() + + assert.Equal(t, 0x0301, cpu.PC) + assert.Equal(t, 0x80, cpu.A) + assert.True(t, cpu.getNegative()) +} + +func TestROLZeropage(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x26, 0x80}, 0x0300) + cpu.bus.Write(0x0080, 0x01) + + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.Equal(t, 0x02, cpu.bus.Read(0x0080)) +} + +func TestROLZeropageX(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x36, 0x80}, 0x0300) + cpu.X = 0x02 + cpu.bus.Write(0x0082, 0x01) + + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.Equal(t, 0x02, cpu.bus.Read(0x0082)) +} + +func TestROLAbsolute(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x2E, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8000, 0x01) + + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.Equal(t, 0x02, cpu.bus.Read(0x8000)) +} + +func TestROLAbsoluteX(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x3E, 0x00, 0x80}, 0x0300) + cpu.X = 0x02 + cpu.bus.Write(0x8002, 0x01) + + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.Equal(t, 0x02, cpu.bus.Read(0x8002)) +} + +//// ROR + +func TestRORAccumulator(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x6A}, 0x0300) + cpu.A = 0x02 + + cpu.Step() + + assert.Equal(t, 0x0301, cpu.PC) + assert.Equal(t, 0x01, cpu.A) +} + +func TestRORAccumulatorZeroAndCarry(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x6A}, 0x0300) + + cpu.setCarry(false) + cpu.A = 0x01 + + cpu.Step() + + assert.Equal(t, 0x0301, cpu.PC) + assert.Equal(t, 0x00, cpu.A) + assert.True(t, cpu.getZero()) + assert.True(t, cpu.getCarry()) +} + +func TestRORAccumulatorNegative(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x6A}, 0x0300) + + cpu.setCarry(true) + cpu.A = 0x01 + + cpu.Step() + + assert.Equal(t, 0x0301, cpu.PC) + assert.Equal(t, 0x80, cpu.A) + assert.True(t, cpu.getNegative()) +} + +func TestRORZeropage(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x66, 0x80}, 0x0300) + cpu.bus.Write(0x0080, 0x02) + + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.Equal(t, 0x01, cpu.bus.Read(0x0080)) +} + +func TestRORZeropageX(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x76, 0x80}, 0x0300) + cpu.X = 0x02 + cpu.bus.Write(0x0082, 0x02) + + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.Equal(t, 0x01, cpu.bus.Read(0x0082)) +} + +func TestRORAbsolute(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x6E, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8000, 0x02) + + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.Equal(t, 0x01, cpu.bus.Read(0x8000)) +} + +func TestRORAbsoluteX(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x7E, 0x00, 0x80}, 0x0300) + cpu.X = 0x02 + cpu.bus.Write(0x8002, 0x02) + + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.Equal(t, 0x01, cpu.bus.Read(0x8002)) +} + +/// CMP + +func TestCMPImmediate(t *testing.T) { + cpu, _, _ := NewRamMachine() + + // Equality + cpu.LoadProgram([]byte{0xC9, 0x42}, 0x0300) + cpu.A = 0x42 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.True(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Greater Than + cpu.LoadProgram([]byte{0xC9, 0x42}, 0x0300) + cpu.A = 0x43 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Less Than + cpu.LoadProgram([]byte{0xC9, 0x0A}, 0x0300) + cpu.A = 0x08 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.False(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) +} + +func TestCMPZeropage(t *testing.T) { + cpu, _, _ := NewRamMachine() + + // Equality + cpu.LoadProgram([]byte{0xC5, 0x80}, 0x0300) + cpu.bus.Write(0x0080, 0x42) + cpu.A = 0x42 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.True(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Greater Than + cpu.LoadProgram([]byte{0xC5, 0x80}, 0x0300) + cpu.bus.Write(0x0080, 0x42) + cpu.A = 0x43 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Less Than + cpu.LoadProgram([]byte{0xC5, 0x80}, 0x0300) + cpu.bus.Write(0x0080, 0x0A) + cpu.A = 0x08 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.False(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) +} + +func TestCMPZeropageX(t *testing.T) { + cpu, _, _ := NewRamMachine() + + // Equality + cpu.LoadProgram([]byte{0xD5, 0x80}, 0x0300) + cpu.bus.Write(0x0082, 0x42) + cpu.X = 0x02 + cpu.A = 0x42 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.True(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Greater Than + cpu.LoadProgram([]byte{0xD5, 0x80}, 0x0300) + cpu.bus.Write(0x0082, 0x42) + cpu.X = 0x02 + cpu.A = 0x43 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Less Than + cpu.LoadProgram([]byte{0xD5, 0x80}, 0x0300) + cpu.bus.Write(0x0082, 0x0A) + cpu.X = 0x02 + cpu.A = 0x08 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.False(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) +} + +func TestCMPAbsolute(t *testing.T) { + cpu, _, _ := NewRamMachine() + + // Equality + cpu.LoadProgram([]byte{0xCD, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8000, 0x42) + cpu.A = 0x42 + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.True(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Greater Than + cpu.LoadProgram([]byte{0xCD, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8000, 0x42) + cpu.A = 0x43 + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Less Than + cpu.LoadProgram([]byte{0xCD, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8000, 0x0A) + cpu.A = 0x08 + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.False(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) +} + +func TestCMPAbsoluteX(t *testing.T) { + cpu, _, _ := NewRamMachine() + + // Equality + cpu.LoadProgram([]byte{0xDD, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8002, 0x42) + cpu.X = 0x02 + cpu.A = 0x42 + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.True(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Greater Than + cpu.LoadProgram([]byte{0xDD, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8002, 0x42) + cpu.X = 0x02 + cpu.A = 0x43 + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Less Than + cpu.LoadProgram([]byte{0xDD, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8002, 0x0A) + cpu.X = 0x02 + cpu.A = 0x08 + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.False(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) +} + +func TestCMPAbsoluteY(t *testing.T) { + cpu, _, _ := NewRamMachine() + + // Equality + cpu.LoadProgram([]byte{0xD9, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8002, 0x42) + cpu.Y = 0x02 + cpu.A = 0x42 + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.True(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Greater Than + cpu.LoadProgram([]byte{0xD9, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8002, 0x42) + cpu.Y = 0x02 + cpu.A = 0x43 + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Less Than + cpu.LoadProgram([]byte{0xD9, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8002, 0x0A) + cpu.Y = 0x02 + cpu.A = 0x08 + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.False(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) +} + +func TestCMPIndirectX(t *testing.T) { + cpu, _, _ := NewRamMachine() + + // Equality + cpu.LoadProgram([]byte{0xC1, 0x80}, 0x0300) + cpu.bus.Write16(0x0082, 0xC000) + cpu.bus.Write(0xC000, 0x42) + cpu.X = 0x02 + cpu.A = 0x42 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.True(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Greater Than + cpu.LoadProgram([]byte{0xC1, 0x80}, 0x0300) + cpu.bus.Write16(0x0082, 0xC000) + cpu.bus.Write(0xC000, 0x42) + cpu.X = 0x02 + cpu.A = 0x43 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Less Than + cpu.LoadProgram([]byte{0xC1, 0x80}, 0x0300) + cpu.bus.Write16(0x0082, 0xC000) + cpu.bus.Write(0xC000, 0x0A) + cpu.X = 0x02 + cpu.A = 0x08 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.False(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) +} + +func TestCMPIndirectY(t *testing.T) { + cpu, _, _ := NewRamMachine() + + // Equality + cpu.LoadProgram([]byte{0xD1, 0x80}, 0x0300) + cpu.bus.Write16(0x0080, 0xC000) + cpu.bus.Write(0xC002, 0x42) + cpu.Y = 0x02 + cpu.A = 0x42 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.True(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Greater Than + cpu.LoadProgram([]byte{0xD1, 0x80}, 0x0300) + cpu.bus.Write16(0x0080, 0xC000) + cpu.bus.Write(0xC002, 0x42) + cpu.X = 0x02 + cpu.A = 0x43 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Less Than + cpu.LoadProgram([]byte{0xD1, 0x80}, 0x0300) + cpu.bus.Write16(0x0080, 0xC000) + cpu.bus.Write(0xC002, 0x0A) + cpu.X = 0x02 + cpu.A = 0x08 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.False(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) +} + +//// CPX + +func TestCPXImmediate(t *testing.T) { + cpu, _, _ := NewRamMachine() + + // Equality + cpu.LoadProgram([]byte{0xE0, 0x42}, 0x0300) + cpu.X = 0x42 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.True(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Greater Than + cpu.LoadProgram([]byte{0xE0, 0x42}, 0x0300) + cpu.X = 0x43 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Less Than + cpu.LoadProgram([]byte{0xE0, 0x0A}, 0x0300) + cpu.X = 0x08 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.False(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) +} + +func TestCPXZeropage(t *testing.T) { + cpu, _, _ := NewRamMachine() + + // Equality + cpu.LoadProgram([]byte{0xE4, 0x80}, 0x0300) + cpu.bus.Write(0x0080, 0x42) + cpu.X = 0x42 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.True(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Greater Than + cpu.LoadProgram([]byte{0xE4, 0x80}, 0x0300) + cpu.bus.Write(0x0080, 0x42) + cpu.X = 0x43 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Less Than + cpu.LoadProgram([]byte{0xE4, 0x80}, 0x0300) + cpu.bus.Write(0x0080, 0x0A) + cpu.X = 0x08 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.False(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) +} + +func TestCPXAbsolute(t *testing.T) { + cpu, _, _ := NewRamMachine() + + // Equality + cpu.LoadProgram([]byte{0xEC, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8000, 0x42) + cpu.X = 0x42 + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.True(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Greater Than + cpu.LoadProgram([]byte{0xEC, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8000, 0x42) + cpu.X = 0x43 + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Less Than + cpu.LoadProgram([]byte{0xEC, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8000, 0x0A) + cpu.X = 0x08 + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.False(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) +} + +//// CPY + +func TestCPYImmediate(t *testing.T) { + cpu, _, _ := NewRamMachine() + + // Equality + cpu.LoadProgram([]byte{0xC0, 0x42}, 0x0300) + cpu.Y = 0x42 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.True(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Greater Than + cpu.LoadProgram([]byte{0xC0, 0x42}, 0x0300) + cpu.Y = 0x43 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Less Than + cpu.LoadProgram([]byte{0xC0, 0x0A}, 0x0300) + cpu.Y = 0x08 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.False(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) +} + +func TestCPYZeropage(t *testing.T) { + cpu, _, _ := NewRamMachine() + + // Equality + cpu.LoadProgram([]byte{0xC4, 0x80}, 0x0300) + cpu.bus.Write(0x0080, 0x42) + cpu.Y = 0x42 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.True(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Greater Than + cpu.LoadProgram([]byte{0xC4, 0x80}, 0x0300) + cpu.bus.Write(0x0080, 0x42) + cpu.Y = 0x43 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Less Than + cpu.LoadProgram([]byte{0xC4, 0x80}, 0x0300) + cpu.bus.Write(0x0080, 0x0A) + cpu.Y = 0x08 + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.False(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) +} + +func TestCPYAbsolute(t *testing.T) { + cpu, _, _ := NewRamMachine() + + // Equality + cpu.LoadProgram([]byte{0xCC, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8000, 0x42) + cpu.Y = 0x42 + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.True(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Greater Than + cpu.LoadProgram([]byte{0xCC, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8000, 0x42) + cpu.Y = 0x43 + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.True(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + + // Less Than + cpu.LoadProgram([]byte{0xCC, 0x00, 0x80}, 0x0300) + cpu.bus.Write(0x8000, 0x0A) + cpu.Y = 0x08 + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.False(t, cpu.getCarry()) + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) +} + +//// BRK + +func TestBRK(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x00}, 0x0300) + cpu.bus.Write16(IrqVector, 0x1234) + cpu.SP = 0xFF + status := cpu.P + + cpu.Step() + + assert.Equal(t, 0x1234, cpu.PC) + assert.Equal(t, 0x03, cpu.bus.Read(0x01FF)) + assert.Equal(t, 0x02, cpu.bus.Read(0x01FE)) + assert.Equal(t, status, cpu.bus.Read(0x01FD)) + assert.True(t, cpu.getBreak()) + +} + +//// BCC + +func TestBCC(t *testing.T) { + cpu, _, _ := NewRamMachine() + + /// Positive offset + // Carry set + cpu.LoadProgram([]byte{0x90, 0x05}, 0x0300) + cpu.setCarry(true) + cpu.Step() + assert.Equal(t, 0x0302, cpu.PC) + + // Carry not set + cpu.LoadProgram([]byte{0x90, 0x05}, 0x0300) + cpu.setCarry(false) + cpu.Step() + // 0x0302 + 0x05 = 0x0307 + assert.Equal(t, 0x0307, cpu.PC) + + /// Negative offset + // Carry set + cpu.LoadProgram([]byte{0x90, 0xfb}, 0x0300) + cpu.setCarry(true) + cpu.Step() + assert.Equal(t, 0x0302, cpu.PC) + + // Carry not set + cpu.LoadProgram([]byte{0x90, 0xFB}, 0x0300) + cpu.setCarry(false) + cpu.Step() + // 0x0302 + 0xFB => 0x0302 - 0x05 => 0x02FD + assert.Equal(t, 0x02FD, cpu.PC) +} + +//// BCS + +func TestBCS(t *testing.T) { + cpu, _, _ := NewRamMachine() + + /// Positive offset + // Carry set + cpu.LoadProgram([]byte{0xB0, 0x05}, 0x0300) + cpu.setCarry(true) + cpu.Step() + // 0x0302 + 0x05 = 0x0307 + assert.Equal(t, 0x0307, cpu.PC) + + // Carry not set + cpu.LoadProgram([]byte{0xB0, 0x05}, 0x0300) + cpu.setCarry(false) + cpu.Step() + assert.Equal(t, 0x0302, cpu.PC) + + /// Negative offset + // Carry set + cpu.LoadProgram([]byte{0xB0, 0xfb}, 0x0300) + cpu.setCarry(true) + cpu.Step() + // 0x0302 + 0xFB => 0x0302 - 0x05 => 0x02FD + assert.Equal(t, 0x02FD, cpu.PC) + + // Carry not set + cpu.LoadProgram([]byte{0xB0, 0xFB}, 0x0300) + cpu.setCarry(false) + cpu.Step() + assert.Equal(t, 0x0302, cpu.PC) +} + +//// BNE + +func TestBNE(t *testing.T) { + cpu, _, _ := NewRamMachine() + + /// Positive offset + // Carry set + cpu.LoadProgram([]byte{0xD0, 0x05}, 0x0300) + cpu.setZero(true) + cpu.Step() + assert.Equal(t, 0x0302, cpu.PC) + + // Carry not set + cpu.LoadProgram([]byte{0xD0, 0x05}, 0x0300) + cpu.setZero(false) + cpu.Step() + // 0x0302 + 0x05 = 0x0307 + assert.Equal(t, 0x0307, cpu.PC) + + /// Negative offset + // Carry set + cpu.LoadProgram([]byte{0xD0, 0xfb}, 0x0300) + cpu.setZero(true) + cpu.Step() + assert.Equal(t, 0x0302, cpu.PC) + + // Carry not set + cpu.LoadProgram([]byte{0xD0, 0xFB}, 0x0300) + cpu.setZero(false) + cpu.Step() + // 0x0302 + 0xFB => 0x0302 - 0x05 => 0x02FD + assert.Equal(t, 0x02FD, cpu.PC) +} + +//// BEQ + +func TestBEQ(t *testing.T) { + cpu, _, _ := NewRamMachine() + + /// Positive offset + // Carry set + cpu.LoadProgram([]byte{0xF0, 0x05}, 0x0300) + cpu.setZero(true) + cpu.Step() + // 0x0302 + 0x05 = 0x0307 + assert.Equal(t, 0x0307, cpu.PC) + + // Carry not set + cpu.LoadProgram([]byte{0xF0, 0x05}, 0x0300) + cpu.setZero(false) + cpu.Step() + assert.Equal(t, 0x0302, cpu.PC) + + /// Negative offset + // Carry set + cpu.LoadProgram([]byte{0xF0, 0xfb}, 0x0300) + cpu.setZero(true) + cpu.Step() + // 0x0302 + 0xFB => 0x0302 - 0x05 => 0x02FD + assert.Equal(t, 0x02FD, cpu.PC) + + // Carry not set + cpu.LoadProgram([]byte{0xF0, 0xFB}, 0x0300) + cpu.setZero(false) + cpu.Step() + assert.Equal(t, 0x0302, cpu.PC) +} + +//// BPL + +func TestBPL(t *testing.T) { + cpu, _, _ := NewRamMachine() + + /// Positive offset + // Carry set + cpu.LoadProgram([]byte{0x10, 0x05}, 0x0300) + cpu.setNegative(true) + cpu.Step() + assert.Equal(t, 0x0302, cpu.PC) + + // Carry not set + cpu.LoadProgram([]byte{0x10, 0x05}, 0x0300) + cpu.setNegative(false) + cpu.Step() + // 0x0302 + 0x05 = 0x0307 + assert.Equal(t, 0x0307, cpu.PC) + + /// Negative offset + // Carry set + cpu.LoadProgram([]byte{0x10, 0xfb}, 0x0300) + cpu.setNegative(true) + cpu.Step() + assert.Equal(t, 0x0302, cpu.PC) + + // Carry not set + cpu.LoadProgram([]byte{0x10, 0xFB}, 0x0300) + cpu.setNegative(false) + cpu.Step() + // 0x0302 + 0xFB => 0x0302 - 0x05 => 0x02FD + assert.Equal(t, 0x02FD, cpu.PC) +} + +//// BMI + +func TestBMI(t *testing.T) { + cpu, _, _ := NewRamMachine() + + /// Positive offset + // Carry set + cpu.LoadProgram([]byte{0x30, 0x05}, 0x0300) + cpu.setNegative(true) + cpu.Step() + // 0x0302 + 0x05 = 0x0307 + assert.Equal(t, 0x0307, cpu.PC) + + // Carry not set + cpu.LoadProgram([]byte{0x30, 0x05}, 0x0300) + cpu.setNegative(false) + cpu.Step() + assert.Equal(t, 0x0302, cpu.PC) + + /// Negative offset + // Carry set + cpu.LoadProgram([]byte{0x30, 0xfb}, 0x0300) + cpu.setNegative(true) + cpu.Step() + // 0x0302 + 0xFB => 0x0302 - 0x05 => 0x02FD + assert.Equal(t, 0x02FD, cpu.PC) + + // Carry not set + cpu.LoadProgram([]byte{0x30, 0xFB}, 0x0300) + cpu.setNegative(false) + cpu.Step() + assert.Equal(t, 0x0302, cpu.PC) +} + +//// BVC + +func TestBVC(t *testing.T) { + cpu, _, _ := NewRamMachine() + + /// Positive offset + // Carry set + cpu.LoadProgram([]byte{0x50, 0x05}, 0x0300) + cpu.setOverflow(true) + cpu.Step() + assert.Equal(t, 0x0302, cpu.PC) + + // Carry not set + cpu.LoadProgram([]byte{0x50, 0x05}, 0x0300) + cpu.setOverflow(false) + cpu.Step() + // 0x0302 + 0x05 = 0x0307 + assert.Equal(t, 0x0307, cpu.PC) + + /// Negative offset + // Carry set + cpu.LoadProgram([]byte{0x50, 0xfb}, 0x0300) + cpu.setOverflow(true) + cpu.Step() + assert.Equal(t, 0x0302, cpu.PC) + + // Carry not set + cpu.LoadProgram([]byte{0x50, 0xFB}, 0x0300) + cpu.setOverflow(false) + cpu.Step() + // 0x0302 + 0xFB => 0x0302 - 0x05 => 0x02FD + assert.Equal(t, 0x02FD, cpu.PC) +} + +//// BVS + +func TestBVS(t *testing.T) { + cpu, _, _ := NewRamMachine() + + /// Positive offset + // Carry set + cpu.LoadProgram([]byte{0x70, 0x05}, 0x0300) + cpu.setOverflow(true) + cpu.Step() + // 0x0302 + 0x05 = 0x0307 + assert.Equal(t, 0x0307, cpu.PC) + + // Carry not set + cpu.LoadProgram([]byte{0x70, 0x05}, 0x0300) + cpu.setOverflow(false) + cpu.Step() + assert.Equal(t, 0x0302, cpu.PC) + + /// Negative offset + // Carry set + cpu.LoadProgram([]byte{0x70, 0xfb}, 0x0300) + cpu.setOverflow(true) + cpu.Step() + // 0x0302 + 0xFB => 0x0302 - 0x05 => 0x02FD + assert.Equal(t, 0x02FD, cpu.PC) + + // Carry not set + cpu.LoadProgram([]byte{0x70, 0xFB}, 0x0300) + cpu.setOverflow(false) + cpu.Step() + assert.Equal(t, 0x0302, cpu.PC) +} + +//// BIT + +func TestBITZeropage(t *testing.T) { + cpu, _, _ := NewRamMachine() + + cpu.bus.Write(0x0000, 0xC0) + cpu.bus.Write(0x0010, 0x40) + cpu.bus.Write(0x0020, 0x80) + + cpu.LoadProgram([]byte{0x24, 0x00}, 0x0300) + cpu.A = 0x01 + cpu.Step() + assert.True(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) + assert.True(t, cpu.getOverflow()) + + cpu.LoadProgram([]byte{0x24, 0x20}, 0x0300) + cpu.A = 0x40 + cpu.Step() + assert.True(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) + assert.False(t, cpu.getOverflow()) + + cpu.LoadProgram([]byte{0x24, 0x10}, 0x0300) + cpu.A = 0x80 + cpu.Step() + assert.True(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + assert.True(t, cpu.getOverflow()) + + cpu.LoadProgram([]byte{0x24, 0x00}, 0x0300) + cpu.A = 0xC0 + cpu.Step() + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) + assert.True(t, cpu.getOverflow()) + + cpu.LoadProgram([]byte{0x24, 0x00}, 0x0300) + cpu.A = 0xFF + cpu.Step() + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) + assert.True(t, cpu.getOverflow()) +} + +func TestBITAbsolute(t *testing.T) { + cpu, _, _ := NewRamMachine() + + cpu.bus.Write(0xC000, 0xC0) + cpu.bus.Write(0xC010, 0x40) + cpu.bus.Write(0xC020, 0x80) + + cpu.LoadProgram([]byte{0x2C, 0x00, 0xC0}, 0x0300) + cpu.A = 0x01 + cpu.Step() + assert.True(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) + assert.True(t, cpu.getOverflow()) + + cpu.LoadProgram([]byte{0x2C, 0x20, 0xC0}, 0x0300) + cpu.A = 0x40 + cpu.Step() + assert.True(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) + assert.False(t, cpu.getOverflow()) + + cpu.LoadProgram([]byte{0x2C, 0x10, 0xC0}, 0x0300) + cpu.A = 0x80 + cpu.Step() + assert.True(t, cpu.getZero()) + assert.False(t, cpu.getNegative()) + assert.True(t, cpu.getOverflow()) + + cpu.LoadProgram([]byte{0x2C, 0x00, 0xC0}, 0x0300) + cpu.A = 0xC0 + cpu.Step() + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) + assert.True(t, cpu.getOverflow()) + + cpu.LoadProgram([]byte{0x2C, 0x00, 0xC0}, 0x0300) + cpu.A = 0xFF + cpu.Step() + assert.False(t, cpu.getZero()) + assert.True(t, cpu.getNegative()) + assert.True(t, cpu.getOverflow()) +} + +//// PHP + +func TestPHP(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x08}, 0x0300) + cpu.SP = 0xFF + cpu.P = 0xB5 + + cpu.Step() + + assert.Equal(t, 0x0301, cpu.PC) + assert.Equal(t, 0xFE, cpu.SP) + assert.Equal(t, 0xB5, cpu.bus.Read(0x01FF)) +} + +//// PLP + +func TestPLP(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x28}, 0x0300) + cpu.stackPush(0xB5) + cpu.P = 0x34 + + cpu.Step() + + assert.Equal(t, 0x0301, cpu.PC) + assert.Equal(t, 0xFF, cpu.SP) + assert.Equal(t, 0xB5, cpu.P) +} + +//// PHA + +func TestPHA(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x48}, 0x0300) + cpu.SP = 0xFF + cpu.A = 0xB5 + + cpu.Step() + + assert.Equal(t, 0x0301, cpu.PC) + assert.Equal(t, 0xFE, cpu.SP) + assert.Equal(t, 0xB5, cpu.bus.Read(0x01FF)) +} + +//// PLP + +func TestPLA(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x68, 0x68, 0x68}, 0x0300) + + cpu.stackPush(0x42) + cpu.Step() + + assert.Equal(t, 0x0301, cpu.PC) + assert.Equal(t, 0xFF, cpu.SP) + assert.Equal(t, 0x42, cpu.A) + + cpu.stackPush(0xB5) + cpu.Step() + + assert.Equal(t, 0x0302, cpu.PC) + assert.Equal(t, 0xFF, cpu.SP) + assert.Equal(t, 0xB5, cpu.A) + assert.True(t, cpu.getNegative()) + + cpu.stackPush(0x00) + cpu.Step() + + assert.Equal(t, 0x0303, cpu.PC) + assert.Equal(t, 0xFF, cpu.SP) + assert.Equal(t, 0x00, cpu.A) + assert.True(t, cpu.getZero()) +} + +//// JMP + +func TestJMPAbsolute(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x4C, 0x34, 0x12}, 0x0300) + + cpu.Step() + + assert.Equal(t, 0x1234, cpu.PC) +} + +func TestJMPIndirect(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x6C, 0x00, 0xC0}, 0x0300) + cpu.bus.Write16(0xC000, 0x1234) + + cpu.Step() + + assert.Equal(t, 0x1234, cpu.PC) +} + +//// JSR + +func TestJSR(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x20, 0x34, 0x12}, 0x0300) + cpu.SP = 0xFF + + cpu.Step() + + assert.Equal(t, 0x1234, cpu.PC) + assert.Equal(t, 0xFD, cpu.SP) + + // We expect PC - 1 (e.g. 3rd byte of JSR) to be on the stack + assert.Equal(t, 0x03, cpu.bus.Read(0x1FF)) + assert.Equal(t, 0x02, cpu.bus.Read(0x1FE)) +} + +//// RTS + +func TestRTS(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x60}, 0x0300) + cpu.P = 0x34 + + cpu.stackPush(0x12) // PC HI + cpu.stackPush(0x34) // PC LO + + cpu.Step() + + assert.Equal(t, 0x1234+1, cpu.PC) + assert.Equal(t, 0x34, cpu.P) +} + +//// RTI + +func TestRTI(t *testing.T) { + cpu, _, _ := NewRamMachine() + cpu.LoadProgram([]byte{0x40}, 0x0300) + + cpu.stackPush(0x12) // PC HI + cpu.stackPush(0x34) // PC LO + cpu.stackPush(0x5B) // P + + cpu.Step() + + assert.Equal(t, 0x1234, cpu.PC) + assert.Equal(t, 0x5B|0x20, cpu.P) +} diff --git a/instruction.go b/instruction.go index 22877af..061eb9b 100644 --- a/instruction.go +++ b/instruction.go @@ -37,10 +37,10 @@ func (c *Cpu) readNextInstruction() Instruction { optype, ok := opTypes[opcode] if !ok { - panic(fmt.Sprintf("Unknown or unimplemented opcode 0x%02X", opcode)) + panic(fmt.Sprintf("Unknown or unimplemented opcode 0x%02X\n%s", opcode, c.String())) } - instruction := Instruction{OpType: optype} + instruction := Instruction{OpType: optype, Address: c.PC} switch instruction.Size { case 1: // Zero operand instruction case 2: // 8-bit operand diff --git a/opcodes.go b/opcodes.go index 8a7fbe9..b4e2276 100644 --- a/opcodes.go +++ b/opcodes.go @@ -213,9 +213,9 @@ var opTypes = map[uint8]OpType{ // LDY 0xA0: OpType{0xA0, ldy, immediate, 2, 2}, 0xA4: OpType{0xA4, ldy, zeropage, 2, 3}, - 0xB4: OpType{0xB4, ldy, zeropageY, 2, 4}, + 0xB4: OpType{0xB4, ldy, zeropageX, 2, 4}, 0xAC: OpType{0xAC, ldy, absolute, 3, 4}, - 0xBC: OpType{0xBC, ldy, absoluteY, 3, 4}, + 0xBC: OpType{0xBC, ldy, absoluteX, 3, 4}, // ORA 0x09: OpType{0x09, ora, immediate, 2, 2}, @@ -297,4 +297,82 @@ var opTypes = map[uint8]OpType{ 0x56: OpType{0x56, lsr, zeropageX, 2, 6}, 0x4E: OpType{0x4E, lsr, absolute, 3, 6}, 0x5E: OpType{0x5E, lsr, absoluteX, 3, 7}, + + // ROL + 0x2A: OpType{0x2A, rol, accumulator, 1, 2}, + 0x26: OpType{0x26, rol, zeropage, 2, 5}, + 0x36: OpType{0x36, rol, zeropageX, 2, 6}, + 0x2E: OpType{0x2E, rol, absolute, 3, 6}, + 0x3E: OpType{0x3E, rol, absoluteX, 3, 7}, + + // ROR + 0x6A: OpType{0x6A, ror, accumulator, 1, 2}, + 0x66: OpType{0x66, ror, zeropage, 2, 5}, + 0x76: OpType{0x76, ror, zeropageX, 2, 6}, + 0x6E: OpType{0x6E, ror, absolute, 3, 6}, + 0x7E: OpType{0x7E, ror, absoluteX, 3, 7}, + + // CMP + 0xC9: OpType{0xC9, cmp, immediate, 2, 2}, + 0xC5: OpType{0xC5, cmp, zeropage, 2, 3}, + 0xD5: OpType{0xD5, cmp, zeropageX, 2, 4}, + 0xCD: OpType{0xCD, cmp, absolute, 3, 4}, + 0xDD: OpType{0xDD, cmp, absoluteX, 3, 4}, + 0xD9: OpType{0xD9, cmp, absoluteY, 3, 4}, + 0xC1: OpType{0xC1, cmp, indirectX, 2, 6}, + 0xD1: OpType{0xD1, cmp, indirectY, 2, 5}, + + // CPX + 0xE0: OpType{0xE0, cpx, immediate, 2, 2}, + 0xE4: OpType{0xE4, cpx, zeropage, 2, 3}, + 0xEC: OpType{0xEC, cpx, absolute, 3, 4}, + + // CPY + 0xC0: OpType{0xC0, cpy, immediate, 2, 2}, + 0xC4: OpType{0xC4, cpy, zeropage, 2, 3}, + 0xCC: OpType{0xCC, cpy, absolute, 3, 4}, + + // BRK + 0x00: OpType{0x00, brk, implied, 1, 7}, + + // BCC / BCS + 0x90: OpType{0x90, bcc, relative, 2, 2}, + 0xB0: OpType{0xB0, bcs, relative, 2, 2}, + + // BNE / BEQ + 0xD0: OpType{0xD0, bne, relative, 2, 2}, + 0xF0: OpType{0xF0, beq, relative, 2, 2}, + + // BPL / BMI + 0x10: OpType{0x10, bpl, relative, 2, 2}, + 0x30: OpType{0x30, bmi, relative, 2, 2}, + + // BVC / BCS + 0x50: OpType{0x50, bvc, relative, 2, 2}, + 0x70: OpType{0x70, bvs, relative, 2, 2}, + + // BIT + 0x24: OpType{0x24, bit, zeropage, 2, 3}, + 0x2C: OpType{0x2C, bit, absolute, 3, 4}, + + // PHP / PLP + 0x08: OpType{0x08, php, implied, 1, 3}, + 0x28: OpType{0x28, plp, implied, 1, 4}, + + // PHA / PLA + 0x48: OpType{0x48, pha, implied, 1, 3}, + 0x68: OpType{0x68, pla, implied, 1, 4}, + + // JMP + 0x4C: OpType{0x4C, jmp, absolute, 3, 3}, + 0x6C: OpType{0x6C, jmp, indirect, 3, 5}, + + // JSR + 0x20: OpType{0x20, jsr, absolute, 3, 6}, + + // RTS + 0x60: OpType{0x60, rts, implied, 1, 6}, + + // RTI + 0x40: OpType{0x40, rti, implied, 1, 6}, } diff --git a/test/6502_functional_test.bin b/test/6502_functional_test.bin new file mode 100644 index 0000000..8a20227 Binary files /dev/null and b/test/6502_functional_test.bin differ