aboutsummaryrefslogtreecommitdiff
path: root/tests/cpu_test.go
diff options
context:
space:
mode:
authorcrecher <crecher@crecher.net>2026-10-12 04:07:09 +0200
committercrecher <crecher@crecher.net>2026-10-12 04:07:09 +0200
commit8b0292cccf768efdd5295fe0b662488cbc671079 (patch)
treef2896047a2328d8aea9a7f7815a6cf8d9c0636ea /tests/cpu_test.go
parent7775f9ad5407f2be4b03c300d1db4c745fda2f69 (diff)
downloadgones-8b0292cccf768efdd5295fe0b662488cbc671079.tar.gz
gones-8b0292cccf768efdd5295fe0b662488cbc671079.zip
added cart package, more tests, tests are now at ./tests and added .gitignore
Diffstat (limited to 'tests/cpu_test.go')
-rw-r--r--tests/cpu_test.go237
1 files changed, 237 insertions, 0 deletions
diff --git a/tests/cpu_test.go b/tests/cpu_test.go
new file mode 100644
index 0000000..87743b1
--- /dev/null
+++ b/tests/cpu_test.go
@@ -0,0 +1,237 @@
+package tests
+
+import (
+ "testing"
+
+ "crecher.net/gones/v2/cpu"
+)
+
+type ramBus struct {
+ mem [65536]uint8
+}
+
+func (b *ramBus) Read(addr uint16) uint8 { return b.mem[addr] }
+func (b *ramBus) Write(addr uint16, v uint8) { b.mem[addr] = v }
+
+func has(c *cpu.CPU, flag uint8) bool { return c.Status&flag != 0 }
+
+func newTestCPU(prog []uint8) (*cpu.CPU, *ramBus) {
+ bus := &ramBus{}
+ copy(bus.mem[0x8000:], prog)
+ bus.mem[0xFFFC] = 0x00
+ bus.mem[0xFFFD] = 0x80
+ c := cpu.NewCPU(bus)
+ c.Reset()
+ return c, bus
+}
+
+func TestResetVector(t *testing.T) {
+ c, _ := newTestCPU([]uint8{0xEA})
+ if c.PC != 0x8000 {
+ t.Fatalf("PC = 0x%04X, want 0x8000", c.PC)
+ }
+ if c.SP != 0xFD {
+ t.Fatalf("SP = 0x%02X, want 0xFD", c.SP)
+ }
+}
+
+func TestLDAImmediate(t *testing.T) {
+ c, _ := newTestCPU([]uint8{0xA9, 0x42})
+ cycles := c.Step()
+ if c.A != 0x42 {
+ t.Fatalf("A = 0x%02X, want 0x42", c.A)
+ }
+ if cycles != 2 {
+ t.Fatalf("cycles = %d, want 2", cycles)
+ }
+ if has(c, cpu.FlagZ) || has(c, cpu.FlagN) {
+ t.Fatalf("unexpected flags: 0x%02X", c.Status)
+ }
+}
+
+func TestLDAZeroAndNegativeFlags(t *testing.T) {
+ c, _ := newTestCPU([]uint8{0xA9, 0x00})
+ c.Step()
+ if !has(c, cpu.FlagZ) {
+ t.Fatalf("Z flag should be set for A=0")
+ }
+
+ c, _ = newTestCPU([]uint8{0xA9, 0x80})
+ c.Step()
+ if !has(c, cpu.FlagN) {
+ t.Fatalf("N flag should be set for A=0x80")
+ }
+}
+
+func TestSTAAbsolute(t *testing.T) {
+ c, bus := newTestCPU([]uint8{0xA9, 0x55, 0x8D, 0x00, 0x02})
+ c.Step()
+ c.Step()
+ if bus.mem[0x0200] != 0x55 {
+ t.Fatalf("mem[0x0200] = 0x%02X, want 0x55", bus.mem[0x0200])
+ }
+}
+
+func TestAbsoluteXPageCross(t *testing.T) {
+ // 0x80FF + X(1): page cross, 5 cycles
+ c, bus := newTestCPU([]uint8{0xA2, 0x01, 0xBD, 0xFF, 0x80})
+ bus.mem[0x8100] = 0x99
+ c.Step()
+ cycles := c.Step()
+ if c.A != 0x99 {
+ t.Fatalf("A = 0x%02X, want 0x99", c.A)
+ }
+ if cycles != 5 {
+ t.Fatalf("cycles = %d, want 5 (4 base + 1 page cross)", cycles)
+ }
+}
+
+func TestAdcCarryAndOverflow(t *testing.T) {
+ // 0x50 + 0x50 = 0xA0, V=1, C=0
+ c, _ := newTestCPU([]uint8{0xA9, 0x50, 0x69, 0x50})
+ c.Step()
+ c.Step()
+ if c.A != 0xA0 {
+ t.Fatalf("A = 0x%02X, want 0xA0", c.A)
+ }
+ if !has(c, cpu.FlagV) {
+ t.Fatalf("V flag should be set")
+ }
+ if has(c, cpu.FlagC) {
+ t.Fatalf("C flag should be clear")
+ }
+
+ // 0xFF + 0x01 = 0x00, C=1, V=0
+ c, _ = newTestCPU([]uint8{0xA9, 0xFF, 0x69, 0x01})
+ c.Step()
+ c.Step()
+ if c.A != 0x00 {
+ t.Fatalf("A = 0x%02X, want 0x00", c.A)
+ }
+ if !has(c, cpu.FlagC) {
+ t.Fatalf("C flag should be set")
+ }
+ if !has(c, cpu.FlagZ) {
+ t.Fatalf("Z flag should be set")
+ }
+ if has(c, cpu.FlagV) {
+ t.Fatalf("V flag should be clear")
+ }
+}
+
+func TestSbcBorrow(t *testing.T) {
+ // 0x10 - 0x01 = 0x0F, C=1
+ c, _ := newTestCPU([]uint8{0x38, 0xA9, 0x10, 0xE9, 0x01})
+ c.Step()
+ c.Step()
+ c.Step()
+ if c.A != 0x0F {
+ t.Fatalf("A = 0x%02X, want 0x0F", c.A)
+ }
+ if !has(c, cpu.FlagC) {
+ t.Fatalf("C flag should be set (no borrow)")
+ }
+}
+
+func TestBranchTakenAndPageCross(t *testing.T) {
+ // beq taken, same page: 3 cycles
+ c, _ := newTestCPU([]uint8{0xA9, 0x00, 0xF0, 0x02, 0xEA, 0xEA, 0xEA})
+ c.Step()
+ cycles := c.Step()
+ if cycles != 3 {
+ t.Fatalf("cycles = %d, want 3 (2 base + 1 taken)", cycles)
+ }
+ if c.PC != 0x8006 {
+ t.Fatalf("PC = 0x%04X, want 0x8006", c.PC)
+ }
+}
+
+func TestJsrRts(t *testing.T) {
+ prog := []uint8{0x20, 0x04, 0x80, 0xEA, 0x60}
+ c, _ := newTestCPU(prog)
+ c.Step()
+ if c.PC != 0x8004 {
+ t.Fatalf("PC after JSR = 0x%04X, want 0x8004", c.PC)
+ }
+ c.Step()
+ if c.PC != 0x8003 {
+ t.Fatalf("PC after RTS = 0x%04X, want 0x8003", c.PC)
+ }
+}
+
+func TestStackPushPullRoundTrip(t *testing.T) {
+ c, _ := newTestCPU([]uint8{0xA9, 0x7E, 0x48, 0xA9, 0x00, 0x68})
+ c.Step()
+ c.Step()
+ c.Step()
+ c.Step()
+ if c.A != 0x7E {
+ t.Fatalf("A after PLA = 0x%02X, want 0x7E", c.A)
+ }
+}
+
+func TestPhpPlpPreservesFlags(t *testing.T) {
+ c, _ := newTestCPU([]uint8{0x38, 0x08, 0x18, 0x28})
+ c.Step()
+ c.Step()
+ c.Step()
+ if has(c, cpu.FlagC) {
+ t.Fatalf("C flag should be clear after CLC")
+ }
+ c.Step()
+ if !has(c, cpu.FlagC) {
+ t.Fatalf("C flag should be restored by PLP")
+ }
+}
+
+func TestIndirectJmpPageBug(t *testing.T) {
+ // jmp ($30FF) reads high byte from $3000
+ c, bus := newTestCPU([]uint8{0x6C, 0xFF, 0x30})
+ bus.mem[0x30FF] = 0x34
+ bus.mem[0x3000] = 0x12
+ bus.mem[0x3100] = 0x99
+ c.Step()
+ if c.PC != 0x1234 {
+ t.Fatalf("PC = 0x%04X, want 0x1234", c.PC)
+ }
+}
+
+func TestNmiPushesStateAndJumps(t *testing.T) {
+ c, bus := newTestCPU([]uint8{0xEA})
+ bus.mem[0xFFFA] = 0x00
+ bus.mem[0xFFFB] = 0x90
+ pcBefore := c.PC
+ spBefore := c.SP
+ c.NMI()
+ if c.PC != 0x9000 {
+ t.Fatalf("PC after NMI = 0x%04X, want 0x9000", c.PC)
+ }
+ if c.SP != spBefore-3 {
+ t.Fatalf("SP after NMI = 0x%02X, want 0x%02X", c.SP, spBefore-3)
+ }
+ stack := func(sp uint8) uint16 { return uint16(bus.mem[0x0100+uint16(sp)]) }
+ pc := stack(spBefore)<<8 | stack(spBefore-1)
+ if pc != pcBefore {
+ t.Fatalf("pushed PC = 0x%04X, want 0x%04X", pc, pcBefore)
+ }
+}
+
+func TestCompareFlags(t *testing.T) {
+ // Z=1, C=1, N=0
+ c, _ := newTestCPU([]uint8{0xA9, 0x10, 0xC9, 0x10})
+ c.Step()
+ c.Step()
+ if !has(c, cpu.FlagZ) || !has(c, cpu.FlagC) || has(c, cpu.FlagN) {
+ t.Fatalf("flags = 0x%02X, want Z and C set, N clear", c.Status)
+ }
+}
+
+func TestUnimplementedOpcodePanics(t *testing.T) {
+ defer func() {
+ if r := recover(); r == nil {
+ t.Fatalf("expected panic for illegal opcode")
+ }
+ }()
+ c, _ := newTestCPU([]uint8{0x02})
+ c.Step()
+}