Gopher2600/hardware/cpu/cpu_test.go
2023-07-06 13:49:18 +01:00

642 lines
16 KiB
Go

// This file is part of Gopher2600.
//
// Gopher2600 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
package cpu_test
import (
"fmt"
"testing"
"github.com/jetsetilly/gopher2600/hardware/cpu"
rtest "github.com/jetsetilly/gopher2600/hardware/cpu/registers/test"
"github.com/jetsetilly/gopher2600/test"
)
type testMem struct {
internal []uint8
}
func newTestMem() *testMem {
return &testMem{
internal: make([]uint8, 0x10000),
}
}
func (mem *testMem) putInstructions(origin uint16, bytes ...uint8) uint16 {
for i, b := range bytes {
_ = mem.Write(uint16(i)+origin, b)
}
return origin + uint16(len(bytes))
}
func (mem testMem) assert(t *testing.T, address uint16, value uint8) {
t.Helper()
d, _ := mem.Read(address)
if d != value {
t.Errorf("memory assertion failed (%v - wanted %v at address %04x", d, value, address)
}
}
// Clear sets all bytes in memory to zero.
func (mem *testMem) Clear() {
for i := 0; i < len(mem.internal); i++ {
mem.internal[i] = 0
}
}
func (mem testMem) Read(address uint16) (uint8, error) {
return mem.internal[address], nil
}
func (mem testMem) ReadZeroPage(address uint8) (uint8, error) {
return mem.Read(uint16(address))
}
func (mem *testMem) Write(address uint16, data uint8) error {
mem.internal[address] = data
return nil
}
func step(t *testing.T, mc *cpu.CPU) {
t.Helper()
err := mc.ExecuteInstruction(cpu.NilCycleCallback)
if err != nil {
fmt.Println(mc.LastResult.Defn)
t.Fatal(err)
}
err = mc.LastResult.IsValid()
if err != nil {
t.Fatal(err)
}
}
func testStatusInstructions(t *testing.T, mc *cpu.CPU, mem *testMem) {
var origin uint16
mem.Clear()
mc.Reset()
// SEC; CLC; CLI; SEI; SED; CLD; CLV
origin = mem.putInstructions(origin, 0x38, 0x18, 0x58, 0x78, 0xf8, 0xd8, 0xb8)
step(t, mc) // SEC
rtest.EquateRegisters(t, mc.Status, "sv-BdiZC")
step(t, mc) // CLC
rtest.EquateRegisters(t, mc.Status, "sv-BdiZc")
step(t, mc) // CLI
rtest.EquateRegisters(t, mc.Status, "sv-BdiZc")
step(t, mc) // SEI
rtest.EquateRegisters(t, mc.Status, "sv-BdIZc")
step(t, mc) // SED
rtest.EquateRegisters(t, mc.Status, "sv-BDIZc")
step(t, mc) // CLD
rtest.EquateRegisters(t, mc.Status, "sv-BdIZc")
step(t, mc) // CLV
rtest.EquateRegisters(t, mc.Status, "sv-BdIZc")
// PHP; PLP
_ = mem.putInstructions(origin, 0x08, 0x28)
step(t, mc) // PHP
rtest.EquateRegisters(t, mc.Status, "sv-BdIZc")
rtest.EquateRegisters(t, mc.SP.Register, 254)
// mangle status register
mc.Status.Sign = true
mc.Status.Overflow = true
mc.Status.Break = false
// restore status register
step(t, mc) // PLP
rtest.EquateRegisters(t, mc.SP.Register, 255)
// break flag is always set on PLP
rtest.EquateRegisters(t, mc.Status, "sv-BdIZc")
}
func testRegsiterArithmetic(t *testing.T, mc *cpu.CPU, mem *testMem) {
var origin uint16
mem.Clear()
mc.Reset()
// LDA immediate; ADC immediate
origin = mem.putInstructions(origin, 0xa9, 1, 0x69, 10)
step(t, mc) // LDA #1
step(t, mc) // ADC #10
rtest.EquateRegisters(t, mc.A, 11)
// SEC; SBC immediate
_ = mem.putInstructions(origin, 0x38, 0xe9, 8)
step(t, mc) // SEC
step(t, mc) // SBC #8
rtest.EquateRegisters(t, mc.A, 3)
}
func testRegsiterBitwiseInstructions(t *testing.T, mc *cpu.CPU, mem *testMem) {
var origin uint16
mem.Clear()
mc.Reset()
// ORA immediate; EOR immediate; AND immediate
origin = mem.putInstructions(origin, 0x09, 0xff, 0x49, 0xf0, 0x29, 0x01)
rtest.EquateRegisters(t, mc.A, 0)
step(t, mc) // ORA #$FF
rtest.EquateRegisters(t, mc.A, 255)
step(t, mc) // EOR #$F0
rtest.EquateRegisters(t, mc.A, 15)
step(t, mc) // AND #$01
rtest.EquateRegisters(t, mc.A, 1)
// ASL implied; LSR implied; LSR implied
origin = mem.putInstructions(origin, 0x0a, 0x4a, 0x4a)
step(t, mc) // ASL
rtest.EquateRegisters(t, mc.A, 2)
rtest.EquateRegisters(t, mc.Status, "sv-Bdizc")
step(t, mc) // LSR
rtest.EquateRegisters(t, mc.A, 1)
rtest.EquateRegisters(t, mc.Status, "sv-Bdizc")
step(t, mc) // LSR
rtest.EquateRegisters(t, mc.A, 0)
rtest.EquateRegisters(t, mc.Status, "sv-BdiZC")
// ROL implied; ROR implied; ROR implied; ROR implied
_ = mem.putInstructions(origin, 0x2a, 0x6a, 0x6a, 0x6a)
step(t, mc) // ROL
rtest.EquateRegisters(t, mc.A, 1)
rtest.EquateRegisters(t, mc.Status, "sv-Bdizc")
step(t, mc) // ROR
rtest.EquateRegisters(t, mc.A, 0)
rtest.EquateRegisters(t, mc.Status, "sv-BdiZC")
step(t, mc) // ROR
rtest.EquateRegisters(t, mc.A, 128)
rtest.EquateRegisters(t, mc.Status, "Sv-Bdizc")
step(t, mc) // ROR
rtest.EquateRegisters(t, mc.A, 64)
rtest.EquateRegisters(t, mc.Status, "sv-Bdizc")
}
func testImmediateImplied(t *testing.T, mc *cpu.CPU, mem *testMem) {
var origin uint16
mem.Clear()
mc.Reset()
// LDX immediate; INX; DEX
origin = mem.putInstructions(origin, 0xa2, 5, 0xe8, 0xca)
step(t, mc) // LDX #5
rtest.EquateRegisters(t, mc.X, 5)
step(t, mc) // INX
rtest.EquateRegisters(t, mc.X, 6)
step(t, mc) // DEX
rtest.EquateRegisters(t, mc.X, 5)
rtest.EquateRegisters(t, mc.Status, "sv-Bdizc")
// PHA; LDA immediate; PLA
origin = mem.putInstructions(origin, 0xa9, 5, 0x48, 0xa9, 0, 0x68)
step(t, mc) // LDA #5
step(t, mc) // PHA
rtest.EquateRegisters(t, mc.SP.Register, 254)
step(t, mc) // LDA #0
rtest.EquateRegisters(t, mc.A, 0)
rtest.EquateRegisters(t, mc.Status, "sv-BdiZc")
step(t, mc) // PLA
rtest.EquateRegisters(t, mc.A, 5)
// TAX; TAY; LDX immediate; TXA; LDY immediate; TYA; INY; DEY
origin = mem.putInstructions(origin, 0xaa, 0xa8, 0xa2, 1, 0x8a, 0xa0, 2, 0x98, 0xc8, 0x88)
step(t, mc) // TAX
rtest.EquateRegisters(t, mc.X, 5)
step(t, mc) // TAY
rtest.EquateRegisters(t, mc.Y, 5)
step(t, mc) // LDX #1
step(t, mc) // TXA
rtest.EquateRegisters(t, mc.A, 1)
step(t, mc) // LDY #2
step(t, mc) // TYA
rtest.EquateRegisters(t, mc.A, 2)
step(t, mc) // INY
rtest.EquateRegisters(t, mc.Y, 3)
step(t, mc) // DEY
rtest.EquateRegisters(t, mc.Y, 2)
// TSX; LDX immediate; TXS
_ = mem.putInstructions(origin, 0xba, 0xa2, 100, 0x9a)
step(t, mc) // TSX
rtest.EquateRegisters(t, mc.X, 255)
step(t, mc) // LDX #100
step(t, mc) // TXS
rtest.EquateRegisters(t, mc.SP.Register, 100)
}
func testOtherAddressingModes(t *testing.T, mc *cpu.CPU, mem *testMem) {
var origin uint16
mem.Clear()
mc.Reset()
mem.putInstructions(0x0100, 123, 43)
mem.putInstructions(0x01a2, 47)
// LDA zero page
origin = mem.putInstructions(origin, 0xa5, 0x00)
step(t, mc) // LDA $00
rtest.EquateRegisters(t, mc.A, 0xa5)
// LDX immediate; LDA zero page,X
origin = mem.putInstructions(origin, 0xa2, 1, 0xb5, 0x01)
step(t, mc) // LDX #1
step(t, mc) // LDA 01,X
rtest.EquateRegisters(t, mc.A, 0xa2)
// LDY immediate; LDX zero page,Y
origin = mem.putInstructions(origin, 0xa0, 3, 0xb6, 0x01)
step(t, mc) // LDX #3
step(t, mc) // LDA 01,Y
rtest.EquateRegisters(t, mc.A, 0xa2)
// LDA absolute
origin = mem.putInstructions(origin, 0xad, 0x00, 0x01)
step(t, mc) // LDA $0100
rtest.EquateRegisters(t, mc.A, 123)
// LDX immediate; LDA absolute,X
origin = mem.putInstructions(origin, 0xa2, 1, 0xbd, 0x01, 0x00)
step(t, mc) // LDX #1
rtest.EquateRegisters(t, mc.X, 1)
step(t, mc) // LDA $0001,X
rtest.EquateRegisters(t, mc.A, 0xa2)
// LDY immediate; LDA absolute,Y
origin = mem.putInstructions(origin, 0xa0, 1, 0xb9, 0x01, 0x00)
step(t, mc) // LDY #1
rtest.EquateRegisters(t, mc.X, 1)
step(t, mc) // LDA $0001,Y
rtest.EquateRegisters(t, mc.A, 0xa2)
// pre-indexed indirect
// X = 1
// INX; LDA (Indirect, X)
origin = mem.putInstructions(origin, 0xe8, 0xa1, 0x0b)
step(t, mc) // INX (x equals 2)
step(t, mc) // LDA (0x0b,X)
// post-indexed indirect (see below)
// pre-indexed indirect (with wraparound)
// X = 1
// INX; LDA (Indirect, X)
origin = mem.putInstructions(origin, 0xe8, 0xa1, 0xff)
step(t, mc) // INX (x equals 2)
step(t, mc) // LDA (0xff,X)
rtest.EquateRegisters(t, mc.A, 47)
// post-indexed indirect (with page-fault)
// Y = 1
// INY; INY; LDA (Indirect), Y
mem.putInstructions(0xc0, 0xfd, 0x00)
_ = mem.putInstructions(origin, 0xc8, 0xc8, 0xb1, 0xc0)
step(t, mc) // INY (y = 2)
step(t, mc) // INY (y = 2)
step(t, mc) // LDA (0x0b),Y
rtest.EquateRegisters(t, mc.A, 123)
if mc.LastResult.PageFault != true {
t.Errorf("expected page-fault")
}
}
func testPostIndexedIndirect(t *testing.T, mc *cpu.CPU, mem *testMem) {
var origin uint16
mem.Clear()
mc.Reset()
mem.putInstructions(0xee00, 0x01, 0x02, 0x03)
mc.PC.Load(0x04)
origin = mem.putInstructions(origin, 0x01, 0xee, 0xfe, 0xfd)
origin = mem.putInstructions(origin, 0xa0, 0x01)
step(t, mc)
rtest.EquateRegisters(t, mc.Y, 1)
_ = mem.putInstructions(origin, 0xb1, 0x00)
step(t, mc)
rtest.EquateRegisters(t, mc.A, 0x03)
}
func testStorageInstructions(t *testing.T, mc *cpu.CPU, mem *testMem) {
var origin uint16
mem.Clear()
mc.Reset()
// LDA immediate; STA absolute
origin = mem.putInstructions(origin, 0xa9, 0x54, 0x8d, 0x00, 0x01)
step(t, mc) // LDA 0x54
step(t, mc) // STA 0x0100
mem.assert(t, 0x0100, 0x54)
// LDX immediate; STX absolute
origin = mem.putInstructions(origin, 0xa2, 0x63, 0x8e, 0x01, 0x01)
step(t, mc) // LDX 0x63
step(t, mc) // STX 0x0101
mem.assert(t, 0x0101, 0x63)
// LDY immediate; STY absolute
origin = mem.putInstructions(origin, 0xa0, 0x72, 0x8c, 0x02, 0x01)
step(t, mc) // LDY 0x72
step(t, mc) // STY 0x0102
mem.assert(t, 0x0101, 0x63)
// INC zero page
origin = mem.putInstructions(origin, 0xe6, 0x01)
step(t, mc) // INC $01
mem.assert(t, 0x01, 0x55)
// DEC absolute
_ = mem.putInstructions(origin, 0xce, 0x00, 0x01)
step(t, mc) // DEC 0x0100
mem.assert(t, 0x0100, 0x53)
}
func testBranching(t *testing.T, mc *cpu.CPU, mem *testMem) {
var origin uint16
mem.Clear()
mc.Reset()
origin = 0
mem.Clear()
mc.Reset()
_ = mem.putInstructions(origin, 0x10, 0x10)
step(t, mc) // BPL $10
rtest.EquateRegisters(t, mc.PC, 0x12)
origin = 0
mem.Clear()
mc.Reset()
_ = mem.putInstructions(origin, 0x50, 0x10)
step(t, mc) // BVC $10
rtest.EquateRegisters(t, mc.PC, 0x12)
origin = 0
mem.Clear()
mc.Reset()
_ = mem.putInstructions(origin, 0x90, 0x10)
step(t, mc) // BCC $10
rtest.EquateRegisters(t, mc.PC, 0x12)
origin = 0
mem.Clear()
mc.Reset()
_ = mem.putInstructions(origin, 0x38, 0xb0, 0x10)
step(t, mc) // SEC
step(t, mc) // BCS $10
rtest.EquateRegisters(t, mc.PC, 0x13)
origin = 0
mem.Clear()
mc.Reset()
_ = mem.putInstructions(origin, 0xe8, 0xd0, 0x10)
step(t, mc) // INX
step(t, mc) // BNE $10
rtest.EquateRegisters(t, mc.PC, 0x13)
origin = 0
mem.Clear()
mc.Reset()
_ = mem.putInstructions(origin, 0xca, 0x30, 0x10)
step(t, mc) // DEX
step(t, mc) // BMI $10
rtest.EquateRegisters(t, mc.PC, 0x13)
_ = mem.putInstructions(0x13, 0xe8, 0xf0, 0x10)
step(t, mc) // INX
step(t, mc) // BEQ $10
rtest.EquateRegisters(t, mc.PC, 0x26)
origin = 0
mem.Clear()
mc.Reset()
// fudging overflow test
mc.Status.Overflow = true
_ = mem.putInstructions(origin, 0x70, 0x10)
step(t, mc) // BVS $10
rtest.EquateRegisters(t, mc.PC, 0x12)
}
func testBranchingBackwards(t *testing.T, mc *cpu.CPU, mem *testMem) {
var origin uint16
mem.Clear()
mc.Reset()
mem.Clear()
mc.Reset()
origin = 0x20
err := mc.LoadPC(0x20)
test.ExpectSuccess(t, err)
// BPL backwards
_ = mem.putInstructions(origin, 0x10, 0xfd)
step(t, mc) // BPL $FF
rtest.EquateRegisters(t, mc.PC, 0x1f)
// BVS backwards
origin = 0x20
err = mc.LoadPC(0x20)
test.ExpectSuccess(t, err)
mc.Status.Overflow = true
_ = mem.putInstructions(origin, 0x70, 0xfd)
step(t, mc) // BVS $FF
rtest.EquateRegisters(t, mc.PC, 0x1f)
}
func testBranchingPageFaults(t *testing.T, mc *cpu.CPU, mem *testMem) {
var origin uint16
mem.Clear()
mc.Reset()
// BNE backwards - with PC wrap (causing a page fault)
origin = 0x20
err := mc.LoadPC(0x20)
test.ExpectSuccess(t, err)
mc.Status.Zero = false
_ = mem.putInstructions(origin, 0xd0, 0x80)
step(t, mc) // BNE $F0
rtest.EquateRegisters(t, mc.PC, 0xffa2)
// pagefault flag should be set
if !mc.LastResult.PageFault {
t.Errorf("expected pagefault on branch")
}
// number of cycles should be 4 instead of 2
// +1 for failed branch test (causing PC to jump)
// +1 for page fault
if mc.LastResult.Cycles != 4 {
t.Errorf("expected pagefault on branch")
}
}
func testJumps(t *testing.T, mc *cpu.CPU, mem *testMem) {
var origin uint16
mem.Clear()
mc.Reset()
// JMP absolute
_ = mem.putInstructions(origin, 0x4c, 0x00, 0x01)
step(t, mc) // JMP $100
rtest.EquateRegisters(t, mc.PC, 0x0100)
// JMP indirect
origin = 0
mem.Clear()
mc.Reset()
mem.putInstructions(0x0050, 0x49, 0x01)
_ = mem.putInstructions(origin, 0x6c, 0x50, 0x00)
step(t, mc) // JMP ($50)
rtest.EquateRegisters(t, mc.PC, 0x0149)
// JMP indirect (bug)
origin = 0
mem.Clear()
mc.Reset()
mem.putInstructions(0x01FF, 0x03)
mem.putInstructions(0x0100, 0x00)
_ = mem.putInstructions(origin, 0x6c, 0xFF, 0x01)
step(t, mc) // JMP ($0x01FF)
rtest.EquateRegisters(t, mc.PC, 0x0003)
}
func testComparisonInstructions(t *testing.T, mc *cpu.CPU, mem *testMem) {
var origin uint16
mem.Clear()
mc.Reset()
// CMP immediate (equality)
origin = mem.putInstructions(origin, 0xc9, 0x00)
step(t, mc) // CMP $00
rtest.EquateRegisters(t, mc.Status, "sv-BdiZC")
// LDA immediate; CMP immediate
origin = mem.putInstructions(origin, 0xa9, 0xf6, 0xc9, 0x18)
step(t, mc) // LDA $F6
step(t, mc) // CMP $10
rtest.EquateRegisters(t, mc.Status, "Sv-BdizC")
// LDX immediate; CMP immediate
origin = mem.putInstructions(origin, 0xa2, 0xf6, 0xe0, 0x18)
step(t, mc) // LDX $F6
step(t, mc) // CMP $10
rtest.EquateRegisters(t, mc.Status, "Sv-BdizC")
// LDY immediate; CMP immediate
origin = mem.putInstructions(origin, 0xa0, 0xf6, 0xc0, 0x18)
step(t, mc) // LDY $F6
step(t, mc) // CMP $10
rtest.EquateRegisters(t, mc.Status, "Sv-BdizC")
// LDA immediate; CMP immediate
origin = mem.putInstructions(origin, 0xa9, 0x18, 0xc9, 0xf6)
step(t, mc) // LDA $F6
step(t, mc) // CMP $10
rtest.EquateRegisters(t, mc.Status, "sv-Bdizc")
// BIT zero page
origin = mem.putInstructions(origin, 0x24, 0x01)
step(t, mc) // BIT $01
rtest.EquateRegisters(t, mc.Status, "sv-BdiZc")
// BIT immediate
_ = mem.putInstructions(origin, 0x24, 0x01)
step(t, mc) // BIT $01
rtest.EquateRegisters(t, mc.Status, "sv-BdiZc")
}
func testSubroutineInstructions(t *testing.T, mc *cpu.CPU, mem *testMem) {
var origin uint16
mem.Clear()
mc.Reset()
// JSR absolute
_ = mem.putInstructions(origin, 0x20, 0x00, 0x01)
step(t, mc) // JSR $0100
rtest.EquateRegisters(t, mc.PC, 0x0100)
mem.assert(t, 0x01ff, 0x00)
mem.assert(t, 0x01fe, 0x02)
rtest.EquateRegisters(t, mc.SP.Register, 253)
_ = mem.putInstructions(0x100, 0x60)
step(t, mc) // RTS
rtest.EquateRegisters(t, mc.PC, 0x0003)
mem.assert(t, 0x01ff, 0x00)
mem.assert(t, 0x01fe, 0x02)
rtest.EquateRegisters(t, mc.SP.Register, 255)
}
func testDecimalMode(t *testing.T, mc *cpu.CPU, mem *testMem) {
var origin uint16
mem.Clear()
mc.Reset()
_ = mem.putInstructions(origin, 0xf8, 0xa9, 0x20, 0x38, 0xe9, 0x01)
step(t, mc) // SED
step(t, mc) // LDA #$20
step(t, mc) // SEC
step(t, mc) // SBC #$00
rtest.EquateRegisters(t, mc.A, 0x19)
}
func testBRK(t *testing.T, mc *cpu.CPU, mem *testMem) {
var origin uint16
mem.Clear()
mc.Reset()
_ = mem.putInstructions(origin, 0x69, 0x01, 0x00)
step(t, mc) // ADC #$01
rtest.EquateRegisters(t, mc.PC, 0x02)
rtest.EquateRegisters(t, mc.A, 0x01)
step(t, mc) // BRK
rtest.EquateRegisters(t, mc.PC, 0x00)
}
func testKIL(t *testing.T, mc *cpu.CPU, mem *testMem) {
var origin uint16
mem.Clear()
mc.Reset()
_ = mem.putInstructions(origin, 0x02, 0x69, 0x01)
step(t, mc) // KIL
rtest.EquateRegisters(t, mc.PC, 0x01)
step(t, mc) // ADC #$01
rtest.EquateRegisters(t, mc.PC, 0x01)
rtest.EquateRegisters(t, mc.A, 0x00)
}
func TestCPU(t *testing.T) {
mem := newTestMem()
mc := cpu.NewCPU(nil, mem)
testStatusInstructions(t, mc, mem)
testRegsiterArithmetic(t, mc, mem)
testRegsiterBitwiseInstructions(t, mc, mem)
testImmediateImplied(t, mc, mem)
testOtherAddressingModes(t, mc, mem)
testPostIndexedIndirect(t, mc, mem)
testStorageInstructions(t, mc, mem)
testBranching(t, mc, mem)
testBranchingBackwards(t, mc, mem)
testBranchingPageFaults(t, mc, mem)
testJumps(t, mc, mem)
testComparisonInstructions(t, mc, mem)
testSubroutineInstructions(t, mc, mem)
testDecimalMode(t, mc, mem)
testBRK(t, mc, mem)
testKIL(t, mc, mem)
}