4.6 KiB
4.6 KiB
Status
Done
- Repo set up, backed up the original game ROMs to roms/backup (gitignored, stays local)
- Got a full set of CPU test ROMs in roms/test/cpu: kevtris nestest (with reference log) plus the blargg suite (instr tests, timing, interrupts, dummy reads, reset checks)
- Cartridge module done and working (src/cartridge.rs):
- iNES header parser (magic, PRG/CHR sizes, mapper, mirroring, flags)
- Mapper 0 (NROM) support only: 16KB PRG mirroring, 32KB direct, CHR-ROM and CHR-RAM (zeroed 8KB buffer)
- read_prg/read_chr/write_chr with debug_assert guards
- Split from_file (thin I/O wrapper) / parse_data (pure parser) for testability
- 8 unit tests, all passing (header, PRG mapping, error cases, CHR-RAM roundtrip)
- Bus module done (src/bus.rs):
- Bus trait (read/write) so the CPU is decoupled from the concrete memory layout
- NesBus: 2KB mirrored CPU RAM, cartridge PRG routing, PPU/APU/SRAM/expansion regions stubbed (return 0)
- CPU skeleton done (src/cpu.rs):
- Flag bit constants, registers (A/X/Y/SP/PC/P), set/clear/flag helpers, set_zp_flags
- Memory helpers: fetch, fetch16, read, write, read16, push, pop
- All 10 addressing mode helpers, including the JMP ($xxxx) page-wrap bug and page-cross cycle penalties
- First 3 opcodes implemented (JMP abs, LDX imm, STX zp)
- step dispatch shell + trace in nestest format
- set_test_start for the nestest harness
- nestest harness working (main.rs): loads nestest.nes, runs from $C000 with CYC:7, dumps trace to ours.log
- First 5 trace lines match the nestest reference log exactly (PC, regs, P, SP, CYC), then it panics on the next unimplemented opcode (JSR 0x20), which names the next target
Next
Finish the official CPU. Nestest analysis findings: the full 8991-line log tests illegal opcodes too (225 distinct opcodes total; first illegal is 0x04 at line 5004). So the official-only CPU is verified against lines 1-5003, then panics on 0x04 by design. Official opcodes never seen in the log: BRK 0x00, CLI 0x58.
CPU implementation batches (by family, ordered by nestest log frequency):
- Batch 1 - Subroutines + stack: JSR 0x20 (push ret-1 high then low), RTS 0x60 (pop low then high), PHA 0x48, PLA 0x68, PHP 0x08, PLP 0x28 (B-flag on push rule), RTI 0x40, BRK 0x00
- Batch 2 - Branches (all 8): BEQ F0, BNE D0, BCC 90, BCS B0, BPL 10, BMI 30, BVC 50, BVS 70. One shared method per flag. +1 cycle if taken, +1 more if target crosses page
- Batch 3 - Loads: LDA 8 modes, LDX 5 modes, LDY 5 modes (share load + set_zp_flags)
- Batch 4 - Stores: STA 7 modes, STX 3, STY 3 (share store core, no flag changes)
- Batch 5 - Compares: CMP 8 modes, CPX 3, CPY 3 (set C/Z/N, no store)
- Batch 6 - Arithmetic: ADC 8 modes, SBC 8 modes (N/V/C/Z, decimal flag inert), BIT 2 modes
- Batch 7 - Logic: AND, ORA, EOR (8 modes each)
- Batch 8 - Inc/Dec: INC/DEC 4 modes each (RMW), INX, INY, DEX, DEY
- Batch 9 - Shifts: ASL, LSR, ROL, ROR (accumulator + 4 memory modes each, RMW)
- Batch 10 - Flags + misc: CLC 18, SEC 38, CLI 58, SEI 78, CLD D8, SED F8, CLV B8, NOP EA, JMP abs 4C, JMP ind 6C, transfers TAX/TAY/TXA/TYA/TSX/TXS
Rules for each batch:
- Add the opcode's length to opcode_len in the same edit (never let them drift)
- Re-diff after each batch (cargo run > ours.log + awk column compare)
- Check base cycles per mode, feed page-cross extra into the cycle total
Verification for this phase:
- All 151 official opcodes implemented
- Trace matches lines 1-5003 field-for-field (PC/A/X/Y/P/SP/CYC)
- cargo build clean
- Commit + update STATE.md
After official CPU is done (follow-ups):
- Add MMC1 (mapper 1) to cartridge so blargg official_only.nes / all_instrs.nes can run (authoritative official check)
- Run blargg mapper-0 suite (coredump, cpu_timing_test, ram_after_reset, registers)
- Then the illegal-opcode phase for the full 8991-line nestest pass
- Then bus/PPU/timing, GUI (egui + winit) come after the CPU milestone
Decisions
- No external dependencies yet, keeping it pure std until the GUI milestone
- GUI choice: egui + winit (decided, not yet used)
- Scope: cartridge + CPU first, verified headless before graphics
- Bus is a trait (impl Bus) so the CPU works against any memory layout (real bus, test bus, debug bus)
- Flags stored as a raw u8 with named bit-mask constants (FLAG_CARRY etc.)
- opcode length is a growing match in opcode_len() (returns u8); migrate to a full 256-entry table when it gets big
- trace diff compares PC + A/X/Y/P/SP/CYC, ignoring disassembly/PPU columns
- Mapper 0 first, other mappers (MMC1, CNROM) implemented after CPU works
- Test ROMs are homebrew/open, no ethics issue; game backups stay in gitignored roms/backup