diff options
| -rw-r--r-- | .gitignore | 18 | ||||
| -rw-r--r-- | README.md | 54 | ||||
| -rw-r--r-- | cart/cart.go | 83 | ||||
| -rw-r--r-- | cart/mapper.go | 30 | ||||
| -rw-r--r-- | cart/nrom.go | 47 | ||||
| -rw-r--r-- | tests/cart_test.go | 115 | ||||
| -rw-r--r-- | tests/cpu_test.go (renamed from cpu/cpu_test.go) | 88 | ||||
| -rw-r--r-- | tests/nestest_test.go (renamed from cpu/nestest_test.go) | 78 | ||||
| -rw-r--r-- | tests/testdata/nestest.log (renamed from cpu/testdata/nestest.log) | 0 | ||||
| -rw-r--r-- | tests/testdata/nestest.nes (renamed from cpu/testdata/nestest.nes) | bin | 24592 -> 24592 bytes |
10 files changed, 419 insertions, 94 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9006382 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +*.sav + +*.nes +!tests/testdata/nestest.nes + +/gones +/cmd/gones/gones + +*.test +*.out +*.prof + +.idea/ +.vscode + +# fuck you emacs <3 +*~ +.#*
\ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9fa3119 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +nes emulator in go + +TODO: +emulator base +- [x] cpu passing nestest +- [x] cart package (iNES parser, mapper 0) + +system bus (nes package) +- [ ] Bus type: 2KB RAM mirrored through $1FFF, PPU regs $2000-$3FFF (mirrored every 8), APU/IO $4000-$4017, cart $4020+ +- [ ] OAM DMA at $4014 (stalls CPU 513/514 cycles) +- [ ] reuse it in nestest_test.go instead of the test-only bus +- [ ] CPU stall hook and proper IRQ/NMI line (polled between instructions) + +ppu, background +- [ ] registers $2000-$2007 and v/t/x/w latch logic +- [ ] nametables with mirroring, pattern tables, palettes, buffered $2007 reads +- [ ] 341 dots x 262 lines timing, 3 dots per CPU cycle +- [ ] vblank flag and NMI +- [ ] 256x240 framebuffer and RGB palette + +frontend and input +- [ ] cmd/gones with Ebitengine, one frame per 1/60s +- [ ] controller $4016/$4017 strobe and shift register, keyboard mapping + +ppu, sprites +- [ ] OAM, per-scanline evaluation, 8-sprite limit, 8x8 and 8x16, flip, priority +- [ ] sprite-0 hit + +more mappers +- [ ] MMC1 (1), UxROM (2), CNROM (3), AxROM (7) +- [ ] MMC3 (4) with scanline IRQ +- [ ] battery-backed save files + +apu +- [ ] pulse x2, triangle, noise, DMC, frame counter, mixer +- [ ] resampling and audio output + +accuracy +- [ ] missing unofficial NOPs +- [ ] run blargg's CPU, PPU, APU and MMC3 test ROMs +- [ ] decide whether to go cycle-stepped + +polish +- [ ] save states, pause/reset, fullscreen, gamepad +- [ ] headless mode with framebuffer-hash regression tests + + +testing: +run "go test ./..." to run all test, "go test ./... -v" for verbose + +disclaimer: +not affiliated with or endorsed by nintendo. no commercial roms are included, bring your own. (i dont want to get shot by nintendo :D) +tests/testdata/nestest.nes and nestest.log are by kevin horton (kevtris), included for testing only. +provided as is, without warranty. diff --git a/cart/cart.go b/cart/cart.go new file mode 100644 index 0000000..2438c1b --- /dev/null +++ b/cart/cart.go @@ -0,0 +1,83 @@ +package cart + +import ( + "bytes" + "errors" + "fmt" + "os" +) + +type Cartridge struct { + PRG []uint8 + CHR []uint8 + CHRIsRAM bool + MapperID uint8 + Mirroring Mirroring + Battery bool + + Mapper Mapper +} + +func Load(path string) (*Cartridge, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + return Parse(data) +} + +func Parse(data []byte) (*Cartridge, error) { + if len(data) < 16 || !bytes.Equal(data[:4], []byte("NES\x1A")) { + return nil, errors.New("cart: not an iNES file") + } + + prgSize := int(data[4]) * 16 * 1024 + chrSize := int(data[5]) * 8 * 1024 + flags6, flags7 := data[6], data[7] + + isNES2 := flags7&0x0C == 0x08 + mapperID := flags6 >> 4 + if isNES2 || bytes.Equal(data[12:16], []byte{0, 0, 0, 0}) { + mapperID |= flags7 & 0xF0 + } + + c := &Cartridge{ + MapperID: mapperID, + Battery: flags6&0x02 != 0, + } + + switch { + case flags6&0x08 != 0: + c.Mirroring = FourScreen + case flags6&0x01 != 0: + c.Mirroring = Vertical + default: + c.Mirroring = Horizontal + } + + offset := 16 + if flags6&0x04 != 0 { + offset += 512 + } + if len(data) < offset+prgSize+chrSize { + return nil, fmt.Errorf("cart: file truncated: need %d bytes, have %d", + offset+prgSize+chrSize, len(data)) + } + + c.PRG = data[offset : offset+prgSize] + offset += prgSize + + if chrSize == 0 { + c.CHR = make([]uint8, 8*1024) + c.CHRIsRAM = true + } else { + c.CHR = data[offset : offset+chrSize] + } + + m, err := newMapper(c) + if err != nil { + return nil, err + } + c.Mapper = m + return c, nil +} diff --git a/cart/mapper.go b/cart/mapper.go new file mode 100644 index 0000000..7ddd8a9 --- /dev/null +++ b/cart/mapper.go @@ -0,0 +1,30 @@ +package cart + +import "fmt" + +type Mirroring uint8 + +const ( + Horizontal Mirroring = iota + Vertical + FourScreen + SingleScreenLower + SingleScreenUpper +) + +type Mapper interface { + CPURead(addr uint16) uint8 + CPUWrite(addr uint16, v uint8) + PPURead(addr uint16) uint8 + PPUWrite(addr uint16, v uint8) + Mirroring() Mirroring +} + +func newMapper(c *Cartridge) (Mapper, error) { + switch c.MapperID { + case 0: + return newNROM(c), nil + default: + return nil, fmt.Errorf("cart: unsupported mapper %d", c.MapperID) + } +} diff --git a/cart/nrom.go b/cart/nrom.go new file mode 100644 index 0000000..3828c16 --- /dev/null +++ b/cart/nrom.go @@ -0,0 +1,47 @@ +package cart + +type nrom struct { + prg []uint8 + chr []uint8 + chrIsRAM bool + prgRAM [8 * 1024]uint8 + mirroring Mirroring +} + +func newNROM(c *Cartridge) *nrom { + return &nrom{ + prg: c.PRG, + chr: c.CHR, + chrIsRAM: c.CHRIsRAM, + mirroring: c.Mirroring, + } +} + +func (m *nrom) CPURead(addr uint16) uint8 { + switch { + case addr >= 0x8000: + return m.prg[int(addr-0x8000)%len(m.prg)] + case addr >= 0x6000: + return m.prgRAM[addr-0x6000] + default: + return 0 + } +} + +func (m *nrom) CPUWrite(addr uint16, v uint8) { + if addr >= 0x6000 && addr < 0x8000 { + m.prgRAM[addr-0x6000] = v + } +} + +func (m *nrom) PPURead(addr uint16) uint8 { + return m.chr[int(addr)%len(m.chr)] +} + +func (m *nrom) PPUWrite(addr uint16, v uint8) { + if m.chrIsRAM { + m.chr[int(addr)%len(m.chr)] = v + } +} + +func (m *nrom) Mirroring() Mirroring { return m.mirroring } diff --git a/tests/cart_test.go b/tests/cart_test.go new file mode 100644 index 0000000..5b41007 --- /dev/null +++ b/tests/cart_test.go @@ -0,0 +1,115 @@ +package tests + +import ( + "crecher.net/gones/v2/cart" + "os" + "testing" +) + +const nestestPath = "testdata/nestest.nes" + +func header(prgBanks, chrBanks, flags6, flags7 byte) []byte { + h := make([]byte, 16, 16+int(prgBanks)*16*1024+int(chrBanks)*8*1024) + copy(h, "NES\x1A") + h[4], h[5], h[6], h[7] = prgBanks, chrBanks, flags6, flags7 + h = append(h, make([]byte, int(prgBanks)*16*1024)...) + h = append(h, make([]byte, int(chrBanks)*8*1024)...) + return h +} + +func TestNestestHeader(t *testing.T) { + raw, err := os.ReadFile(nestestPath) + if err != nil { + t.Fatal(err) + } + t.Logf("header: % x", raw[:16]) + + c, err := cart.Parse(raw) + if err != nil { + t.Fatal(err) + } + + if want := (raw[6] >> 4) | (raw[7] & 0xF0); c.MapperID != want { + t.Errorf("MapperID = %d, want %d", c.MapperID, want) + } + if c.MapperID != 0 { + t.Errorf("nestest should be mapper 0, got %d", c.MapperID) + } + if want := int(raw[4]) * 16 * 1024; len(c.PRG) != want { + t.Errorf("len(PRG) = %d, want %d", len(c.PRG), want) + } + if want := int(raw[5]) * 8 * 1024; len(c.CHR) != want { + t.Errorf("len(CHR) = %d, want %d", len(c.CHR), want) + } + wantMirror := cart.Horizontal + if raw[6]&0x01 != 0 { + wantMirror = cart.Vertical + } + if c.Mirroring != wantMirror { + t.Errorf("Mirroring = %v, want %v", c.Mirroring, wantMirror) + } +} + +func TestLoadNestestResetVector(t *testing.T) { + c, err := cart.Load(nestestPath) + if err != nil { + t.Fatal(err) + } + lo := uint16(c.Mapper.CPURead(0xFFFC)) + hi := uint16(c.Mapper.CPURead(0xFFFD)) + if got := hi<<8 | lo; got != 0xC004 { + t.Errorf("reset vector = %#04x, want 0xC004", got) + } +} + +func TestParseErrors(t *testing.T) { + tests := []struct { + name string + data []byte + }{ + {"empty", nil}, + {"shorter than header", []byte("NES\x1A")}, + {"bad magic", append([]byte("XXXX"), make([]byte, 12)...)}, + {"truncated PRG", header(2, 1, 0, 0)[:16+1024]}, + {"unsupported mapper 1", header(1, 1, 0x10, 0)}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if _, err := cart.Parse(tt.data); err == nil { + t.Error("expected error, got nil") + } + }) + } +} + +func TestParseCHRRAM(t *testing.T) { + c, err := cart.Parse(header(1, 0, 0, 0)) + if err != nil { + t.Fatal(err) + } + if !c.CHRIsRAM || len(c.CHR) != 8*1024 { + t.Errorf("CHRIsRAM = %v, len(CHR) = %d; want true, 8192", c.CHRIsRAM, len(c.CHR)) + } + c.Mapper.PPUWrite(0x0010, 0xAB) + if got := c.Mapper.PPURead(0x0010); got != 0xAB { + t.Errorf("CHR-RAM readback = %#02x, want 0xAB", got) + } +} + +func TestParseFlags(t *testing.T) { + c, err := cart.Parse(header(1, 1, 0x03, 0)) + if err != nil { + t.Fatal(err) + } + if c.Mirroring != cart.Vertical || !c.Battery { + t.Errorf("Mirroring = %v, Battery = %v; want cart.Vertical, true", c.Mirroring, c.Battery) + } + + c, err = cart.Parse(header(1, 1, 0x09, 0)) + if err != nil { + t.Fatal(err) + } + if c.Mirroring != cart.FourScreen { + t.Errorf("Mirroring = %v, want cart.FourScreen", c.Mirroring) + } +} diff --git a/cpu/cpu_test.go b/tests/cpu_test.go index e4a32a6..87743b1 100644 --- a/cpu/cpu_test.go +++ b/tests/cpu_test.go @@ -1,6 +1,10 @@ -package cpu +package tests -import "testing" +import ( + "testing" + + "crecher.net/gones/v2/cpu" +) type ramBus struct { mem [65536]uint8 @@ -9,12 +13,14 @@ type ramBus struct { func (b *ramBus) Read(addr uint16) uint8 { return b.mem[addr] } func (b *ramBus) Write(addr uint16, v uint8) { b.mem[addr] = v } -func newTestCPU(prog []uint8) (*CPU, *ramBus) { +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 := NewCPU(bus) + c := cpu.NewCPU(bus) c.Reset() return c, bus } @@ -38,7 +44,7 @@ func TestLDAImmediate(t *testing.T) { if cycles != 2 { t.Fatalf("cycles = %d, want 2", cycles) } - if c.flag(FlagZ) || c.flag(FlagN) { + if has(c, cpu.FlagZ) || has(c, cpu.FlagN) { t.Fatalf("unexpected flags: 0x%02X", c.Status) } } @@ -46,19 +52,19 @@ func TestLDAImmediate(t *testing.T) { func TestLDAZeroAndNegativeFlags(t *testing.T) { c, _ := newTestCPU([]uint8{0xA9, 0x00}) c.Step() - if !c.flag(FlagZ) { + if !has(c, cpu.FlagZ) { t.Fatalf("Z flag should be set for A=0") } c, _ = newTestCPU([]uint8{0xA9, 0x80}) c.Step() - if !c.flag(FlagN) { + 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}) // lda #$55 ; sta $0200 + c, bus := newTestCPU([]uint8{0xA9, 0x55, 0x8D, 0x00, 0x02}) c.Step() c.Step() if bus.mem[0x0200] != 0x55 { @@ -67,10 +73,10 @@ func TestSTAAbsolute(t *testing.T) { } func TestAbsoluteXPageCross(t *testing.T) { - // lda $80ff,X with X=1 crosses into page $8100 -> extra cycle + // 0x80FF + X(1): page cross, 5 cycles c, bus := newTestCPU([]uint8{0xA2, 0x01, 0xBD, 0xFF, 0x80}) bus.mem[0x8100] = 0x99 - c.Step() // ldx #1 + c.Step() cycles := c.Step() if c.A != 0x99 { t.Fatalf("A = 0x%02X, want 0x99", c.A) @@ -81,55 +87,55 @@ func TestAbsoluteXPageCross(t *testing.T) { } func TestAdcCarryAndOverflow(t *testing.T) { - // 0x50 + 0x50 = 0xa0, signed overflow (positive+positive=negative), no carry + // 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 !c.flag(FlagV) { + if !has(c, cpu.FlagV) { t.Fatalf("V flag should be set") } - if c.flag(FlagC) { + if has(c, cpu.FlagC) { t.Fatalf("C flag should be clear") } - // 0xff + 0x01 = 0x00, carry out, no overflow + // 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 !c.flag(FlagC) { + if !has(c, cpu.FlagC) { t.Fatalf("C flag should be set") } - if !c.flag(FlagZ) { + if !has(c, cpu.FlagZ) { t.Fatalf("Z flag should be set") } - if c.flag(FlagV) { + if has(c, cpu.FlagV) { t.Fatalf("V flag should be clear") } } func TestSbcBorrow(t *testing.T) { - // 0x10 - 0x01 with carry preset (no borrow): result 0x0f, carry stays set - c, _ := newTestCPU([]uint8{0x38, 0xA9, 0x10, 0xE9, 0x01}) // sec ; lda #$10 ; sbc #$01 + // 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 !c.flag(FlagC) { + if !has(c, cpu.FlagC) { t.Fatalf("C flag should be set (no borrow)") } } func TestBranchTakenAndPageCross(t *testing.T) { - // beq with Z set, forward branch within the same page: +1 cycle - c, _ := newTestCPU([]uint8{0xA9, 0x00, 0xF0, 0x02, 0xEA, 0xEA, 0xEA}) // lda #0 ; beq +2 ; nop ; nop ; nop + // beq taken, same page: 3 cycles + c, _ := newTestCPU([]uint8{0xA9, 0x00, 0xF0, 0x02, 0xEA, 0xEA, 0xEA}) c.Step() cycles := c.Step() if cycles != 3 { @@ -141,21 +147,20 @@ func TestBranchTakenAndPageCross(t *testing.T) { } func TestJsrRts(t *testing.T) { - // jsr $8004 ; (pad) ; rts at $8004, followed by nop at $8003 after return prog := []uint8{0x20, 0x04, 0x80, 0xEA, 0x60} c, _ := newTestCPU(prog) - c.Step() // jsr + c.Step() if c.PC != 0x8004 { t.Fatalf("PC after JSR = 0x%04X, want 0x8004", c.PC) } - c.Step() // rts + 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}) // lda #$7e ; pha ; lda #0 ; pla + c, _ := newTestCPU([]uint8{0xA9, 0x7E, 0x48, 0xA9, 0x00, 0x68}) c.Step() c.Step() c.Step() @@ -166,25 +171,25 @@ func TestStackPushPullRoundTrip(t *testing.T) { } func TestPhpPlpPreservesFlags(t *testing.T) { - c, _ := newTestCPU([]uint8{0x38, 0x08, 0x18, 0x28}) // sec ; php ; clc ; plp - c.Step() // sec - c.Step() // php - c.Step() // clc - if c.flag(FlagC) { + 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() // plp - if !c.flag(FlagC) { + c.Step() + if !has(c, cpu.FlagC) { t.Fatalf("C flag should be restored by PLP") } } func TestIndirectJmpPageBug(t *testing.T) { - // jmp ($30ff) must read the high byte from $3000, not $3100 + // jmp ($30FF) reads high byte from $3000 c, bus := newTestCPU([]uint8{0x6C, 0xFF, 0x30}) bus.mem[0x30FF] = 0x34 - bus.mem[0x3000] = 0x12 // wrap target for the bug - bus.mem[0x3100] = 0x99 // must not be used + bus.mem[0x3000] = 0x12 + bus.mem[0x3100] = 0x99 c.Step() if c.PC != 0x1234 { t.Fatalf("PC = 0x%04X, want 0x1234", c.PC) @@ -204,18 +209,19 @@ func TestNmiPushesStateAndJumps(t *testing.T) { if c.SP != spBefore-3 { t.Fatalf("SP after NMI = 0x%02X, want 0x%02X", c.SP, spBefore-3) } - c.pop() // discard pushed status - if pc := c.pop16(); pc != pcBefore { + 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) { - // cmp #$10 with A=$10 -> Z=1, C=1, N=0 + // Z=1, C=1, N=0 c, _ := newTestCPU([]uint8{0xA9, 0x10, 0xC9, 0x10}) c.Step() c.Step() - if !c.flag(FlagZ) || !c.flag(FlagC) || c.flag(FlagN) { + 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) } } @@ -226,6 +232,6 @@ func TestUnimplementedOpcodePanics(t *testing.T) { t.Fatalf("expected panic for illegal opcode") } }() - c, _ := newTestCPU([]uint8{0x02}) // illegal/unofficial opcode + c, _ := newTestCPU([]uint8{0x02}) c.Step() } diff --git a/cpu/nestest_test.go b/tests/nestest_test.go index 4786c5b..de98156 100644 --- a/cpu/nestest_test.go +++ b/tests/nestest_test.go @@ -1,4 +1,4 @@ -package cpu +package tests import ( "bufio" @@ -6,45 +6,36 @@ import ( "regexp" "strconv" "testing" + + "crecher.net/gones/v2/cart" + "crecher.net/gones/v2/cpu" ) type nestestBus struct { - ram [0x0800]uint8 - prg []uint8 + ram [0x0800]uint8 + cart cart.Mapper } func (b *nestestBus) Read(addr uint16) uint8 { switch { case addr < 0x2000: return b.ram[addr%0x0800] - case addr >= 0x8000: - return b.prg[int(addr-0x8000)%len(b.prg)] + case addr >= 0x4020: + return b.cart.CPURead(addr) default: return 0 } } func (b *nestestBus) Write(addr uint16, v uint8) { - if addr < 0x2000 { + switch { + case addr < 0x2000: b.ram[addr%0x0800] = v + case addr >= 0x4020: + b.cart.CPUWrite(addr, v) } } -func loadNestestPRG(path string) ([]uint8, error) { - data, err := os.ReadFile(path) - if err != nil { - return nil, err - } - prgBanks := int(data[4]) - prgSize := prgBanks * 16 * 1024 - hasTrainer := data[6]&0x04 != 0 - offset := 16 - if hasTrainer { - offset += 512 - } - return data[offset : offset+prgSize], nil -} - type cpuState struct { pc uint16 a, x, y, p, sp uint8 @@ -61,6 +52,11 @@ func loadNestestLog(path string) ([]cpuState, error) { } defer f.Close() + parseHex8 := func(s string) uint8 { + v, _ := strconv.ParseUint(s, 16, 8) + return uint8(v) + } + var lines []cpuState scanner := bufio.NewScanner(f) for scanner.Scan() { @@ -68,10 +64,6 @@ func loadNestestLog(path string) ([]cpuState, error) { if m == nil { continue } - parseHex8 := func(s string) uint8 { - v, _ := strconv.ParseUint(s, 16, 8) - return uint8(v) - } pc, _ := strconv.ParseUint(m[1], 16, 16) cyc, _ := strconv.ParseUint(m[7], 10, 64) lines = append(lines, cpuState{ @@ -87,16 +79,11 @@ func loadNestestLog(path string) ([]cpuState, error) { return lines, scanner.Err() } -// TestNestest runs nestest.nes and diffs the state against a known good log before each instruction func TestNestest(t *testing.T) { const romPath = "testdata/nestest.nes" const logPath = "testdata/nestest.log" - if _, err := os.Stat(romPath); err != nil { - t.Skip("testdata/nestest.nes not present; add it and testdata/nestest.log to run this test") - } - - prg, err := loadNestestPRG(romPath) + c, err := cart.Load(romPath) if err != nil { t.Fatalf("loading ROM: %v", err) } @@ -108,32 +95,17 @@ func TestNestest(t *testing.T) { t.Fatal("no log lines parsed") } - bus := &nestestBus{prg: prg} - c := NewCPU(bus) - c.PC = 0xC000 - c.SP = 0xFD - c.Status = 0x24 - c.Cycles = want[0].cyc + core := cpu.NewCPU(&nestestBus{cart: c.Mapper}) + core.PC = 0xC000 + core.SP = 0xFD + core.Status = 0x24 + core.Cycles = want[0].cyc for i, w := range want { - got := cpuState{c.PC, c.A, c.X, c.Y, c.Status, c.SP, c.Cycles} + got := cpuState{core.PC, core.A, core.X, core.Y, core.Status, core.SP, core.Cycles} if got != w { t.Fatalf("line %d: got %+v, want %+v", i+1, got, w) } - - stopped := func() (panicked bool) { - defer func() { - if r := recover(); r != nil { - t.Logf("stopped at line %d: %v", i+1, r) - panicked = true - } - }() - c.Step() - return false - }() - if stopped { - t.Logf("matched %d instructions before stopping", i+1) - return - } + core.Step() } } diff --git a/cpu/testdata/nestest.log b/tests/testdata/nestest.log index fead022..fead022 100644 --- a/cpu/testdata/nestest.log +++ b/tests/testdata/nestest.log diff --git a/cpu/testdata/nestest.nes b/tests/testdata/nestest.nes Binary files differindex fc2a88c..fc2a88c 100644 --- a/cpu/testdata/nestest.nes +++ b/tests/testdata/nestest.nes |