Skip to content

ADR 0012 — v1.9.0: accuracy tail — per-cycle 65816 bus trace

  • Status: Accepted
  • Release: v1.9.0 (2026-06-29)
  • Theme: The post-v1.8.0 accuracy tail. Opened by the first decision of the cycle; per-release decisions fold in here.

D1 — Per-cycle 65816 bus-trace validation (TestHarte65816BusTrace, #495)

  • Context: the WDC 65C816 core (#456, ADR 0010) was validated against the Tom Harte 65816 corpus for final state + cycle count only. step816 computed the cycle count abstractly (cyc := 2; cyc += extras) and issued only the functional read24/write24 accesses — the internal/dummy cycles the real chip drives on the bus (direct-page adds, index adds, RMW modify cycles, stack/branch/jump internal cycles) were counted but not emitted. The 6502 and 65C02 cores already had per-cycle bus-trace tests (TestHarte6502BusTrace #428, TestHarte65C02BusTrace #455/#475); the 65816 was the gap.

  • Decision: add TestHarte65816BusTrace, the 16-bit/24-bit sibling, and rework step816 to emit every cycle's 24-bit bus access in hardware order. Delivered in four chunks mirroring the original core build:

  • register/flag/transfer/immediate (the io816 internal-cycle helper);
  • addressing-mode / ALU-memory — direct-page +1 (dpIO), dp-index and stack-relative adds at PC-1 (ioPC1), the abs/(dp)-indexed cross cycle at the un-carry-corrected address (indexIO/unfixedAddr), and (sr),Y's +Y re-read of the pointer high byte;
  • RMW, stack, control-flow — the RMW modify cycle is mode-dependent (emulation drives a dummy write of the original value, native a dummy read; 16-bit writes the result high byte then low), stack pushes take one internal cycle / pulls two, taken branches add an internal cycle (plus one on an emulation page-cross), and JSR/JSL/RTS/RTL/RTI/BRK/COP emit their interleaved pushes, signature-byte read, and vector reads;
  • the test iterates all 256 opcodes in both emulation and native, gated by harteBusSkip816. The harness records each Read24/Write24 as [addr, value, rw] (busRecorder816) and compares against the corpus cycle list, parsing the pin string for read/write and treating a null value (internal cycle) as a don't-care.

  • Consequences: every opcode is per-cycle bus-exact in both modes except four in harteBusSkip816: WAI/STP halt with a None-address bus cycle the recorder can't represent, and MVN/MVP use chippy's whole-block-move debugger model (the corpus caps each block-move case mid-instruction — the same generator artifact that excludes them from the state harness, ADR 0010 D6). The internal cycles are pure dummy reads/writes, so TestHarte65816 (state + count) is unchanged and the 8-bit cores are unaffected (the helpers live only on the step816 path). The trace validates the full Harte cycle string — address + value + the 8 pin bits VDA/VPA/VPB/RWB/E/M/X/MLB. Each access tags its type via c.busPins (pinData/pinProg/pinNone/ pinVector, plus MLB across RMW): the opcode fetch asserts VDA+VPA, operand fetches VPA, data/stack/pointer accesses VDA, internal cycles none, vector reads VDA+VPB, and RMW accesses MLB (its modify cycle MLB-only). E/M/X are snapshotted at instruction start so they reflect the pre-change flags (SEP/REP/XCE show their old widths). This is stricter than the 6502/65C02 traces (which the corpus gives only as addr+value+rw).