diff --git a/CHANGELOG.md b/CHANGELOG.md index 13c6d30c..4c6d08f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **AccuracySNES: `A5.19` — `RTI` costs 7 cycles native and 6 emulation.** The extra native cycle is + the PBR pull, an 8-clock WRAM read rather than a 6-clock internal cycle, so eight iterations differ + by 16 dots. Group A on-cart coverage goes 12 → 13 of 15 in `A5`. + + The RTIs are chained through **one** `rti` instruction: every return frame is built before + `measure_begin`, all returning to the `@spin` label that is the `rti` itself, except the + first-pushed one — pulled last — which returns to `@done`. The measured span therefore holds + nothing but the RTIs, with no loop counter or branch inside it. + + Three traps on the way, all recorded in `docs/accuracysnes-plan.md` §`A5.19`. The 341-dot line + wrap returned native **42** against emulation **76** — the native reading smaller, the arithmetic + opposite of the assertion — diagnosed by measuring at two repeat counts and reading the slope. + `rep` cannot clear `m` while `E = 1`, so `measure_result`'s 16-bit arithmetic silently ran 8-bit + and pinned the reading at a constant regardless of repeat count. And the one that forced the + design: `hv_begin`/`hv_end` run at the caller's register width, so the instrument's own overhead + **does not cancel across a mode boundary** — every other `A5` differential compares two spans in + the same mode. The `xce` pairs now sit inside the span, so both arms run the instrument natively + and differ only in the mode the RTIs execute in. + + Verified by injection at the site the row names: making native `RTI` advance `S` without paying + the bus cycle collapses the difference to exactly 0 and fails the row. snes9x's divergence count + is unchanged, so it passes the row as well. + - **Netplay: a connection-quality readout, a graded desync banner, and a disconnect that actually fires.** The two preceding entries built the machinery; nothing in the frontend read it. The session's transport is now wrapped in `LivenessTransport` and ticked once per frame from diff --git a/docs/accuracysnes-coverage.md b/docs/accuracysnes-coverage.md index 8903e461..dfb2d45d 100644 --- a/docs/accuracysnes-coverage.md +++ b/docs/accuracysnes-coverage.md @@ -12,7 +12,7 @@ Every sub-group of Part V is enumerated, so this is a **complete** statement of | `A2` | 10 | 10 | 0 | — | | `A3` | 10 | 10 | 0 | — | | `A4` | 10 | 10 | 0 | — | -| `A5` | 15 | 12 | 0 | A5.18, A5.19, A5.20 | +| `A5` | 15 | 13 | 0 | A5.18, A5.20 | | `A6` | 15 | 13 | 0 | A6.13, A6.15 | | `A7` | 5 | 5 | 0 | — | | `A8` | 6 | 6 | 0 | — | @@ -52,7 +52,7 @@ Every sub-group of Part V is enumerated, so this is a **complete** statement of | `F1` | 22 | 13 | 0 | F1.13, F1.15, F1.16, F1.17, F1.18, F1.19, F1.20, F1.21, F1.22 | | `G1` | 18 | 15 | 0 | G1.06, G1.13, G1.18 | -**291 of 443** enumerated assertion rows covered by an on-cart test, plus **53** covered only by a rendered scene (`docs/adr/0013`) — **344 of 443** in total. +**292 of 443** enumerated assertion rows covered by an on-cart test, plus **53** covered only by a rendered scene (`docs/adr/0013`) — **345 of 443** in total. The two columns are kept apart on purpose. An on-cart result means the same thing on any emulator and on real hardware; a rendered scene needs a host holding the golden. Adding them into one figure would quietly change what the number claims. diff --git a/docs/accuracysnes-plan.md b/docs/accuracysnes-plan.md index 792e75fd..f7d86dae 100644 --- a/docs/accuracysnes-plan.md +++ b/docs/accuracysnes-plan.md @@ -455,6 +455,45 @@ So nothing is asserted and nothing is recorded. What is known: The next attempt should start from a scratch build that dumps `$213F` before and after each step into slots verified unused, rather than from a folded variant that hides where the discrepancy is. +### `A5.19` — a cross-mode differential needs the instrument on the same side of the mode switch + +`RTI` costs 7 cycles native and 6 emulation; the extra one is the PBR pull, an 8-clock WRAM read, +so eight iterations differ by 16 dots. Landed, and it took three revisions, each a trap worth +naming. + +**The shape that works.** Chain the RTIs through *one* `rti` instruction: build every return frame +before `measure_begin`, all of them returning to the `@spin` label that is the `rti` itself, except +the first-pushed one — pulled last — which returns to `@done`. The span then holds nothing but the +RTIs, with no loop counter or branch inside it to dilute the signal. + +**Trap 1: the 341-dot line wrap**, the same one `A5.08` records. Sixteen RTIs is a ~200-dot span; +it wrapped and reported native **42** against emulation **76** — the native reading *smaller*, the +arithmetic opposite of the assertion. Diagnose it by measuring at two repeat counts and reading the +**slope**: 4 → 8 RTIs moved native by exactly 52 dots = 4 x 13, the predicted per-RTI cost, which +proved the per-iteration model right and only the offset junk. Eight RTIs is a ~104-dot span. + +**Trap 2: `rep` cannot clear `m` while `E = 1`.** `measure_result` emits 16-bit arithmetic; run it +in emulation and it silently executes 8-bit, `sta` writes one byte, and the reading sticks at a +constant **regardless of repeat count**. A number that does not move when the work moves is the +signature. `measure_end` is width-neutral (`php`/`plp`), so latch first and widen after. + +**Trap 3, the one that blocked the design: the instrument's own overhead does not cancel across a +mode boundary.** `hv_begin`/`hv_end` are subroutines that run at whatever width the caller is in, so +the native and emulation spans carried *different* fixed overheads — measured native 279 against +emulation 332, emulation larger when the assertion says it must be smaller. Every other `A5` +differential compares two spans in the same mode, which is why this had never surfaced. + +The fix is to put the `xce` pairs **inside** the span, so both arms run the instrument natively and +contain identical non-RTI instructions, differing only in the mode the RTIs execute in. The native +arm therefore carries a deliberately no-op `clc`/`xce` at each end. With that, both spans carry the +same 189-dot overhead: native 293, emulation 277, difference **16**, and 4 RTIs gives **8** — linear +in the repeat count, which is what distinguishes a per-iteration cost from a fixed artifact. + +Verified by injection at the site the row names: making native `RTI` advance `S` without paying the +bus cycle collapses the difference to exactly **0** and fails the row. snes9x's divergence count is +unchanged, so it passes the row too; Mesen2 could not be consulted (the headless runner times out in +this environment, which predates this work). + ### `A5.20` — MVN's per-byte cost does not measure, and the number is interesting > **Read the conclusion first.** The `A5.20` **cart test** is withdrawn; no test ships. The diff --git a/tests/roms/AccuracySNES/ERROR_CODES.md b/tests/roms/AccuracySNES/ERROR_CODES.md index d33df63b..9ad43dcf 100644 --- a/tests/roms/AccuracySNES/ERROR_CODES.md +++ b/tests/roms/AccuracySNES/ERROR_CODES.md @@ -596,6 +596,14 @@ Provenance: **Documented** (WDC/GTE/VLSI instruction-operation tables; docs/accu |---|---|---| | 1 | `$02` | LDX abs did not cost one extra 8-clock access with x=0 | +### A5.19 — RTI 7 native, 6 emul + +Provenance: **Documented** (WDC datasheet instruction-operation table; docs/accuracysnes-timing-oracle.md). Kind: scored. + +| Code | Byte | Meaning | +|---|---|---| +| 1 | `$02` | RTI did not cost one extra stack pull in native mode (7 vs 6) | + ### A6.11 — WAI wakes, no vector Provenance: **Documented** (WDC datasheet: WAI wakes on the interrupt line; I gates the vector). Kind: scored. diff --git a/tests/roms/AccuracySNES/SOURCE_CATALOG.tsv b/tests/roms/AccuracySNES/SOURCE_CATALOG.tsv index cc7d58a3..23b5f8fe 100644 --- a/tests/roms/AccuracySNES/SOURCE_CATALOG.tsv +++ b/tests/roms/AccuracySNES/SOURCE_CATALOG.tsv @@ -71,264 +71,265 @@ 68 A3.06 A (d,S),Y escape + carry Documented WDC datasheet; superfamicom.org escape list Scored 0x7EF064 A3.06 69 A5.09 A +1 m width penalty Documented WDC/GTE/VLSI instruction-operation tables; docs/accuracysnes-timing-oracle.md Scored 0x7EF065 A5.09 70 A5.10 A +1 x width penalty Documented WDC/GTE/VLSI instruction-operation tables; docs/accuracysnes-timing-oracle.md Scored 0x7EF066 A5.10 -71 A6.11 A WAI wakes, no vector Documented WDC datasheet: WAI wakes on the interrupt line; I gates the vector Scored 0x7EF067 A6.11 -72 A8.07 A MVN interruptible Documented WDC datasheet: MVN rewinds PC by 3 per iteration, so RTI re-enters Scored 0x7EF068 A8.06 -73 A6.12 A WAI wake lat (golden) Contested the stated 1-cycle latency is 1.5 dots, below what a cart can resolve; recorded in buckets rather than asserted Golden 0x7EF069 A6.12 -74 A4.11 A JMP (a,X) ptr bank Documented SNESdev Errata, 65C816 section (worked example PBR=$05) Scored 0x7EF06A A4.04 -75 A4.12 A JSR (a,X) ptr bank Documented SNESdev Errata, 65C816 section Scored 0x7EF06B A4.05 -76 A6.13 A IRQ handler PBR = $00 Documented WDC datasheet: the vector is 16-bit, so the handler runs in bank 0 Scored 0x7EF06C A6.09 -77 A6.14 A RTI pull matches mode Documented WDC datasheet: RTI pulls PBR in native mode only Scored 0x7EF06D A6.10 -78 A2.13 A 16-bit dp page cross Contested superfamicom.org describes the wrap as happening "theoretically"; the dossier records the row as UNVERIFIED, so it is observed rather than asserted Golden 0x7EF06E A2.05 -79 C1.01 C OAM word write/read Documented SNESdev Wiki, OAM; fullsnes Scored 0x7EF06F C1.02 -80 C1.02 C OAM odd write latched Documented SNESdev Wiki, OAM; anomie Scored 0x7EF070 C1.02 -81 C1.03 C OAMADDR reload clears Documented SNESdev Wiki, OAM; anomie Scored 0x7EF071 C1.01 -82 C1.04 C OAM rd/wr one counter Documented SNESdev Wiki, OAM Scored 0x7EF072 C1.05 -83 C1.05 C OAM high table mirror Documented SNESdev Wiki, OAM; fullsnes Scored 0x7EF073 C1.04 -84 C1.03b C High table commits bytes Documented SNESdev Wiki, OAM; fullsnes Scored 0x7EF074 C1.03 -85 C1.07 C Blank edge reloads OAM Documented SNESdev Wiki, OAM; fullsnes Scored 0x7EF075 C1.07 -86 C7.09 C Overflow flags clear Documented SNESdev Wiki, sprites; fullsnes Scored 0x7EF076 C7.09 -87 C2.01 C VMAIN step 1 word Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF077 C2.01 -88 C2.02 C VMAIN low-byte trigger Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF078 C2.02 -89 C2.03 C VRAM read prefetch Documented SNESdev Wiki; docs/ppu.md edge case 4 Scored 0x7EF079 C2.08 -90 C2.04 C VRAM bit 15 unconnected Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF07A C2.07 -91 C2.05 C VMAIN step 32 words Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF07B C2.01 -92 C2.06 C VMAIN remap hits bus Documented SNESdev Wiki, PPU registers; fullsnes; anomie Scored 0x7EF07C C2.03-05,C2.06 -93 C2.12 C Blank off closes VRAM Documented SNESdev Wiki, VRAM access; fullsnes Scored 0x7EF07D C2.12 -94 C3.03b C CGRAM read bit 7 is bus Documented SNESdev Wiki, CGRAM; fullsnes Scored 0x7EF07E C3.03 -95 C3.09 C $213F is a field flag Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF07F C3.09 -96 C3.01 C CGRAM two-write commit Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF080 C3.01 -97 C3.02 C CGADD resets flipflop Documented SNESdev Wiki, PPU registers Scored 0x7EF081 C3.02 -98 C3.03 C OPHCT is a 9-bit pair Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF082 C3.06 -99 C3.04 C H counter advances Documented SNESdev Wiki, PPU registers Scored 0x7EF083 - -100 C3.05 C $213F resets flipflop Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF084 C3.08 -101 C3.07 C Counter flipflops differ Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF085 C3.07 -102 C3.12 C CGRAM taken in render Documented fullsnes and the SNESdev Wiki: a CGRAM access during active display uses the colour the PPU is drawing, not the CPU CGADD. Mesen2 models it (writes use InternalCgramAddress when !CanAccessCgram); the batch compositor and snes9x use the programmed CGADD and fail Scored 0x7EF086 C3.04 -103 C13.01 C PPU1 open bus in $213E Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF087 C13.07,C13.10 -104 C13.02 C PPU2 open bus in $213F Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF088 C13.08,C13.10 -105 C13.03 C PPU1/PPU2 bus separate Corroborated the bsnes/ares lineage and Mesen2 model two distinct latches Scored 0x7EF089 C13.09 -106 C14.01 C PPU1 version (golden) Documented SNESdev Wiki, PPU registers; fullsnes Golden 0x7EF08A C14.01 -107 C14.02 C PPU2 version (golden) Documented SNESdev Wiki, PPU registers; fullsnes Golden 0x7EF08B C14.02 -108 C14.03 C PPU1 mstr/slv (golden) Contested the bit reports a board wiring input, so no software-visible value is correct or incorrect; recorded rather than asserted Golden 0x7EF08C C14.03 -109 C11.06 C MPY is 16x8 signed Documented SNESdev Wiki, Mode 7; fullsnes Scored 0x7EF08D C11.06 -110 C11.06b C MPY sign handling Documented SNESdev Wiki, Mode 7; fullsnes Scored 0x7EF08E C11.06 -111 C11.07 C MPY latch is shared Documented fullsnes and the SNESdev Wiki: PPU1's write-twice registers share one byte latch, so $210D/$210E and $211B-$211E interfere with each other Scored 0x7EF08F C11.07 -112 C11.08 C MPY busy during render Contested fullsnes and the SNESdev Wiki agree the Mode 7 multiplier is the renderer's and is busy during active display, but neither states which intermediate it holds at a given moment, and that is the only thing a cart can observe Golden 0x7EF090 C11.08 -113 C7.01 C Range Over at 32 sprites Documented SNESdev Wiki, Sprites; fullsnes Scored 0x7EF091 C7.01 -114 C7.04 C Offscreen X takes a slot Documented fullsnes and the SNESdev Wiki: sprite range evaluation selects on Y alone, so a sprite at X = $100 occupies a range slot despite being entirely off-screen Scored 0x7EF092 C7.04 -115 C7.02 C Time Over is slivers Documented SNESdev Wiki, Sprites; fullsnes; anomie Scored 0x7EF093 C7.02 -116 C7.08 C Flags ignore $212C Documented SNESdev Wiki, Sprites; fullsnes Scored 0x7EF094 C7.08 -117 C7.10 C OAM write to high table Documented fullsnes and the SNESdev Wiki (the Uniracers case): an OAM write during active display is driven to the evaluator's address and lands in the high table, not the CPU OAMADDR. Mesen2 models it (oamAddr = 0x200 | ((oamAddr & 0x1F0) >> 4)); the batch compositor and snes9x write the programmed OAMADDR and fail Scored 0x7EF095 C7.16 -118 C2.11 C VRAM locked in render Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF096 C2.11 -119 C1.08 C OAM addr in render Documented fullsnes and the SNESdev Wiki: during active display the renderer drives the OAM address, so a $2138 read returns a render-time address, not the CPU-programmed one. MesenCE models it (GetOamAddress -> _oamEvaluationIndex<<2); the batch compositor and snes9x return the programmed address and fail Scored 0x7EF097 C1.08 -120 C2.10 C Dropped write still incs Documented SNESdev Wiki, PPU registers; fullsnes; anomie Scored 0x7EF098 C2.10 -121 C1.06 C OAM addr reloads Documented SNESdev Wiki, OAM; anomie Scored 0x7EF099 C1.06 -122 C9.04 C Overscan moves vblank Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF09A C9.04,B4.02 -123 C9.05 C Mid-frame overscan lock Contested RustySNES and snes9x re-close the VRAM window on a mid-frame overscan enable and Mesen2 does not, so the references split one each way; the dossier's repro cannot break the tie because it read-modify-writes the write-only $2133 Golden 0x7EF09B C9.05 -124 C2.09 C VRAM read latch order Documented SNESdev Wiki, PPU registers: return latch, refill latch, then increment, with VMAIN bit 7 selecting which of $2139/$213A triggers it; bsnes and ares both refill before the step in sfc/ppu/io.cpp, and snes9x and Mesen2 agree Scored 0x7EF09C C2.09 -125 C3.10 C $2137 latch is gated Documented superfamicom.org registers: $2137 latches the H/V counter only if $4201 bit 7 is set, and no latching can occur while it is 0; snes9x and Mesen2 both gate it. What the read returns is a separate question, split out into C3.11 Scored 0x7EF09D C3.05 -126 C3.11 C $2137 open bus source Contested the sources say only that $2137 reads back as open bus; snes9x and RustySNES present PPU1's latch while Mesen2 presents the CPU's, and nothing available decides between two physically reasonable readings Golden 0x7EF09E C3.05 -127 B1.01 B MEMSEL selects FastROM Documented SNESdev Wiki, Memory map / timing; fullsnes Scored 0x7EF09F B1.01 -128 B1.02 B JOYSER is 12 clocks Documented SNESdev Wiki, Memory map / timing; fullsnes Scored 0x7EF0A0 B1.02 -129 B2.04 B NTSC frame is 262 lines Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A1 B2.04 -130 B4.03 B RDNMI sets at vblank Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A2 B4.03 -131 B2.01 B No dot above 339 Corroborated fullsnes' PPU H-Counter-Latch Quantities histogram, a direct hardware measurement: dots 323 and 327 latch six times, dot 340 never. bsnes, ares and Mesen2 all implement it; snes9x uses 322/326 and is the outlier Scored 0x7EF0A3 B2.01 -132 B4.16 B H-IRQ position (golden) Contested no source pins the fired dot at single-dot precision; recorded as the before/after guard for T-06-A's clock-domain comparator change Golden 0x7EF0A4 - -133 B4.17 B NMI enable is a level Documented SNESdev Wiki NMITIMEN/RDNMI [ERRATA]; fullsnes $4200/$4210 Scored 0x7EF0A5 B4.06 -134 B4.04 B RDNMI is read-to-clear Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A6 B4.04 -135 B4.05 B RDNMI auto-clears Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A7 B4.05 -136 B4.08 B V-IRQ fires at VTIME Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A8 B4.08 -137 B4.12 B $4211 read releases IRQ Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A9 B4.12 -138 B4.15 B CPU revision (golden) Documented SNESdev Wiki, Timing; fullsnes Golden 0x7EF0AA G1.09 -139 B5.01 B 8x8 unsigned multiply Documented SNESdev Wiki, CPU registers; fullsnes Scored 0x7EF0AB B5.01 -140 B5.02 B 16/8 unsigned divide Documented SNESdev Wiki, CPU registers; fullsnes Scored 0x7EF0AC B5.02 -141 B5.03 B Divide by zero Documented SNESdev Wiki, CPU registers; fullsnes Scored 0x7EF0AD - -142 B5.04 B Mul/div overlap (golden) Contested SNESdev Errata states overlapping $4203/$4206 operation is undefined Golden 0x7EF0AE B5.03 -143 B5.05 B Mul/div power-on state Documented anomie regs.txt r1157 and nocash fullsnes, independently; implemented by bsnes/ares/Mesen2. No known hardware test ROM Scored 0x7EF0AF B5.04 -144 B1.03 B Internal cycles are 6 Documented SNES Development Manual Bk I 21.1; SNESdev Wiki; fullsnes Scored 0x7EF0B0 B1.03 -145 B1.04 B DMA speed is uniform Documented SNES Development Manual Bk I 21.1 (DMA at 2.68MHz regardless of address) Scored 0x7EF0B1 B1.04 -146 B2.06 B Interlace line count Contested the dossier conditions the extra line on $213F.7 (the field), which this test does not control — sampling V across an uncontrolled field cannot assert a line count Golden 0x7EF0B2 B2.06 -147 B2.05 B PAL frame is 312 lines Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0B3 B2.05 -148 B4.14 B IRQ dispatch latency Documented SNESdev Wiki, Timing; fullsnes — the sub-cycle poll point is not CPU-observable, so its consequence is measured instead Golden 0x7EF0B4 B4.14 -149 B2.10 B Region bit (golden) Contested SNESdev PPU registers places the 50/60Hz bit at bit 3, which overlaps the PPU2 version field; fullsnes places it at bit 4 Golden 0x7EF0B5 B2.10 -150 B4.07 B H-IRQ position (golden) Contested the $4211 poll loop is coarser than the dot the comparator fires on, so the exact H position is not resolvable from software by polling Golden 0x7EF0B6 B4.07 -151 B4.09 B HV-IRQ needs both Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0B7 B4.09 -152 B3.01 B DRAM refresh pause Contested fullsnes and anomie put the pause at 40 clocks near line-clock 536, and RustySNES now models it there (docs/dram-refresh.md), so this loop records variant 2 (one interval ~10 dots longer); ares' own source still calls its refresh *pattern* technically wrong and only right on average, so the exact per-line position stays host-cross-validated rather than self-asserted Golden 0x7EF0B8 B3.01,B3.02,B3.03 -153 B4.13 B Timer range is 9-bit Documented fullsnes $4207-$420A: HTIME is 0-339 and VTIME 0-261 (NTSC) / 0-311 (PAL), both held in nine bits Scored 0x7EF0B9 B4.13 -154 B4.11 B Dot 153, last line Contested superfamicom.org's timing page states the exception and gives no mechanism; its timing text derives from fullsnes, so the two are one source, and no test ROM verifies it. ares, bsnes, Mesen2 and snes9x were each searched and none implements it Golden 0x7EF0BA B4.11 -155 D1.01 D DMA mode 0 Documented SNESdev Wiki, DMA; fullsnes Scored 0x7EF0BB D1.01 -156 D1.01b D DMA mode 1 Documented SNESdev Wiki, DMA; fullsnes Scored 0x7EF0BC D1.01 -157 D1.06 D DMA count hits zero Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0BD D1.06 -158 D1.07 D DMA fixed A-bus Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0BE D1.07 -159 D1.07b D DMA decrementing A-bus Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0BF D1.07 -160 D1.10 D DMA $43xB scratch latch Corroborated ares and bsnes both model the latch and serialize it Scored 0x7EF0C0 D1.10 -161 D1.02 D DMA 8 clocks/byte Documented SNESdev Wiki, DMA timing; fullsnes Scored 0x7EF0C1 D1.02 -162 D1.05 D DMA count 0 = 65536 Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0C2 D1.05 -163 D1.09 D WRAM->$2180 no-write Documented fullsnes: "does not cause a write to occur" Scored 0x7EF0C3 D1.09,D1.15 -164 D2.03 D HDMA line-count byte Documented SNESdev Wiki, HDMA; fullsnes Scored 0x7EF0C4 D2.03 -165 D2.04 D HDMA repeat flag Documented SNESdev Wiki, HDMA; fullsnes Scored 0x7EF0C5 D2.04 -166 D2.07 D HDMA preempts GP-DMA Documented SNESdev Wiki, HDMA; anomie's timing doc; fullsnes Scored 0x7EF0C6 D2.07 -167 D2.09 D HDMA armed mid-frame Contested fullsnes and the SNESdev Wiki record that enabling HDMA outside vblank produces erroneous writes from uninitialised A2An/NLTRn, but what those writes contain is a function of the previous frame's leftover state and is specified nowhere Golden 0x7EF0C7 D2.09 -168 D1.14 D $2180 B->A does write Documented fullsnes: $2180->WRAM writes, but the value written is invalid Scored 0x7EF0C8 D1.14 -169 D1.13 D DMA count hits zero Documented fullsnes and ares: the DAS $43x5/6 byte-count register decrements as GP-DMA transfers and reads $0000 when the transfer completes Scored 0x7EF0C9 D1.13 -170 D1.11 D DMA power-on state Corroborated fullsnes register table and the SNESdev DMA-registers page agree independently; ares and bsnes default every channel field to match Scored 0x7EF0CA D1.11 -171 D1.08 D Invalid A-bus (golden) Contested the errata names the ranges invalid but does not specify what is read instead; the substitute is open bus, whose content is core-specific and time-dependent Golden 0x7EF0CB D1.08 -172 D1.03 D DMA startup overhead Documented SNESdev Wiki, DMA timing; fullsnes Golden 0x7EF0CC D1.03 -173 D1.04 D DMA channel priority Documented SNESdev Wiki, DMA; fullsnes Scored 0x7EF0CD D1.04 -174 D2.05 D HDMA indirect mode Documented SNESdev Wiki, HDMA; fullsnes Scored 0x7EF0CE D2.05 -175 D2.06 D HDMA $4308/$430A state Documented SNESdev Wiki, HDMA registers; fullsnes Scored 0x7EF0CF D2.06 -176 E4.11 E ARAM power-on pattern Contested the dossier records a repeating 32x$00 / 32x$FF fill and marks it chip-dependent and informational; RustySNES, snes9x and Mesen2 all boot APU RAM uniformly zero instead Golden 0x7EF0D0 E4.11 -177 E1.01 E MUL YA flags from Y Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0D1 E1.01 -178 E1.02 E DIV YA,X normal branch Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D2 E1.02 -179 E1.07 E DIV valid to Q<=511 Documented SNESdev Wiki SPC700 reference and fullsnes, both flagging DIV as valid only for quotients up to 511; the values past it follow E1.03's overflow formula Scored 0x7EF0D3 E1.07 -180 E1.04 E DIV H = nibble compare Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0D4 E1.04 -181 E1.05 E DIV V is quotient bit 8 Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D5 E1.05 -182 E1.06 E DIV flags from quotient Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0D6 E1.06 -183 E1.13 E ADDW H = bit-11 carry Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D7 E1.13 -184 E1.15 E MOVW YA sets 16-bit N/Z Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D8 E1.15 -185 E3.01 E Timer read clears it Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0D9 E3.01 -186 E3.02 E Timer enable 0->1 resets Documented SNESdev Wiki SPC700 timers and fullsnes: a 0->1 on a $F1 timer-enable bit resets that timer's stage-2 divider and stage-3 output counter Scored 0x7EF0DA E3.02 -187 E3.11 E $F2 bit 7 blocks writes Documented SNESdev Wiki, S-DSP; fullsnes Scored 0x7EF0DB E3.11 -188 E3.11b E DSP register addressing Documented SNESdev Wiki, S-DSP registers; fullsnes Scored 0x7EF0DC - -189 E2.01 E Store dummy-reads target Documented SNESdev Wiki, SPC700; fullsnes — flagged as errata Scored 0x7EF0DD E2.01 -190 E2.05 E DP index wraps in page Documented SNESdev Wiki, SPC700 addressing; fullsnes Scored 0x7EF0DE E2.05 -191 E6.09 E Gaussian sum wraps Documented fullsnes and anomie's DSP doc: of the four gaussian additions the first cannot overflow, the second wraps in 16 bits and the third saturates Scored 0x7EF0DF E6.09 -192 E6.11 E BRR waveform vectors Contested the dossier names four nibble patterns and asks what a decoder makes of each without stating an expected value for any of them; the row's content is the measurement Golden 0x7EF0E0 E6.11 -193 E10.01 E 32 SPC cycles per sample Documented fullsnes and anomie: the SPC700's 1.024 MHz clock and the DSP's 32 kHz output rate fix the ratio at 32 CPU cycles per output sample Scored 0x7EF0E1 E10.01 -194 E10.05 E Soft reset acts as $E0 Contested both sources agree FLG bit 7 makes the DSP behave as $E0 and force every voice into release, and contradict each other on what ENDX then reads: nocash says $FF, anomie says 0. The dossier marks the row [CONFLICT] and asks for a golden vector Golden 0x7EF0E2 E10.05 -195 E1.14 E XCN costs five cycles Documented the SNESdev Wiki SPC700 reference and fullsnes both give XCN as 5 cycles, against 2 for NOP Scored 0x7EF0E3 E1.14 -196 E2.04 E DBNZ dp is an RMW Documented SNESdev Wiki SPC700 reference and fullsnes: DBNZ dp,rel reads its operand, decrements it and writes it back, and $FD-$FF are read-to-clear Scored 0x7EF0E4 E2.04 -197 E3.14 E $F8/$F9 are plain RAM Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0E5 E3.14 -198 E3.11c E DSP global registers Documented SNESdev Wiki, S-DSP registers; fullsnes Scored 0x7EF0E6 - -199 E9.19 E ENDX write clears it Documented SNESdev Wiki, S-DSP registers; fullsnes Scored 0x7EF0E7 E9.19 -200 E9.03 E Pitch not noise rate Documented fullsnes and anomie's DSP doc: the noise generator's step rate is set by FLG bits 0-4, and a voice's pitch register does not participate Scored 0x7EF0E8 E9.03 -201 E9.01 E Noise LFSR seed Documented fullsnes and anomie's DSP doc: the noise shift register resets to $4000, with taps bit0 XOR bit1 feeding bit 14 Scored 0x7EF0E9 E9.01 -202 E5.07 E End+mute zeroes env Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0EA E5.07 -203 E5.08 E Loop flag without end Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0EB E5.08 -204 E5.09 E ENDX sets on end block Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0EC E5.09 -205 E5.01 E BRR header layout Documented fullsnes and anomie's DSP doc: the BRR header is ssssffle, shift in bits 7-4, and a nibble decodes as (nibble << shift) >> 1 Scored 0x7EF0ED E5.01 -206 E5.10 E Released voice decodes Documented fullsnes and anomie's DSP doc: key-off begins the release ramp and does not halt BRR decoding, which continues to follow loop points and set ENDX Scored 0x7EF0EE E5.10 -207 E5.11 E Directory entry address Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0EF E5.11 -208 E7.10 E Direct GAIN is envelope Documented SNESdev Wiki, S-DSP envelopes; fullsnes Scored 0x7EF0F0 E7.10 -209 E1.03 E DIV overflow branch Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0F1 E1.03 -210 E1.08 E DAA adjustments Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0F2 E1.08 -211 E2.08 E TCALL vector table Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0F3 E2.08 -212 E2.09 E BRK shares TCALL 0 Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0F4 E2.09 -213 E3.03 E $F1 bit 5 clears port 3 Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0F5 E3.03 -214 E3.04 E Writes pass under IPL Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0F6 E3.04 -215 E3.05 E TnDIV $00 means 256 Documented SNESdev Wiki, SPC700 timers; fullsnes Scored 0x7EF0F7 E3.05 -216 E3.10 E TEST gates RAM writes Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0F8 E3.10 -217 E1.09 E DAS mirrors DAA Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0F9 E1.09 -218 E1.10 E TSET1 is a compare Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0FA E1.10 -219 E1.12 E CLRV clears H too Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0FB E1.12 -220 E2.02 E MOV dp,dp is exempt Documented SNESdev Wiki, SPC700; fullsnes — flagged as errata Scored 0x7EF0FC E2.02 -221 E2.03 E MOVW reads the low byte Documented SNESdev Wiki, SPC700; fullsnes — flagged as errata Scored 0x7EF0FD E2.03 -222 E2.06 E PSW.P selects the page Documented SNESdev Wiki, SPC700 addressing; fullsnes Scored 0x7EF0FE E2.06 -223 E2.07 E CALL pushes exact addr Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0FF E2.07 -224 E4.01 E IPL ROM contents Documented the canonical 64-byte IPL listing; fullsnes, SNESdev Wiki Scored 0x7EF100 E4.01 -225 E4.02 E IPL handoff state Documented fullsnes, SNESdev Wiki, APU boot handshake Scored 0x7EF101 E4.02 -226 E4.03 E IPL zerofills $00-$EF Documented the canonical 64-byte IPL listing zero-fills $0000-$00EF as the first thing it does at $FFC0, before entering its transfer loop; fullsnes and the SNESdev Wiki both carry it Scored 0x7EF102 E4.03 -227 E4.04 E IPL ready announcement Documented fullsnes, SNESdev Wiki, APU boot handshake Scored 0x7EF103 E4.04 -228 E4.06 E IPL multi-block continue Documented fullsnes, SNESdev Wiki; canonical IPL boot ROM $FFEF-$FFFB Scored 0x7EF104 E4.06 -229 E4.08 E IPL DSP-poke via $00F2 Documented fullsnes (APU / S-DSP); canonical IPL boot ROM store loop Scored 0x7EF105 E4.08 -230 E5.12 E SRCN change source Documented fullsnes and anomie's DSP doc: a mid-playback SRCN change takes the new entry's start address, or its loop address if the voice has already passed a loop point Scored 0x7EF106 E5.12 -231 E5.02 E BRR nibbles are signed Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF107 E5.02 -232 E7.16 E OUTX is pre-volume Documented fullsnes, S-DSP envelopes; anomie's DSP doc Scored 0x7EF108 E7.16 -233 E8.04 E KOFF outranks KON Documented fullsnes, S-DSP key on/off; anomie's DSP doc Scored 0x7EF109 E8.04 -234 E8.05 E KON is edge-triggered Documented fullsnes and anomie's DSP doc: KON is write-triggered and self-clears ~63 clocks later; only KOFF and FLG bit 7 are level-sensitive Scored 0x7EF10A E8.05 -235 E9.04 E Noise voices decode BRR Documented fullsnes, S-DSP noise; anomie's DSP doc — flagged as errata Scored 0x7EF10B E9.04 -236 E9.06 E EDL 0 is a 4-byte buffer Documented fullsnes, S-DSP echo — flagged as errata; anomie's DSP doc Scored 0x7EF10C E9.06 -237 E9.12 E Echo writes are masked Documented fullsnes, S-DSP echo; anomie's DSP doc Scored 0x7EF10D E9.12 -238 E9.15 E Voice mix saturates Documented fullsnes and anomie's DSP doc: the per-voice mix clamps to 16 bits after each addition rather than accumulating and clamping once Scored 0x7EF10E E9.15 -239 E9.05 E Echo entry is 4 bytes Documented fullsnes and anomie's DSP doc: the echo buffer holds four bytes per sample, a 16-bit left sample followed by a 16-bit right one Scored 0x7EF10F E9.05 -240 E9.13 E L/R FIR independent Documented fullsnes and anomie's DSP doc: the echo FIR keeps a separate eight-sample history and accumulator per channel; only the coefficients are shared Scored 0x7EF110 E9.13 -241 E9.10 E FLG.5 stops echo writes Documented fullsnes, S-DSP echo; anomie's DSP doc Scored 0x7EF111 E9.10 -242 E9.17 E Mute is after OUTX Documented fullsnes, S-DSP FLG; anomie's DSP doc Scored 0x7EF112 E9.17 -243 E9.18 E FLG reset kills voices Documented SNESdev Wiki, S-DSP; fullsnes Scored 0x7EF113 E9.18 -244 E5.03 E BRR sample arithmetic Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF114 E5.03 -245 E5.04 E Invalid shift collapses Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF115 E5.04 -246 E5.05 E BRR filter 1 Documented fullsnes, S-DSP BRR filters; anomie's DSP doc Scored 0x7EF116 E5.05 -247 E5.13 E Released voices decode Documented fullsnes and anomie's DSP doc: the BRR decoder advances on the pitch clock regardless of the envelope; voices never actually stop decoding Scored 0x7EF117 E5.13 -248 E7.01 E Rate 0 never fires Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF118 E7.01 -249 E7.13 E GAIN bent-increase Documented fullsnes and anomie's DSP doc: GAIN mode 7 increases +32 per sample below $600 and +8 above, comparing the internal envelope latch unsigned Scored 0x7EF119 E7.13 -250 E7.17 E Lin-decrease clamps 0 Documented fullsnes and anomie's DSP doc: GAIN linear-decrease subtracts $20 per tick and clamps the envelope to zero on underflow, comparing the internal envelope unsigned Scored 0x7EF11A E7.17 -251 E7.18 E ENVX is E>>4 Documented fullsnes and anomie's DSP doc: VxENVX = envelope >> 4, a seven-bit value with bit 7 always clear Scored 0x7EF11B E7.18 -252 E7.04 E Attack $F is instant Documented fullsnes and anomie's DSP doc: attack rate $F fires every sample with a step of +1024, rather than the +32 on a counter tick every other rate uses Scored 0x7EF11C E7.04 -253 E7.09 E Release rate is fixed Documented fullsnes and anomie's DSP doc: release steps -8 per sample regardless of ADSR, which is why a custom release has to be built from GAIN Scored 0x7EF11D E7.09 -254 E7.05 E Decay index d*2+16 Documented fullsnes and anomie's DSP doc: the decay phase indexes the counter table at d*2+16, stepping E -= 1 then E -= E>>8 Scored 0x7EF11E E7.05 -255 E7.06 E Sustain index verbatim Documented fullsnes and anomie's DSP doc: the sustain phase indexes the counter table with the ADSR2 rate field directly, unlike decay's d*2+16 Scored 0x7EF11F E7.06 -256 E7.07 E Sustain boundary Documented fullsnes and anomie's DSP doc: the decay phase ends when (E >> 8) equals the ADSR2 sustain level, giving a boundary of $100 per level Scored 0x7EF120 E7.07 -257 E7.03 E Attack index a*2+1 Documented fullsnes and anomie's DSP doc: the attack phase indexes the counter table at a*2+1 and steps the envelope by +32 Scored 0x7EF121 E7.03 -258 E7.12 E GAIN sustain boundary Contested the dossier records the GAIN-sourced boundary as [ERRATA]; RustySNES takes it from ADSR2 instead, which E7.07 asserts, so which behaviour is right is not settled here Golden 0x7EF122 E7.12 -259 E7.08 E Key-off releases to zero Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF123 E7.08 -260 E8.02 E Key-on takes 5 samples Documented fullsnes and anomie's DSP doc: KON is held for five output samples while the directory and the first BRR block are fetched, before the envelope starts Scored 0x7EF124 E8.02 -261 E8.03 E KON restarts a voice Documented fullsnes and anomie's DSP doc: KON re-enters the key-on sequence unconditionally, resetting the BRR pointer and zeroing the envelope, whether or not the voice was already playing Scored 0x7EF125 E8.03 -262 E8.07 E KOFF pulse collapses Contested KON/KOFF are sampled every second output sample, so whether a short pulse is seen depends on where the poll falls inside it -- which makes the outcome phase-dependent rather than fixed, the same hedge E8.05 and E8.06 carry as "usually" Golden 0x7EF126 E8.07 -263 E8.10 E KOFF+KON cuts faster Documented fullsnes and anomie's DSP doc: KON zeroes the envelope before its attack, and KOFF outranks KON, so the pair silences immediately where KOFF alone ramps Scored 0x7EF127 E8.10 -264 E7.11 E GAIN linear increase Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF128 E7.11 -265 E7.14 E GAIN decrease clamps Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF129 E7.14 -266 E7.15 E ENVX is E >> 4 Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF12A E7.15 -267 E6.02 E Pitch $1000 is 1:1 Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12B E6.02 -268 E6.02b E Pitch $1000 does finish Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12C E6.02 -269 E6.02c E Pitch $2000 is +1 octave Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12D E6.02 -270 E6.02d E Pitch $2000 upper bound Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12E E6.02 -271 E3.06 E T2 is eight times T0 Documented SNESdev Wiki, SPC700 timers; fullsnes Scored 0x7EF12F E3.06 -272 E3.08 E TEST bit 0 halts timers Documented fullsnes, SPC700 TEST register; ares and bsnes smp/timing Scored 0x7EF130 E3.08 -273 F1.01 F Manual pad read order Documented fullsnes and the SNESdev Wiki controller protocol: the shift register presents B, Y, Select, Start, Up, Down, Left, Right, A, X, L, R and then four zero bits Scored 0x7EF131 F1.01 -274 F1.02 F Pad reads 17+ are 1 Documented SNESdev Wiki, controller protocol; fullsnes Scored 0x7EF132 F1.02 -275 F1.03 F Shared $4016 latch Documented fullsnes and the SNESdev Wiki controller protocol: bit 0 of $4016 is the shared latch line that parallel-loads both controller ports' shift registers Scored 0x7EF133 F1.03 -276 F1.04 F $4016 bits 7-2 open bus Corroborated RustySNES, snes9x and Mesen2 all return $41 for the absolute read and $01 for the long one -- identical bytes, so bits 7-2 follow the CPU bus in all three Scored 0x7EF134 F1.04 -277 F1.07 F Auto-read needs $4200.0 Documented fullsnes and the SNESdev Wiki: bit 0 of $4200 arms the automatic joypad read, and with it clear $4218-$421F are not written Scored 0x7EF135 F1.07 -278 F1.05 F Pad signature is 0000 Documented fullsnes and the SNESdev Wiki: bits 3-0 of the auto-read result identify the device, and a standard controller reports 0000 Scored 0x7EF136 F1.05 -279 F1.06 F First bit clocked is B Documented fullsnes and the SNESdev Wiki: the auto-read result holds the sixteen shifted bits in clock order, most significant first, so B is bit 15 Scored 0x7EF137 F1.06 -280 F1.08 F Auto-read start dot Documented fullsnes: the automatic joypad read begins ~dot 32.5-95.5 of the first vblank line (RustySNES delays it 256 master clocks ~ dot 64), not at the vblank edge Golden 0x7EF138 F1.08 -281 F1.09 F Auto-read duration Documented fullsnes: the automatic joypad read is busy for exactly 4224 master cycles (~3.097 scanlines); the poll count is a CPU-cycle-proportional proxy for it Golden 0x7EF139 F1.09 -282 F1.10 F Auto-read start race Documented fullsnes: the automatic joypad read begins ~dot 32.5-95.5 of the first vblank line, not at the vblank edge, so $4212 bit 0 reads not-busy for that window and a $4212 poll at NMI entry sees the read not yet started Scored 0x7EF13A F1.10 -283 F1.11 F Latch corrupts auto-read Documented fullsnes and the SNESdev Wiki: while $4016 bit 0 is high the shift registers reload continuously rather than shifting, so an automatic read taken across it returns the same bit in every position Scored 0x7EF13B F1.11 -284 F1.12 F Auto-read result timing Contested F1.12 says results are valid by V = $E3, which does not reconcile with F1.09's 4224-cycle duration and F1.08's start window; no source says which to believe, and the cores split on whether the result appears at once or progressively Golden 0x7EF13C F1.12 -285 F1.14 F $4213 reads $4201 back Documented fullsnes: RDIO reads the WRIO output pins, which are open-collector, so with nothing driving them low the value read is the value written Scored 0x7EF13D F1.14 -286 G1.02 G Reset: $4210/$4211 clear Documented SNESdev Wiki, power-on state; fullsnes Scored 0x7EF13E G1.02 -287 G1.04 G Reset: emulation mode Documented SNESdev Wiki, power-on state; WDC 65C816 datasheet, XCE Scored 0x7EF13F G1.04 -288 G1.05 G Power-on PPU registers Documented the dossier marks the PPU power-on state indeterminate ('no boot ROM; most PPU registers start unknown') and says to report it, never assert; the readable registers ($2134-$2136, $213E/$213F) are reported and the only scored check is the self-guard that the power-on capture actually ran Scored 0x7EF140 G1.05 -289 G1.08 G Write-only read: openbus Documented SNESdev Wiki, open bus; fullsnes, memory map notes Scored 0x7EF141 G1.08 -290 G1.10 G Checksum XOR complement Documented SNESdev Wiki, cartridge header; fullsnes Scored 0x7EF142 G1.10 -291 G1.11 G Checksum over the image Documented SNESdev Wiki, cartridge header checksum; fullsnes Scored 0x7EF143 G1.11 -292 G1.12 G LoROM header location Documented SNESdev Wiki, cartridge header; fullsnes Scored 0x7EF144 G1.12 -293 G1.14 G LoROM bank decode Documented SNESdev Wiki, memory map; fullsnes Scored 0x7EF145 G1.14 -294 G1.19 G Power-on $4201/timers Documented fullsnes and the SNESdev Wiki power-on table: $4201 = $FF, HTIME and VTIME = $1FF; the other registers in the row are observed by B5.05 and B1.01 Scored 0x7EF146 G1.01 -295 G1.20 G Power-on indeterminate Contested the dossier marks the whole row [UNDEFINED] and says to report it and never assert it; half the registers it names are write-only and cannot be reported at all Golden 0x7EF147 G1.03 -296 G1.07 G WRAM power-on fill Contested the dossier marks the row [UNDEFINED] and asks for a golden vector by name: no canonical WRAM fill exists and real consoles disagree Golden 0x7EF148 G1.07 -297 A5.S01 A Sweep: CLC Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF149 A5.01-08 -298 A5.S02 A Sweep: SEC Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14A A5.01-08 -299 A5.S03 A Sweep: CLV Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14B A5.01-08 -300 A5.S04 A Sweep: INX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14C A5.01-08 -301 A5.S05 A Sweep: DEX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14D A5.01-08 -302 A5.S06 A Sweep: TAX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14E A5.01-08 -303 A5.S07 A Sweep: TXY Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14F A5.01-08 -304 A5.S08 A Sweep: ASL A Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF150 A5.01-08 -305 A5.S09 A Sweep: XBA Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF151 A5.01-08 -306 A5.S10 A Sweep: TCD Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF152 A5.01-08 -307 A5.S11 A Sweep: LDA #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF153 A5.01-08 -308 A5.S12 A Sweep: LDX #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF154 A5.01-08 -309 A5.S13 A Sweep: CMP #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF155 A5.01-08 -310 A5.S14 A Sweep: BIT #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF156 A5.01-08 -311 A5.S15 A Sweep: REP #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF157 A5.01-08 -312 A5.S16 A Sweep: SEP #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF158 A5.01-08 -313 A5.S17 A Sweep: WDM Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF159 A5.01-08 -314 A5.S18 A Sweep: PHA+PLA Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15A A5.01-08 -315 A5.S19 A Sweep: PHP+PLP Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15B A5.01-08 -316 A5.S20 A Sweep: PHB+PLB Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15C A5.01-08 -317 A5.S21 A Sweep: PHD+PLD Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15D A5.01-08 -318 A5.S22 A Sweep: PHX+PLX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15E A5.01-08 -319 A5.S23 A Sweep: LDA dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15F A5.01-08 -320 A5.S24 A Sweep: LDA abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF160 A5.01-08 -321 A5.S25 A Sweep: LDA long Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF161 A5.01-08 -322 A5.S26 A Sweep: STA dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF162 A5.01-08 -323 A5.S27 A Sweep: STA abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF163 A5.01-08 -324 A5.S28 A Sweep: LDA dp,X Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF164 A5.01-08 -325 A5.S29 A Sweep: LDA abs,X Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF165 A5.01-08 -326 A5.S30 A Sweep: INC dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF166 A5.01-08 -327 A5.S31 A Sweep: INC abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF167 A5.01-08 -328 A5.S32 A Sweep: ADC dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF168 A5.01-08 -329 A5.S33 A Sweep: CMP abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF169 A5.01-08 -330 A5.S34 A Sweep: BVS untaken Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16A A5.01-08 -331 A5.S35 A Sweep: BRL flat 4 Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16B A5.16 +71 A5.19 A RTI 7 native, 6 emul Documented WDC datasheet instruction-operation table; docs/accuracysnes-timing-oracle.md Scored 0x7EF067 A5.19 +72 A6.11 A WAI wakes, no vector Documented WDC datasheet: WAI wakes on the interrupt line; I gates the vector Scored 0x7EF068 A6.11 +73 A8.07 A MVN interruptible Documented WDC datasheet: MVN rewinds PC by 3 per iteration, so RTI re-enters Scored 0x7EF069 A8.06 +74 A6.12 A WAI wake lat (golden) Contested the stated 1-cycle latency is 1.5 dots, below what a cart can resolve; recorded in buckets rather than asserted Golden 0x7EF06A A6.12 +75 A4.11 A JMP (a,X) ptr bank Documented SNESdev Errata, 65C816 section (worked example PBR=$05) Scored 0x7EF06B A4.04 +76 A4.12 A JSR (a,X) ptr bank Documented SNESdev Errata, 65C816 section Scored 0x7EF06C A4.05 +77 A6.13 A IRQ handler PBR = $00 Documented WDC datasheet: the vector is 16-bit, so the handler runs in bank 0 Scored 0x7EF06D A6.09 +78 A6.14 A RTI pull matches mode Documented WDC datasheet: RTI pulls PBR in native mode only Scored 0x7EF06E A6.10 +79 A2.13 A 16-bit dp page cross Contested superfamicom.org describes the wrap as happening "theoretically"; the dossier records the row as UNVERIFIED, so it is observed rather than asserted Golden 0x7EF06F A2.05 +80 C1.01 C OAM word write/read Documented SNESdev Wiki, OAM; fullsnes Scored 0x7EF070 C1.02 +81 C1.02 C OAM odd write latched Documented SNESdev Wiki, OAM; anomie Scored 0x7EF071 C1.02 +82 C1.03 C OAMADDR reload clears Documented SNESdev Wiki, OAM; anomie Scored 0x7EF072 C1.01 +83 C1.04 C OAM rd/wr one counter Documented SNESdev Wiki, OAM Scored 0x7EF073 C1.05 +84 C1.05 C OAM high table mirror Documented SNESdev Wiki, OAM; fullsnes Scored 0x7EF074 C1.04 +85 C1.03b C High table commits bytes Documented SNESdev Wiki, OAM; fullsnes Scored 0x7EF075 C1.03 +86 C1.07 C Blank edge reloads OAM Documented SNESdev Wiki, OAM; fullsnes Scored 0x7EF076 C1.07 +87 C7.09 C Overflow flags clear Documented SNESdev Wiki, sprites; fullsnes Scored 0x7EF077 C7.09 +88 C2.01 C VMAIN step 1 word Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF078 C2.01 +89 C2.02 C VMAIN low-byte trigger Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF079 C2.02 +90 C2.03 C VRAM read prefetch Documented SNESdev Wiki; docs/ppu.md edge case 4 Scored 0x7EF07A C2.08 +91 C2.04 C VRAM bit 15 unconnected Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF07B C2.07 +92 C2.05 C VMAIN step 32 words Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF07C C2.01 +93 C2.06 C VMAIN remap hits bus Documented SNESdev Wiki, PPU registers; fullsnes; anomie Scored 0x7EF07D C2.03-05,C2.06 +94 C2.12 C Blank off closes VRAM Documented SNESdev Wiki, VRAM access; fullsnes Scored 0x7EF07E C2.12 +95 C3.03b C CGRAM read bit 7 is bus Documented SNESdev Wiki, CGRAM; fullsnes Scored 0x7EF07F C3.03 +96 C3.09 C $213F is a field flag Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF080 C3.09 +97 C3.01 C CGRAM two-write commit Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF081 C3.01 +98 C3.02 C CGADD resets flipflop Documented SNESdev Wiki, PPU registers Scored 0x7EF082 C3.02 +99 C3.03 C OPHCT is a 9-bit pair Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF083 C3.06 +100 C3.04 C H counter advances Documented SNESdev Wiki, PPU registers Scored 0x7EF084 - +101 C3.05 C $213F resets flipflop Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF085 C3.08 +102 C3.07 C Counter flipflops differ Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF086 C3.07 +103 C3.12 C CGRAM taken in render Documented fullsnes and the SNESdev Wiki: a CGRAM access during active display uses the colour the PPU is drawing, not the CPU CGADD. Mesen2 models it (writes use InternalCgramAddress when !CanAccessCgram); the batch compositor and snes9x use the programmed CGADD and fail Scored 0x7EF087 C3.04 +104 C13.01 C PPU1 open bus in $213E Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF088 C13.07,C13.10 +105 C13.02 C PPU2 open bus in $213F Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF089 C13.08,C13.10 +106 C13.03 C PPU1/PPU2 bus separate Corroborated the bsnes/ares lineage and Mesen2 model two distinct latches Scored 0x7EF08A C13.09 +107 C14.01 C PPU1 version (golden) Documented SNESdev Wiki, PPU registers; fullsnes Golden 0x7EF08B C14.01 +108 C14.02 C PPU2 version (golden) Documented SNESdev Wiki, PPU registers; fullsnes Golden 0x7EF08C C14.02 +109 C14.03 C PPU1 mstr/slv (golden) Contested the bit reports a board wiring input, so no software-visible value is correct or incorrect; recorded rather than asserted Golden 0x7EF08D C14.03 +110 C11.06 C MPY is 16x8 signed Documented SNESdev Wiki, Mode 7; fullsnes Scored 0x7EF08E C11.06 +111 C11.06b C MPY sign handling Documented SNESdev Wiki, Mode 7; fullsnes Scored 0x7EF08F C11.06 +112 C11.07 C MPY latch is shared Documented fullsnes and the SNESdev Wiki: PPU1's write-twice registers share one byte latch, so $210D/$210E and $211B-$211E interfere with each other Scored 0x7EF090 C11.07 +113 C11.08 C MPY busy during render Contested fullsnes and the SNESdev Wiki agree the Mode 7 multiplier is the renderer's and is busy during active display, but neither states which intermediate it holds at a given moment, and that is the only thing a cart can observe Golden 0x7EF091 C11.08 +114 C7.01 C Range Over at 32 sprites Documented SNESdev Wiki, Sprites; fullsnes Scored 0x7EF092 C7.01 +115 C7.04 C Offscreen X takes a slot Documented fullsnes and the SNESdev Wiki: sprite range evaluation selects on Y alone, so a sprite at X = $100 occupies a range slot despite being entirely off-screen Scored 0x7EF093 C7.04 +116 C7.02 C Time Over is slivers Documented SNESdev Wiki, Sprites; fullsnes; anomie Scored 0x7EF094 C7.02 +117 C7.08 C Flags ignore $212C Documented SNESdev Wiki, Sprites; fullsnes Scored 0x7EF095 C7.08 +118 C7.10 C OAM write to high table Documented fullsnes and the SNESdev Wiki (the Uniracers case): an OAM write during active display is driven to the evaluator's address and lands in the high table, not the CPU OAMADDR. Mesen2 models it (oamAddr = 0x200 | ((oamAddr & 0x1F0) >> 4)); the batch compositor and snes9x write the programmed OAMADDR and fail Scored 0x7EF096 C7.16 +119 C2.11 C VRAM locked in render Documented SNESdev Wiki, PPU registers; fullsnes Scored 0x7EF097 C2.11 +120 C1.08 C OAM addr in render Documented fullsnes and the SNESdev Wiki: during active display the renderer drives the OAM address, so a $2138 read returns a render-time address, not the CPU-programmed one. MesenCE models it (GetOamAddress -> _oamEvaluationIndex<<2); the batch compositor and snes9x return the programmed address and fail Scored 0x7EF098 C1.08 +121 C2.10 C Dropped write still incs Documented SNESdev Wiki, PPU registers; fullsnes; anomie Scored 0x7EF099 C2.10 +122 C1.06 C OAM addr reloads Documented SNESdev Wiki, OAM; anomie Scored 0x7EF09A C1.06 +123 C9.04 C Overscan moves vblank Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF09B C9.04,B4.02 +124 C9.05 C Mid-frame overscan lock Contested RustySNES and snes9x re-close the VRAM window on a mid-frame overscan enable and Mesen2 does not, so the references split one each way; the dossier's repro cannot break the tie because it read-modify-writes the write-only $2133 Golden 0x7EF09C C9.05 +125 C2.09 C VRAM read latch order Documented SNESdev Wiki, PPU registers: return latch, refill latch, then increment, with VMAIN bit 7 selecting which of $2139/$213A triggers it; bsnes and ares both refill before the step in sfc/ppu/io.cpp, and snes9x and Mesen2 agree Scored 0x7EF09D C2.09 +126 C3.10 C $2137 latch is gated Documented superfamicom.org registers: $2137 latches the H/V counter only if $4201 bit 7 is set, and no latching can occur while it is 0; snes9x and Mesen2 both gate it. What the read returns is a separate question, split out into C3.11 Scored 0x7EF09E C3.05 +127 C3.11 C $2137 open bus source Contested the sources say only that $2137 reads back as open bus; snes9x and RustySNES present PPU1's latch while Mesen2 presents the CPU's, and nothing available decides between two physically reasonable readings Golden 0x7EF09F C3.05 +128 B1.01 B MEMSEL selects FastROM Documented SNESdev Wiki, Memory map / timing; fullsnes Scored 0x7EF0A0 B1.01 +129 B1.02 B JOYSER is 12 clocks Documented SNESdev Wiki, Memory map / timing; fullsnes Scored 0x7EF0A1 B1.02 +130 B2.04 B NTSC frame is 262 lines Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A2 B2.04 +131 B4.03 B RDNMI sets at vblank Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A3 B4.03 +132 B2.01 B No dot above 339 Corroborated fullsnes' PPU H-Counter-Latch Quantities histogram, a direct hardware measurement: dots 323 and 327 latch six times, dot 340 never. bsnes, ares and Mesen2 all implement it; snes9x uses 322/326 and is the outlier Scored 0x7EF0A4 B2.01 +133 B4.16 B H-IRQ position (golden) Contested no source pins the fired dot at single-dot precision; recorded as the before/after guard for T-06-A's clock-domain comparator change Golden 0x7EF0A5 - +134 B4.17 B NMI enable is a level Documented SNESdev Wiki NMITIMEN/RDNMI [ERRATA]; fullsnes $4200/$4210 Scored 0x7EF0A6 B4.06 +135 B4.04 B RDNMI is read-to-clear Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A7 B4.04 +136 B4.05 B RDNMI auto-clears Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A8 B4.05 +137 B4.08 B V-IRQ fires at VTIME Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0A9 B4.08 +138 B4.12 B $4211 read releases IRQ Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0AA B4.12 +139 B4.15 B CPU revision (golden) Documented SNESdev Wiki, Timing; fullsnes Golden 0x7EF0AB G1.09 +140 B5.01 B 8x8 unsigned multiply Documented SNESdev Wiki, CPU registers; fullsnes Scored 0x7EF0AC B5.01 +141 B5.02 B 16/8 unsigned divide Documented SNESdev Wiki, CPU registers; fullsnes Scored 0x7EF0AD B5.02 +142 B5.03 B Divide by zero Documented SNESdev Wiki, CPU registers; fullsnes Scored 0x7EF0AE - +143 B5.04 B Mul/div overlap (golden) Contested SNESdev Errata states overlapping $4203/$4206 operation is undefined Golden 0x7EF0AF B5.03 +144 B5.05 B Mul/div power-on state Documented anomie regs.txt r1157 and nocash fullsnes, independently; implemented by bsnes/ares/Mesen2. No known hardware test ROM Scored 0x7EF0B0 B5.04 +145 B1.03 B Internal cycles are 6 Documented SNES Development Manual Bk I 21.1; SNESdev Wiki; fullsnes Scored 0x7EF0B1 B1.03 +146 B1.04 B DMA speed is uniform Documented SNES Development Manual Bk I 21.1 (DMA at 2.68MHz regardless of address) Scored 0x7EF0B2 B1.04 +147 B2.06 B Interlace line count Contested the dossier conditions the extra line on $213F.7 (the field), which this test does not control — sampling V across an uncontrolled field cannot assert a line count Golden 0x7EF0B3 B2.06 +148 B2.05 B PAL frame is 312 lines Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0B4 B2.05 +149 B4.14 B IRQ dispatch latency Documented SNESdev Wiki, Timing; fullsnes — the sub-cycle poll point is not CPU-observable, so its consequence is measured instead Golden 0x7EF0B5 B4.14 +150 B2.10 B Region bit (golden) Contested SNESdev PPU registers places the 50/60Hz bit at bit 3, which overlaps the PPU2 version field; fullsnes places it at bit 4 Golden 0x7EF0B6 B2.10 +151 B4.07 B H-IRQ position (golden) Contested the $4211 poll loop is coarser than the dot the comparator fires on, so the exact H position is not resolvable from software by polling Golden 0x7EF0B7 B4.07 +152 B4.09 B HV-IRQ needs both Documented SNESdev Wiki, Timing; fullsnes Scored 0x7EF0B8 B4.09 +153 B3.01 B DRAM refresh pause Contested fullsnes and anomie put the pause at 40 clocks near line-clock 536, and RustySNES now models it there (docs/dram-refresh.md), so this loop records variant 2 (one interval ~10 dots longer); ares' own source still calls its refresh *pattern* technically wrong and only right on average, so the exact per-line position stays host-cross-validated rather than self-asserted Golden 0x7EF0B9 B3.01,B3.02,B3.03 +154 B4.13 B Timer range is 9-bit Documented fullsnes $4207-$420A: HTIME is 0-339 and VTIME 0-261 (NTSC) / 0-311 (PAL), both held in nine bits Scored 0x7EF0BA B4.13 +155 B4.11 B Dot 153, last line Contested superfamicom.org's timing page states the exception and gives no mechanism; its timing text derives from fullsnes, so the two are one source, and no test ROM verifies it. ares, bsnes, Mesen2 and snes9x were each searched and none implements it Golden 0x7EF0BB B4.11 +156 D1.01 D DMA mode 0 Documented SNESdev Wiki, DMA; fullsnes Scored 0x7EF0BC D1.01 +157 D1.01b D DMA mode 1 Documented SNESdev Wiki, DMA; fullsnes Scored 0x7EF0BD D1.01 +158 D1.06 D DMA count hits zero Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0BE D1.06 +159 D1.07 D DMA fixed A-bus Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0BF D1.07 +160 D1.07b D DMA decrementing A-bus Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0C0 D1.07 +161 D1.10 D DMA $43xB scratch latch Corroborated ares and bsnes both model the latch and serialize it Scored 0x7EF0C1 D1.10 +162 D1.02 D DMA 8 clocks/byte Documented SNESdev Wiki, DMA timing; fullsnes Scored 0x7EF0C2 D1.02 +163 D1.05 D DMA count 0 = 65536 Documented SNESdev Wiki, DMA registers; fullsnes Scored 0x7EF0C3 D1.05 +164 D1.09 D WRAM->$2180 no-write Documented fullsnes: "does not cause a write to occur" Scored 0x7EF0C4 D1.09,D1.15 +165 D2.03 D HDMA line-count byte Documented SNESdev Wiki, HDMA; fullsnes Scored 0x7EF0C5 D2.03 +166 D2.04 D HDMA repeat flag Documented SNESdev Wiki, HDMA; fullsnes Scored 0x7EF0C6 D2.04 +167 D2.07 D HDMA preempts GP-DMA Documented SNESdev Wiki, HDMA; anomie's timing doc; fullsnes Scored 0x7EF0C7 D2.07 +168 D2.09 D HDMA armed mid-frame Contested fullsnes and the SNESdev Wiki record that enabling HDMA outside vblank produces erroneous writes from uninitialised A2An/NLTRn, but what those writes contain is a function of the previous frame's leftover state and is specified nowhere Golden 0x7EF0C8 D2.09 +169 D1.14 D $2180 B->A does write Documented fullsnes: $2180->WRAM writes, but the value written is invalid Scored 0x7EF0C9 D1.14 +170 D1.13 D DMA count hits zero Documented fullsnes and ares: the DAS $43x5/6 byte-count register decrements as GP-DMA transfers and reads $0000 when the transfer completes Scored 0x7EF0CA D1.13 +171 D1.11 D DMA power-on state Corroborated fullsnes register table and the SNESdev DMA-registers page agree independently; ares and bsnes default every channel field to match Scored 0x7EF0CB D1.11 +172 D1.08 D Invalid A-bus (golden) Contested the errata names the ranges invalid but does not specify what is read instead; the substitute is open bus, whose content is core-specific and time-dependent Golden 0x7EF0CC D1.08 +173 D1.03 D DMA startup overhead Documented SNESdev Wiki, DMA timing; fullsnes Golden 0x7EF0CD D1.03 +174 D1.04 D DMA channel priority Documented SNESdev Wiki, DMA; fullsnes Scored 0x7EF0CE D1.04 +175 D2.05 D HDMA indirect mode Documented SNESdev Wiki, HDMA; fullsnes Scored 0x7EF0CF D2.05 +176 D2.06 D HDMA $4308/$430A state Documented SNESdev Wiki, HDMA registers; fullsnes Scored 0x7EF0D0 D2.06 +177 E4.11 E ARAM power-on pattern Contested the dossier records a repeating 32x$00 / 32x$FF fill and marks it chip-dependent and informational; RustySNES, snes9x and Mesen2 all boot APU RAM uniformly zero instead Golden 0x7EF0D1 E4.11 +178 E1.01 E MUL YA flags from Y Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0D2 E1.01 +179 E1.02 E DIV YA,X normal branch Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D3 E1.02 +180 E1.07 E DIV valid to Q<=511 Documented SNESdev Wiki SPC700 reference and fullsnes, both flagging DIV as valid only for quotients up to 511; the values past it follow E1.03's overflow formula Scored 0x7EF0D4 E1.07 +181 E1.04 E DIV H = nibble compare Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0D5 E1.04 +182 E1.05 E DIV V is quotient bit 8 Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D6 E1.05 +183 E1.06 E DIV flags from quotient Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0D7 E1.06 +184 E1.13 E ADDW H = bit-11 carry Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D8 E1.13 +185 E1.15 E MOVW YA sets 16-bit N/Z Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0D9 E1.15 +186 E3.01 E Timer read clears it Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0DA E3.01 +187 E3.02 E Timer enable 0->1 resets Documented SNESdev Wiki SPC700 timers and fullsnes: a 0->1 on a $F1 timer-enable bit resets that timer's stage-2 divider and stage-3 output counter Scored 0x7EF0DB E3.02 +188 E3.11 E $F2 bit 7 blocks writes Documented SNESdev Wiki, S-DSP; fullsnes Scored 0x7EF0DC E3.11 +189 E3.11b E DSP register addressing Documented SNESdev Wiki, S-DSP registers; fullsnes Scored 0x7EF0DD - +190 E2.01 E Store dummy-reads target Documented SNESdev Wiki, SPC700; fullsnes — flagged as errata Scored 0x7EF0DE E2.01 +191 E2.05 E DP index wraps in page Documented SNESdev Wiki, SPC700 addressing; fullsnes Scored 0x7EF0DF E2.05 +192 E6.09 E Gaussian sum wraps Documented fullsnes and anomie's DSP doc: of the four gaussian additions the first cannot overflow, the second wraps in 16 bits and the third saturates Scored 0x7EF0E0 E6.09 +193 E6.11 E BRR waveform vectors Contested the dossier names four nibble patterns and asks what a decoder makes of each without stating an expected value for any of them; the row's content is the measurement Golden 0x7EF0E1 E6.11 +194 E10.01 E 32 SPC cycles per sample Documented fullsnes and anomie: the SPC700's 1.024 MHz clock and the DSP's 32 kHz output rate fix the ratio at 32 CPU cycles per output sample Scored 0x7EF0E2 E10.01 +195 E10.05 E Soft reset acts as $E0 Contested both sources agree FLG bit 7 makes the DSP behave as $E0 and force every voice into release, and contradict each other on what ENDX then reads: nocash says $FF, anomie says 0. The dossier marks the row [CONFLICT] and asks for a golden vector Golden 0x7EF0E3 E10.05 +196 E1.14 E XCN costs five cycles Documented the SNESdev Wiki SPC700 reference and fullsnes both give XCN as 5 cycles, against 2 for NOP Scored 0x7EF0E4 E1.14 +197 E2.04 E DBNZ dp is an RMW Documented SNESdev Wiki SPC700 reference and fullsnes: DBNZ dp,rel reads its operand, decrements it and writes it back, and $FD-$FF are read-to-clear Scored 0x7EF0E5 E2.04 +198 E3.14 E $F8/$F9 are plain RAM Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0E6 E3.14 +199 E3.11c E DSP global registers Documented SNESdev Wiki, S-DSP registers; fullsnes Scored 0x7EF0E7 - +200 E9.19 E ENDX write clears it Documented SNESdev Wiki, S-DSP registers; fullsnes Scored 0x7EF0E8 E9.19 +201 E9.03 E Pitch not noise rate Documented fullsnes and anomie's DSP doc: the noise generator's step rate is set by FLG bits 0-4, and a voice's pitch register does not participate Scored 0x7EF0E9 E9.03 +202 E9.01 E Noise LFSR seed Documented fullsnes and anomie's DSP doc: the noise shift register resets to $4000, with taps bit0 XOR bit1 feeding bit 14 Scored 0x7EF0EA E9.01 +203 E5.07 E End+mute zeroes env Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0EB E5.07 +204 E5.08 E Loop flag without end Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0EC E5.08 +205 E5.09 E ENDX sets on end block Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0ED E5.09 +206 E5.01 E BRR header layout Documented fullsnes and anomie's DSP doc: the BRR header is ssssffle, shift in bits 7-4, and a nibble decodes as (nibble << shift) >> 1 Scored 0x7EF0EE E5.01 +207 E5.10 E Released voice decodes Documented fullsnes and anomie's DSP doc: key-off begins the release ramp and does not halt BRR decoding, which continues to follow loop points and set ENDX Scored 0x7EF0EF E5.10 +208 E5.11 E Directory entry address Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF0F0 E5.11 +209 E7.10 E Direct GAIN is envelope Documented SNESdev Wiki, S-DSP envelopes; fullsnes Scored 0x7EF0F1 E7.10 +210 E1.03 E DIV overflow branch Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0F2 E1.03 +211 E1.08 E DAA adjustments Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0F3 E1.08 +212 E2.08 E TCALL vector table Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0F4 E2.08 +213 E2.09 E BRK shares TCALL 0 Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0F5 E2.09 +214 E3.03 E $F1 bit 5 clears port 3 Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0F6 E3.03 +215 E3.04 E Writes pass under IPL Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0F7 E3.04 +216 E3.05 E TnDIV $00 means 256 Documented SNESdev Wiki, SPC700 timers; fullsnes Scored 0x7EF0F8 E3.05 +217 E3.10 E TEST gates RAM writes Documented SNESdev Wiki, SPC700 I/O; fullsnes Scored 0x7EF0F9 E3.10 +218 E1.09 E DAS mirrors DAA Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF0FA E1.09 +219 E1.10 E TSET1 is a compare Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0FB E1.10 +220 E1.12 E CLRV clears H too Documented SNESdev Wiki, SPC700 reference; fullsnes — flagged as errata Scored 0x7EF0FC E1.12 +221 E2.02 E MOV dp,dp is exempt Documented SNESdev Wiki, SPC700; fullsnes — flagged as errata Scored 0x7EF0FD E2.02 +222 E2.03 E MOVW reads the low byte Documented SNESdev Wiki, SPC700; fullsnes — flagged as errata Scored 0x7EF0FE E2.03 +223 E2.06 E PSW.P selects the page Documented SNESdev Wiki, SPC700 addressing; fullsnes Scored 0x7EF0FF E2.06 +224 E2.07 E CALL pushes exact addr Documented SNESdev Wiki, SPC700 reference; fullsnes Scored 0x7EF100 E2.07 +225 E4.01 E IPL ROM contents Documented the canonical 64-byte IPL listing; fullsnes, SNESdev Wiki Scored 0x7EF101 E4.01 +226 E4.02 E IPL handoff state Documented fullsnes, SNESdev Wiki, APU boot handshake Scored 0x7EF102 E4.02 +227 E4.03 E IPL zerofills $00-$EF Documented the canonical 64-byte IPL listing zero-fills $0000-$00EF as the first thing it does at $FFC0, before entering its transfer loop; fullsnes and the SNESdev Wiki both carry it Scored 0x7EF103 E4.03 +228 E4.04 E IPL ready announcement Documented fullsnes, SNESdev Wiki, APU boot handshake Scored 0x7EF104 E4.04 +229 E4.06 E IPL multi-block continue Documented fullsnes, SNESdev Wiki; canonical IPL boot ROM $FFEF-$FFFB Scored 0x7EF105 E4.06 +230 E4.08 E IPL DSP-poke via $00F2 Documented fullsnes (APU / S-DSP); canonical IPL boot ROM store loop Scored 0x7EF106 E4.08 +231 E5.12 E SRCN change source Documented fullsnes and anomie's DSP doc: a mid-playback SRCN change takes the new entry's start address, or its loop address if the voice has already passed a loop point Scored 0x7EF107 E5.12 +232 E5.02 E BRR nibbles are signed Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF108 E5.02 +233 E7.16 E OUTX is pre-volume Documented fullsnes, S-DSP envelopes; anomie's DSP doc Scored 0x7EF109 E7.16 +234 E8.04 E KOFF outranks KON Documented fullsnes, S-DSP key on/off; anomie's DSP doc Scored 0x7EF10A E8.04 +235 E8.05 E KON is edge-triggered Documented fullsnes and anomie's DSP doc: KON is write-triggered and self-clears ~63 clocks later; only KOFF and FLG bit 7 are level-sensitive Scored 0x7EF10B E8.05 +236 E9.04 E Noise voices decode BRR Documented fullsnes, S-DSP noise; anomie's DSP doc — flagged as errata Scored 0x7EF10C E9.04 +237 E9.06 E EDL 0 is a 4-byte buffer Documented fullsnes, S-DSP echo — flagged as errata; anomie's DSP doc Scored 0x7EF10D E9.06 +238 E9.12 E Echo writes are masked Documented fullsnes, S-DSP echo; anomie's DSP doc Scored 0x7EF10E E9.12 +239 E9.15 E Voice mix saturates Documented fullsnes and anomie's DSP doc: the per-voice mix clamps to 16 bits after each addition rather than accumulating and clamping once Scored 0x7EF10F E9.15 +240 E9.05 E Echo entry is 4 bytes Documented fullsnes and anomie's DSP doc: the echo buffer holds four bytes per sample, a 16-bit left sample followed by a 16-bit right one Scored 0x7EF110 E9.05 +241 E9.13 E L/R FIR independent Documented fullsnes and anomie's DSP doc: the echo FIR keeps a separate eight-sample history and accumulator per channel; only the coefficients are shared Scored 0x7EF111 E9.13 +242 E9.10 E FLG.5 stops echo writes Documented fullsnes, S-DSP echo; anomie's DSP doc Scored 0x7EF112 E9.10 +243 E9.17 E Mute is after OUTX Documented fullsnes, S-DSP FLG; anomie's DSP doc Scored 0x7EF113 E9.17 +244 E9.18 E FLG reset kills voices Documented SNESdev Wiki, S-DSP; fullsnes Scored 0x7EF114 E9.18 +245 E5.03 E BRR sample arithmetic Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF115 E5.03 +246 E5.04 E Invalid shift collapses Documented fullsnes, S-DSP BRR; anomie's DSP doc Scored 0x7EF116 E5.04 +247 E5.05 E BRR filter 1 Documented fullsnes, S-DSP BRR filters; anomie's DSP doc Scored 0x7EF117 E5.05 +248 E5.13 E Released voices decode Documented fullsnes and anomie's DSP doc: the BRR decoder advances on the pitch clock regardless of the envelope; voices never actually stop decoding Scored 0x7EF118 E5.13 +249 E7.01 E Rate 0 never fires Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF119 E7.01 +250 E7.13 E GAIN bent-increase Documented fullsnes and anomie's DSP doc: GAIN mode 7 increases +32 per sample below $600 and +8 above, comparing the internal envelope latch unsigned Scored 0x7EF11A E7.13 +251 E7.17 E Lin-decrease clamps 0 Documented fullsnes and anomie's DSP doc: GAIN linear-decrease subtracts $20 per tick and clamps the envelope to zero on underflow, comparing the internal envelope unsigned Scored 0x7EF11B E7.17 +252 E7.18 E ENVX is E>>4 Documented fullsnes and anomie's DSP doc: VxENVX = envelope >> 4, a seven-bit value with bit 7 always clear Scored 0x7EF11C E7.18 +253 E7.04 E Attack $F is instant Documented fullsnes and anomie's DSP doc: attack rate $F fires every sample with a step of +1024, rather than the +32 on a counter tick every other rate uses Scored 0x7EF11D E7.04 +254 E7.09 E Release rate is fixed Documented fullsnes and anomie's DSP doc: release steps -8 per sample regardless of ADSR, which is why a custom release has to be built from GAIN Scored 0x7EF11E E7.09 +255 E7.05 E Decay index d*2+16 Documented fullsnes and anomie's DSP doc: the decay phase indexes the counter table at d*2+16, stepping E -= 1 then E -= E>>8 Scored 0x7EF11F E7.05 +256 E7.06 E Sustain index verbatim Documented fullsnes and anomie's DSP doc: the sustain phase indexes the counter table with the ADSR2 rate field directly, unlike decay's d*2+16 Scored 0x7EF120 E7.06 +257 E7.07 E Sustain boundary Documented fullsnes and anomie's DSP doc: the decay phase ends when (E >> 8) equals the ADSR2 sustain level, giving a boundary of $100 per level Scored 0x7EF121 E7.07 +258 E7.03 E Attack index a*2+1 Documented fullsnes and anomie's DSP doc: the attack phase indexes the counter table at a*2+1 and steps the envelope by +32 Scored 0x7EF122 E7.03 +259 E7.12 E GAIN sustain boundary Contested the dossier records the GAIN-sourced boundary as [ERRATA]; RustySNES takes it from ADSR2 instead, which E7.07 asserts, so which behaviour is right is not settled here Golden 0x7EF123 E7.12 +260 E7.08 E Key-off releases to zero Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF124 E7.08 +261 E8.02 E Key-on takes 5 samples Documented fullsnes and anomie's DSP doc: KON is held for five output samples while the directory and the first BRR block are fetched, before the envelope starts Scored 0x7EF125 E8.02 +262 E8.03 E KON restarts a voice Documented fullsnes and anomie's DSP doc: KON re-enters the key-on sequence unconditionally, resetting the BRR pointer and zeroing the envelope, whether or not the voice was already playing Scored 0x7EF126 E8.03 +263 E8.07 E KOFF pulse collapses Contested KON/KOFF are sampled every second output sample, so whether a short pulse is seen depends on where the poll falls inside it -- which makes the outcome phase-dependent rather than fixed, the same hedge E8.05 and E8.06 carry as "usually" Golden 0x7EF127 E8.07 +264 E8.10 E KOFF+KON cuts faster Documented fullsnes and anomie's DSP doc: KON zeroes the envelope before its attack, and KOFF outranks KON, so the pair silences immediately where KOFF alone ramps Scored 0x7EF128 E8.10 +265 E7.11 E GAIN linear increase Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF129 E7.11 +266 E7.14 E GAIN decrease clamps Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF12A E7.14 +267 E7.15 E ENVX is E >> 4 Documented SNESdev Wiki, S-DSP envelopes; fullsnes; anomie's DSP doc Scored 0x7EF12B E7.15 +268 E6.02 E Pitch $1000 is 1:1 Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12C E6.02 +269 E6.02b E Pitch $1000 does finish Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12D E6.02 +270 E6.02c E Pitch $2000 is +1 octave Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12E E6.02 +271 E6.02d E Pitch $2000 upper bound Documented fullsnes, S-DSP pitch; anomie's DSP doc Scored 0x7EF12F E6.02 +272 E3.06 E T2 is eight times T0 Documented SNESdev Wiki, SPC700 timers; fullsnes Scored 0x7EF130 E3.06 +273 E3.08 E TEST bit 0 halts timers Documented fullsnes, SPC700 TEST register; ares and bsnes smp/timing Scored 0x7EF131 E3.08 +274 F1.01 F Manual pad read order Documented fullsnes and the SNESdev Wiki controller protocol: the shift register presents B, Y, Select, Start, Up, Down, Left, Right, A, X, L, R and then four zero bits Scored 0x7EF132 F1.01 +275 F1.02 F Pad reads 17+ are 1 Documented SNESdev Wiki, controller protocol; fullsnes Scored 0x7EF133 F1.02 +276 F1.03 F Shared $4016 latch Documented fullsnes and the SNESdev Wiki controller protocol: bit 0 of $4016 is the shared latch line that parallel-loads both controller ports' shift registers Scored 0x7EF134 F1.03 +277 F1.04 F $4016 bits 7-2 open bus Corroborated RustySNES, snes9x and Mesen2 all return $41 for the absolute read and $01 for the long one -- identical bytes, so bits 7-2 follow the CPU bus in all three Scored 0x7EF135 F1.04 +278 F1.07 F Auto-read needs $4200.0 Documented fullsnes and the SNESdev Wiki: bit 0 of $4200 arms the automatic joypad read, and with it clear $4218-$421F are not written Scored 0x7EF136 F1.07 +279 F1.05 F Pad signature is 0000 Documented fullsnes and the SNESdev Wiki: bits 3-0 of the auto-read result identify the device, and a standard controller reports 0000 Scored 0x7EF137 F1.05 +280 F1.06 F First bit clocked is B Documented fullsnes and the SNESdev Wiki: the auto-read result holds the sixteen shifted bits in clock order, most significant first, so B is bit 15 Scored 0x7EF138 F1.06 +281 F1.08 F Auto-read start dot Documented fullsnes: the automatic joypad read begins ~dot 32.5-95.5 of the first vblank line (RustySNES delays it 256 master clocks ~ dot 64), not at the vblank edge Golden 0x7EF139 F1.08 +282 F1.09 F Auto-read duration Documented fullsnes: the automatic joypad read is busy for exactly 4224 master cycles (~3.097 scanlines); the poll count is a CPU-cycle-proportional proxy for it Golden 0x7EF13A F1.09 +283 F1.10 F Auto-read start race Documented fullsnes: the automatic joypad read begins ~dot 32.5-95.5 of the first vblank line, not at the vblank edge, so $4212 bit 0 reads not-busy for that window and a $4212 poll at NMI entry sees the read not yet started Scored 0x7EF13B F1.10 +284 F1.11 F Latch corrupts auto-read Documented fullsnes and the SNESdev Wiki: while $4016 bit 0 is high the shift registers reload continuously rather than shifting, so an automatic read taken across it returns the same bit in every position Scored 0x7EF13C F1.11 +285 F1.12 F Auto-read result timing Contested F1.12 says results are valid by V = $E3, which does not reconcile with F1.09's 4224-cycle duration and F1.08's start window; no source says which to believe, and the cores split on whether the result appears at once or progressively Golden 0x7EF13D F1.12 +286 F1.14 F $4213 reads $4201 back Documented fullsnes: RDIO reads the WRIO output pins, which are open-collector, so with nothing driving them low the value read is the value written Scored 0x7EF13E F1.14 +287 G1.02 G Reset: $4210/$4211 clear Documented SNESdev Wiki, power-on state; fullsnes Scored 0x7EF13F G1.02 +288 G1.04 G Reset: emulation mode Documented SNESdev Wiki, power-on state; WDC 65C816 datasheet, XCE Scored 0x7EF140 G1.04 +289 G1.05 G Power-on PPU registers Documented the dossier marks the PPU power-on state indeterminate ('no boot ROM; most PPU registers start unknown') and says to report it, never assert; the readable registers ($2134-$2136, $213E/$213F) are reported and the only scored check is the self-guard that the power-on capture actually ran Scored 0x7EF141 G1.05 +290 G1.08 G Write-only read: openbus Documented SNESdev Wiki, open bus; fullsnes, memory map notes Scored 0x7EF142 G1.08 +291 G1.10 G Checksum XOR complement Documented SNESdev Wiki, cartridge header; fullsnes Scored 0x7EF143 G1.10 +292 G1.11 G Checksum over the image Documented SNESdev Wiki, cartridge header checksum; fullsnes Scored 0x7EF144 G1.11 +293 G1.12 G LoROM header location Documented SNESdev Wiki, cartridge header; fullsnes Scored 0x7EF145 G1.12 +294 G1.14 G LoROM bank decode Documented SNESdev Wiki, memory map; fullsnes Scored 0x7EF146 G1.14 +295 G1.19 G Power-on $4201/timers Documented fullsnes and the SNESdev Wiki power-on table: $4201 = $FF, HTIME and VTIME = $1FF; the other registers in the row are observed by B5.05 and B1.01 Scored 0x7EF147 G1.01 +296 G1.20 G Power-on indeterminate Contested the dossier marks the whole row [UNDEFINED] and says to report it and never assert it; half the registers it names are write-only and cannot be reported at all Golden 0x7EF148 G1.03 +297 G1.07 G WRAM power-on fill Contested the dossier marks the row [UNDEFINED] and asks for a golden vector by name: no canonical WRAM fill exists and real consoles disagree Golden 0x7EF149 G1.07 +298 A5.S01 A Sweep: CLC Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14A A5.01-08 +299 A5.S02 A Sweep: SEC Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14B A5.01-08 +300 A5.S03 A Sweep: CLV Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14C A5.01-08 +301 A5.S04 A Sweep: INX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14D A5.01-08 +302 A5.S05 A Sweep: DEX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14E A5.01-08 +303 A5.S06 A Sweep: TAX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF14F A5.01-08 +304 A5.S07 A Sweep: TXY Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF150 A5.01-08 +305 A5.S08 A Sweep: ASL A Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF151 A5.01-08 +306 A5.S09 A Sweep: XBA Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF152 A5.01-08 +307 A5.S10 A Sweep: TCD Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF153 A5.01-08 +308 A5.S11 A Sweep: LDA #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF154 A5.01-08 +309 A5.S12 A Sweep: LDX #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF155 A5.01-08 +310 A5.S13 A Sweep: CMP #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF156 A5.01-08 +311 A5.S14 A Sweep: BIT #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF157 A5.01-08 +312 A5.S15 A Sweep: REP #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF158 A5.01-08 +313 A5.S16 A Sweep: SEP #imm Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF159 A5.01-08 +314 A5.S17 A Sweep: WDM Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15A A5.01-08 +315 A5.S18 A Sweep: PHA+PLA Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15B A5.01-08 +316 A5.S19 A Sweep: PHP+PLP Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15C A5.01-08 +317 A5.S20 A Sweep: PHB+PLB Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15D A5.01-08 +318 A5.S21 A Sweep: PHD+PLD Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15E A5.01-08 +319 A5.S22 A Sweep: PHX+PLX Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF15F A5.01-08 +320 A5.S23 A Sweep: LDA dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF160 A5.01-08 +321 A5.S24 A Sweep: LDA abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF161 A5.01-08 +322 A5.S25 A Sweep: LDA long Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF162 A5.01-08 +323 A5.S26 A Sweep: STA dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF163 A5.01-08 +324 A5.S27 A Sweep: STA abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF164 A5.01-08 +325 A5.S28 A Sweep: LDA dp,X Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF165 A5.01-08 +326 A5.S29 A Sweep: LDA abs,X Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF166 A5.01-08 +327 A5.S30 A Sweep: INC dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF167 A5.01-08 +328 A5.S31 A Sweep: INC abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF168 A5.01-08 +329 A5.S32 A Sweep: ADC dp Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF169 A5.01-08 +330 A5.S33 A Sweep: CMP abs Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16A A5.01-08 +331 A5.S34 A Sweep: BVS untaken Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16B A5.01-08 +332 A5.S35 A Sweep: BRL flat 4 Documented WDC/GTE/VLSI instruction-operation tables agree; docs/accuracysnes-timing-oracle.md Scored 0x7EF16C A5.16 diff --git a/tests/roms/AccuracySNES/asm/tests_group_a.s b/tests/roms/AccuracySNES/asm/tests_group_a.s index b7cd37a9..e7381920 100644 --- a/tests/roms/AccuracySNES/asm/tests_group_a.s +++ b/tests/roms/AccuracySNES/asm/tests_group_a.s @@ -4166,6 +4166,216 @@ CATALOG_IMPL = 1 jml test_restore .endproc +; A5.19 — RTI 7 native, 6 emul +; provenance: Documented (WDC datasheet instruction-operation table; docs/accuracysnes-timing-oracle.md) +.proc test_a5_19 + .a16 + .i16 + ; Chain eight RTIs through one instruction. Both spans run the instrument natively and + ; contain identical non-RTI code; only the mode the RTIs execute in differs. + ; --- native: 4-byte frames --- + clc + xce ; -> native (m/x stay 1: still 8-bit) + .a8 + .i8 + sep #$30 + .a8 + .i8 + ; The exit frame goes on FIRST, so it is pulled LAST. + lda #$00 + pha ; PBR + lda #>.loword(@done) + pha ; PCH + lda #<.loword(@done) + pha ; PCL + lda #$34 + pha ; P: m=1, x=1, I=1 + lda #$00 + pha + lda #>.loword(@spin) + pha + lda #<.loword(@spin) + pha + lda #$34 + pha + lda #$00 + pha + lda #>.loword(@spin) + pha + lda #<.loword(@spin) + pha + lda #$34 + pha + lda #$00 + pha + lda #>.loword(@spin) + pha + lda #<.loword(@spin) + pha + lda #$34 + pha + lda #$00 + pha + lda #>.loword(@spin) + pha + lda #<.loword(@spin) + pha + lda #$34 + pha + lda #$00 + pha + lda #>.loword(@spin) + pha + lda #<.loword(@spin) + pha + lda #$34 + pha + lda #$00 + pha + lda #>.loword(@spin) + pha + lda #<.loword(@spin) + pha + lda #$34 + pha + lda #$00 + pha + lda #>.loword(@spin) + pha + lda #<.loword(@spin) + pha + lda #$34 + pha + jsr hv_begin + ; A no-op clc/xce, present only so this span matches the emulation one instruction for + ; instruction. Removing it here would put the mode switch's own cost into the result. + clc + xce ; -> native (m/x stay 1: still 8-bit) + .a8 + .i8 +@spin: + rti +@done: + clc + xce ; -> native (m/x stay 1: still 8-bit) + .a8 + .i8 + jsr hv_end + rep #$30 + .a16 + .i16 + lda f:$7E0048 ; V_H1 = elapsed dots + sta f:$7E0098 ; native baseline + ; record slot 253: 8x RTI, native (7 cycles each) + sta f:$7EE3FA + ; --- emulation: 3-byte frames, same chain --- + ; Enter emulation once out here purely to force S into page 1, so the frames are pushed + ; where the in-span re-entry will still find them; SH stays $01 on the way back out. + sec + xce ; -> emulation + .a8 + .i8 + clc + xce ; -> native (m/x stay 1: still 8-bit) + .a8 + .i8 + lda #>.loword(@edone) + pha ; PCH + lda #<.loword(@edone) + pha ; PCL + lda #$34 + pha ; P + lda #>.loword(@espin) + pha + lda #<.loword(@espin) + pha + lda #$34 + pha + lda #>.loword(@espin) + pha + lda #<.loword(@espin) + pha + lda #$34 + pha + lda #>.loword(@espin) + pha + lda #<.loword(@espin) + pha + lda #$34 + pha + lda #>.loword(@espin) + pha + lda #<.loword(@espin) + pha + lda #$34 + pha + lda #>.loword(@espin) + pha + lda #<.loword(@espin) + pha + lda #$34 + pha + lda #>.loword(@espin) + pha + lda #<.loword(@espin) + pha + lda #$34 + pha + lda #>.loword(@espin) + pha + lda #<.loword(@espin) + pha + lda #$34 + pha + jsr hv_begin + sec + xce ; -> emulation + .a8 + .i8 +@espin: + rti +@edone: + clc + xce ; -> native (m/x stay 1: still 8-bit) + .a8 + .i8 + jsr hv_end + rep #$30 + .a16 + .i16 + lda f:$7E0048 ; V_H1 = elapsed dots + sta f:$7E009A + ; record slot 254: 8x RTI, emulation (6 cycles each) + sta f:$7EE3FC + rep #$20 + .a16 + lda f:$7E0098 + sec + sbc f:$7E009A + ; record slot 255: 8x (native - emulation), expect 16 + sta f:$7EE3FE + cmp #$000E + bcs :+ + jmp @fail1 + : + cmp #$0013 + bcc :+ + jmp @fail1 + : + sep #$20 + .a8 + lda #$01 + sta f:$7EE010 + jml test_restore +@fail1: + ; RTI did not cost one extra stack pull in native mode (7 vs 6) + sep #$20 + .a8 + lda #$02 + sta f:$7EE010 + jml test_restore +.endproc + ; A6.11 — WAI wakes, no vector ; provenance: Documented (WDC datasheet: WAI wakes on the interrupt line; I gates the vector) .proc test_a6_11 @@ -32800,7 +33010,7 @@ apu_prog_112: .export _test_flags _test_count: - .word 332 + .word 333 ; Entry points, 24-bit: test bodies no longer all live in bank $00. _test_entries: @@ -32875,6 +33085,7 @@ _test_entries: .faraddr test_a3_06 .faraddr test_a5_09 .faraddr test_a5_10 + .faraddr test_a5_19 .faraddr test_a6_11 .faraddr test_a8_07 .faraddr test_a6_12 @@ -33210,6 +33421,7 @@ _test_flags: .byte $01 ; A3.06 .byte $01 ; A5.09 .byte $01 ; A5.10 + .byte $01 ; A5.19 .byte $01 ; A6.11 .byte $01 ; A8.07 .byte $02 ; A6.12 @@ -33545,6 +33757,7 @@ _test_names: .addr @n_a3_06 .addr @n_a5_09 .addr @n_a5_10 + .addr @n_a5_19 .addr @n_a6_11 .addr @n_a8_07 .addr @n_a6_12 @@ -34019,6 +34232,9 @@ _test_names: @n_a5_10: .byte 18 .byte "+1 x width penalty" +@n_a5_19: + .byte 20 + .byte "RTI 7 native, 6 emul" @n_a6_11: .byte 20 .byte "WAI wakes, no vector" @@ -35028,7 +35244,7 @@ _page_len: .byte 10 .byte 10 .byte 10 - .byte 5 + .byte 6 .byte 10 .byte 4 .byte 5 @@ -35082,48 +35298,48 @@ _page_off: .word 59 .word 69 .word 79 - .word 84 - .word 94 - .word 98 - .word 103 - .word 110 - .word 114 - .word 123 - .word 129 - .word 139 - .word 149 + .word 85 + .word 95 + .word 99 + .word 104 + .word 111 + .word 115 + .word 124 + .word 130 + .word 140 .word 150 - .word 153 - .word 156 - .word 160 - .word 162 - .word 166 - .word 171 - .word 181 - .word 184 - .word 189 + .word 151 + .word 154 + .word 157 + .word 161 + .word 163 + .word 167 + .word 172 + .word 182 + .word 185 .word 190 - .word 200 - .word 205 - .word 211 - .word 218 - .word 228 - .word 232 - .word 242 - .word 244 - .word 253 - .word 259 - .word 261 - .word 271 - .word 273 - .word 283 - .word 285 - .word 295 - .word 302 - .word 308 - .word 318 - .word 321 - .word 331 + .word 191 + .word 201 + .word 206 + .word 212 + .word 219 + .word 229 + .word 233 + .word 243 + .word 245 + .word 254 + .word 260 + .word 262 + .word 272 + .word 274 + .word 284 + .word 286 + .word 296 + .word 303 + .word 309 + .word 319 + .word 322 + .word 332 _page_tests: .word 0 @@ -35145,7 +35361,7 @@ _page_tests: .word 11 .word 54 .word 63 - .word 78 + .word 79 .word 12 .word 13 .word 14 @@ -35163,8 +35379,8 @@ _page_tests: .word 62 .word 64 .word 65 - .word 74 .word 75 + .word 76 .word 24 .word 25 .word 26 @@ -35175,7 +35391,7 @@ _page_tests: .word 50 .word 69 .word 70 - .word 297 + .word 71 .word 298 .word 299 .word 300 @@ -35210,6 +35426,7 @@ _page_tests: .word 329 .word 330 .word 331 + .word 332 .word 30 .word 31 .word 32 @@ -35220,10 +35437,10 @@ _page_tests: .word 37 .word 49 .word 56 - .word 71 - .word 73 - .word 76 + .word 72 + .word 74 .word 77 + .word 78 .word 38 .word 39 .word 40 @@ -35235,37 +35452,36 @@ _page_tests: .word 57 .word 60 .word 66 - .word 72 + .word 73 .word 45 .word 46 .word 51 .word 53 - .word 79 .word 80 .word 81 .word 82 .word 83 .word 84 .word 85 - .word 119 - .word 121 .word 86 - .word 113 + .word 120 + .word 122 + .word 87 .word 114 .word 115 .word 116 .word 117 - .word 87 + .word 118 .word 88 .word 89 .word 90 .word 91 .word 92 .word 93 - .word 118 - .word 120 - .word 124 .word 94 + .word 119 + .word 121 + .word 125 .word 95 .word 96 .word 97 @@ -35274,9 +35490,9 @@ _page_tests: .word 100 .word 101 .word 102 - .word 125 - .word 126 .word 103 + .word 126 + .word 127 .word 104 .word 105 .word 106 @@ -35286,37 +35502,37 @@ _page_tests: .word 110 .word 111 .word 112 - .word 122 + .word 113 .word 123 - .word 127 + .word 124 .word 128 - .word 144 - .word 145 .word 129 - .word 131 + .word 145 .word 146 - .word 147 - .word 149 .word 130 .word 132 + .word 147 + .word 148 + .word 150 + .word 131 .word 133 .word 134 .word 135 .word 136 .word 137 .word 138 - .word 148 - .word 150 + .word 139 + .word 149 .word 151 - .word 153 + .word 152 .word 154 - .word 139 + .word 155 .word 140 .word 141 .word 142 .word 143 - .word 152 - .word 155 + .word 144 + .word 153 .word 156 .word 157 .word 158 @@ -35325,26 +35541,26 @@ _page_tests: .word 161 .word 162 .word 163 - .word 168 + .word 164 .word 169 .word 170 .word 171 .word 172 .word 173 - .word 164 + .word 174 .word 165 .word 166 .word 167 - .word 174 + .word 168 .word 175 .word 176 - .word 224 + .word 177 .word 225 .word 226 .word 227 .word 228 .word 229 - .word 177 + .word 230 .word 178 .word 179 .word 180 @@ -35352,45 +35568,45 @@ _page_tests: .word 182 .word 183 .word 184 - .word 195 - .word 209 + .word 185 + .word 196 .word 210 - .word 217 + .word 211 .word 218 .word 219 - .word 185 + .word 220 .word 186 .word 187 .word 188 - .word 197 + .word 189 .word 198 - .word 213 + .word 199 .word 214 .word 215 .word 216 - .word 271 + .word 217 .word 272 - .word 189 + .word 273 .word 190 - .word 196 - .word 211 + .word 191 + .word 197 .word 212 - .word 220 + .word 213 .word 221 .word 222 .word 223 - .word 191 + .word 224 .word 192 - .word 267 + .word 193 .word 268 .word 269 .word 270 - .word 193 + .word 271 .word 194 - .word 199 + .word 195 .word 200 .word 201 - .word 235 + .word 202 .word 236 .word 237 .word 238 @@ -35399,21 +35615,21 @@ _page_tests: .word 241 .word 242 .word 243 - .word 202 + .word 244 .word 203 .word 204 .word 205 .word 206 .word 207 - .word 230 + .word 208 .word 231 - .word 244 + .word 232 .word 245 .word 246 .word 247 - .word 208 - .word 232 .word 248 + .word 209 + .word 233 .word 249 .word 250 .word 251 @@ -35425,16 +35641,16 @@ _page_tests: .word 257 .word 258 .word 259 - .word 264 + .word 260 .word 265 .word 266 - .word 233 + .word 267 .word 234 - .word 260 + .word 235 .word 261 .word 262 .word 263 - .word 273 + .word 264 .word 274 .word 275 .word 276 @@ -35458,3 +35674,4 @@ _page_tests: .word 294 .word 295 .word 296 + .word 297 diff --git a/tests/roms/AccuracySNES/build/accuracysnes-pal.sfc b/tests/roms/AccuracySNES/build/accuracysnes-pal.sfc index 4445a453..ada0bb23 100644 Binary files a/tests/roms/AccuracySNES/build/accuracysnes-pal.sfc and b/tests/roms/AccuracySNES/build/accuracysnes-pal.sfc differ diff --git a/tests/roms/AccuracySNES/build/accuracysnes.sfc b/tests/roms/AccuracySNES/build/accuracysnes.sfc index 465815f6..6c0bb393 100644 Binary files a/tests/roms/AccuracySNES/build/accuracysnes.sfc and b/tests/roms/AccuracySNES/build/accuracysnes.sfc differ diff --git a/tests/roms/AccuracySNES/gen/src/dossier.rs b/tests/roms/AccuracySNES/gen/src/dossier.rs index 779cf6af..0de29b2c 100644 --- a/tests/roms/AccuracySNES/gen/src/dossier.rs +++ b/tests/roms/AccuracySNES/gen/src/dossier.rs @@ -83,6 +83,7 @@ pub const MAP: &[(&str, &[&str])] = &[ ("A3.06", &["A3.06"]), ("A5.09", &["A5.09"]), ("A5.10", &["A5.10"]), + ("A5.19", &["A5.19"]), ("C14.03", &["C14.03"]), ("A6.11", &["A6.11"]), ("A8.07", &["A8.06"]), diff --git a/tests/roms/AccuracySNES/gen/src/tests/cpu.rs b/tests/roms/AccuracySNES/gen/src/tests/cpu.rs index 32517ae7..5e1179ef 100644 --- a/tests/roms/AccuracySNES/gen/src/tests/cpu.rs +++ b/tests/roms/AccuracySNES/gen/src/tests/cpu.rs @@ -95,6 +95,7 @@ pub fn all() -> Vec { a3_06(), a5_09(), a5_10(), + a5_19(), a6_11(), a8_07(), a6_12(), @@ -2585,7 +2586,8 @@ fn a9_02() -> Test { // sequence is repeated 16 times: 16 cycles = 96 clocks = 24 dots, well clear of the counter's // +/-1 dot resolution. // -// `DOTS_PER_16_CYCLES` is the expected delta for a one-cycle-per-iteration difference. +// The expected delta for a one-cycle-per-iteration difference depends on what that cycle IS: +// `DOTS_PER_8_INTERNAL` for an internal cycle, `DOTS_PER_8_STACK_ACCESSES` for a stack access. // --------------------------------------------------------------------------------------------- /// Dots elapsed per 8 extra *internal* CPU cycles: 8 x 6 master clocks / 4 clocks-per-dot. @@ -2593,6 +2595,13 @@ fn a9_02() -> Test { /// below expects a different figure. const DOTS_PER_8_INTERNAL: u16 = 12; +/// Dots elapsed per 8 extra **stack accesses**: 8 x 8 master clocks / 4 clocks-per-dot. +/// +/// The stack lives in WRAM, so a push or pull is an 8-clock access rather than a 6-clock internal +/// cycle. `A5.19`'s extra native cycle is exactly one such pull, so the delta it expects is this +/// and not `DOTS_PER_8_INTERNAL`. +const DOTS_PER_8_STACK_ACCESSES: u16 = 16; + /// Tolerance on every timing comparison, in dots. /// /// Measured, not guessed: eight runs of an identical sequence span exactly one dot, which is the @@ -2600,6 +2609,142 @@ const DOTS_PER_8_INTERNAL: u16 = 12; /// therefore generous; anything wider would start to blur "the penalty applied" into "it did not". const TOL: u16 = 2; +/// `RTI` costs 7 cycles in native mode and 6 in emulation (`A5.19`). +/// +/// The extra native cycle is the **PBR pull**: a native return frame is four bytes (P, PCL, PCH, +/// PBR) and an emulation frame is three. So the difference is one stack access — an 8-clock WRAM +/// read, not a 6-clock internal cycle, which is why this expects +/// [`DOTS_PER_8_STACK_ACCESSES`] rather than [`DOTS_PER_8_INTERNAL`]. +/// +/// # Why the frames are chained rather than looped +/// +/// A single `RTI` differs by 2 dots between the two modes, which is exactly [`TOL`] — not a signal +/// at all. It has to be repeated, and `RTI` jumps, so the obvious repetition is a loop whose +/// branch and counter land *inside* the measured span. Those cost the same in both modes and would +/// cancel, but they also dominate it: the property under test would be a few percent of what is +/// being measured. +/// +/// Instead the frames are all built **before** `measure_begin`, every one of them returning to the +/// same `@spin` label, which is the `rti` itself. The span then contains nothing but eight +/// `RTI` executions. The last frame — pushed **first**, so it is pulled **last** — returns to +/// `@done` instead, which is how the chain terminates. +/// +/// # The mode switch has to come before the pushes +/// +/// Entering emulation forces `SH = $01`. Pushing frames while native at some `S` outside page 1 +/// and *then* switching would truncate the stack pointer out from under them, and the first `RTI` +/// would pull whatever happened to be at `$01xx`. Switch first, push second. +/// +/// # Eight, not sixteen +/// +/// `hv_begin`/`hv_end` difference the H counter, which wraps at `DOTS_PER_LINE` (341), so a span +/// that outlives its scanline silently returns a small number instead of failing — the same trap +/// `A5.08` records. The first draft used sixteen RTIs, a ~200-dot span, and came back with native +/// **42** dots against emulation **76**: both wrapped, and the native reading was *smaller* than +/// the emulation one, which is the arithmetic opposite of the assertion. Eight RTIs is a ~104-dot +/// native span against ~88 emulation, with 16 dots of signal against a tolerance of 2. +/// +/// The pushed `P` keeps `m = x = 1` so the assembler's `.a8`/`.i8` state still describes the CPU +/// after the chain unwinds; `RTI` restores P from the frame, so a frame with the width bits clear +/// would desync the two silently. +fn a5_19() -> Test { + let mut a = Asm::new(); + a.c("Chain eight RTIs through one instruction. Both spans run the instrument natively and"); + a.c("contain identical non-RTI code; only the mode the RTIs execute in differs."); + + a.c("--- native: 4-byte frames ---"); + a.enter_native(); + a.l("sep #$30"); + a.c("The exit frame goes on FIRST, so it is pulled LAST."); + a.l("lda #$00"); + a.l("pha ; PBR"); + a.l("lda #>.loword(@done)"); + a.l("pha ; PCH"); + a.l("lda #<.loword(@done)"); + a.l("pha ; PCL"); + a.l("lda #$34"); + a.l("pha ; P: m=1, x=1, I=1"); + a.repeat( + 7, + &[ + "lda #$00", + "pha", + "lda #>.loword(@spin)", + "pha", + "lda #<.loword(@spin)", + "pha", + "lda #$34", + "pha", + ], + ); + a.measure_begin(); + a.c("A no-op clc/xce, present only so this span matches the emulation one instruction for"); + a.c("instruction. Removing it here would put the mode switch's own cost into the result."); + a.enter_native(); + a.label("spin"); + a.l("rti"); + a.label("done"); + a.enter_native(); + a.measure_end(); + a.measure_result(); + a.l("sta f:$7E0098 ; native baseline"); + a.record(253, "8x RTI, native (7 cycles each)"); + + a.c("--- emulation: 3-byte frames, same chain ---"); + a.c("Enter emulation once out here purely to force S into page 1, so the frames are pushed"); + a.c("where the in-span re-entry will still find them; SH stays $01 on the way back out."); + a.enter_emulation(); + a.enter_native(); + a.l("lda #>.loword(@edone)"); + a.l("pha ; PCH"); + a.l("lda #<.loword(@edone)"); + a.l("pha ; PCL"); + a.l("lda #$34"); + a.l("pha ; P"); + a.repeat( + 7, + &[ + "lda #>.loword(@espin)", + "pha", + "lda #<.loword(@espin)", + "pha", + "lda #$34", + "pha", + ], + ); + a.measure_begin(); + a.enter_emulation(); + a.label("espin"); + a.l("rti"); + a.label("edone"); + a.enter_native(); + a.measure_end(); + a.measure_result(); + a.l("sta f:$7E009A"); + a.record(254, "8x RTI, emulation (6 cycles each)"); + + a.l("rep #$20"); + a.l("lda f:$7E0098"); + a.l("sec"); + a.l("sbc f:$7E009A"); + a.record(255, "8x (native - emulation), expect 16"); + a.assert_a16_range( + DOTS_PER_8_STACK_ACCESSES - TOL, + DOTS_PER_8_STACK_ACCESSES + TOL, + "RTI did not cost one extra stack pull in native mode (7 vs 6)", + ); + a.finish( + "A5.19", + 'A', + "RTI 7 native, 6 emul", + Provenance::Documented( + "WDC datasheet instruction-operation table; docs/accuracysnes-timing-oracle.md", + ), + Kind::Scored, + None, + ) +} + /// The `+1 w` penalty: direct-page addressing costs an extra cycle when `DL != 0`. /// /// Singled out because it is, by a wide margin, the most commonly mis-implemented 65816 timing