Files
rnes/STATE.md
T

5.3 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
  • Batch 1 done (subroutines + stack): JSR, RTS, PHA, PLA, PHP, PLP, RTI, BRK, plus NOP and SEC as quick wins. Verified SP:FD->FB across JSR (push high-then-low works)
  • Batch 2 done (branches): all 8 branches (BEQ/BNE/BCC/BCS/BPL/BMI/BVC/BVS) via one shared branch() routine with condition param. Taken = +3 cycles, page-cross = +1 more. Verified both taken and not-taken paths in the log
  • Flag set/clear ops done (part of Batch 10): CLC, CLI, CLV, CLD, SEI, SED (SEC done in Batch 1). SEI/SED set, the rest clear. Fixed a real bug where SEI/SED were clearing instead of setting
  • Trace now matches the nestest reference log through line 37 (C780 STA, CYC:95), field-for-field (PC/A/X/Y/P/SP/CYC verified by label-comparison script). Next panic is on 0x85 (STA zp) at line 37, top of Batch 4
  • Batch 3 done (loads): LDA (8 modes), LDX (5 modes), LDY (5 modes) via three shared cores (lda/ldx/ldy) + thin wrappers. Caught two real bugs in review: all 4 LDY wrappers called self.ldx (would write to X instead of Y), and 6 of 8 LDX/LDY wrappers had wrong base cycles (3 instead of 4). Both fixed
  • trace bytes are length-aware via opcode_len (fixed-width byte field, columns align for awk diff)

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 - DONE (see Done section)
  • Batch 2 - DONE (branches, see Done section)
  • Batch 3 - DONE (loads, see Done section)
  • Batch 4 - Stores: STA 7 modes, STX 3, STY 3 (share store core, no flag changes). Next up
  • 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 - PARTIAL: flag ops done (CLC/CLI/CLV/CLD/SEI/SED, plus SEC/NOP). Remaining: 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