diff --git a/docs/internal/ACCURACY_BURNDOWN.md b/docs/internal/ACCURACY_BURNDOWN.md index 7fbe727f..9b0b9aa1 100644 --- a/docs/internal/ACCURACY_BURNDOWN.md +++ b/docs/internal/ACCURACY_BURNDOWN.md @@ -136,6 +136,14 @@ observe added to the recompiler so ANY block leader is anchorable on both backen (compiled == interp fetch path == Beetle transcription, per instruction, on OpenBIOS) and `uncached_fetch_codegen_test` (game emitter). OPEN: full LLE boot (`bios_hle = false`) parity against live Beetle (no oracle binary on the Mac). +- [x] **BIOS emitter tests cache-line starts on the runtime PC (2026-09-29).** The + line-leader test used the ROM address, assuming every copy window preserves + bits[3:0]. OpenBIOS copies its kernel from ROM 0x1FC1E4D4 to RAM 0x500, so every + kernel line crossing was charged one instruction early (a hit) and the real + crossing went uncharged: 1,152 kernel instruction sites differed from the interp. + Retail profiles are unaffected (SCPH-1001's windows are 16-byte aligned; + SCPH-101/5552 declare none). Covered by ctest `uncached_fetch_charge` (cached runs + compared from a cold cache). - [ ] **HW test-ROM ruler (#2)** — Amidog CPU/GTE timing ROMs for hand-crafted single-COMPONENT isolation (div-only, load-only loops) that organic BIOS code can't give (the prologue combines div+loads in one block). Strongest validator. diff --git a/docs/internal/FAITHFUL_TIMING_PLAN.md b/docs/internal/FAITHFUL_TIMING_PLAN.md index d0bb6175..8cafa85a 100644 --- a/docs/internal/FAITHFUL_TIMING_PLAN.md +++ b/docs/internal/FAITHFUL_TIMING_PLAN.md @@ -224,9 +224,10 @@ on a fixed region -> next. psx_icache.c also uses. The A0/B0/C0 call-vector stubs charge one fetch per executed word. - Cached code keeps the leader rule. R4's regenerated game C (50 shards and the - dispatch table) is byte-identical, as are the 26 R4 overlay shards compiled in - both runs. - - The OpenBIOS diff is insertions only: 5,477 fetches plus 5 stub fetches. + dispatch table) is byte-identical. So is every R4 overlay shard compiled in + more than one run (26 of the 45 captured; runs capture different sets). + - For the uncached fix, the OpenBIOS diff is insertions only: 5,477 fetches + plus 5 stub fetches. - The codegen hash changes because the emitter sources do, and psx_instr_cost.h is now in the hash list. Every title reshards its overlay cache once. @@ -238,6 +239,16 @@ on a fixed region -> next. game entry 0x8007D8F4 moves from guest cycle 97,718,389 to 100,583,391 (+2,865,002, about 5.1 frames). The count was the same on two baseline boots. R4 reached a race with 0 dispatch misses. + - Same day, second fix in the BIOS emitter: the cached line-start test now + uses the runtime PC instead of the ROM address. OpenBIOS copies its kernel + from ROM 0x1FC1E4D4 to RAM 0x500, which shifts bits[3:0] by 4. Each kernel + line crossing was charged one instruction early (a hit), and the real + crossing went uncharged: 1,152 kernel sites differed from the interp. + In the OpenBIOS kernel, 823 misplaced fetch sites go and 773 are added at + runtime line starts. Retail profiles are unaffected: SCPH-1001's windows + are 16-byte aligned and SCPH-101/5552 declare none. With both fixes, the + R4 game entry is at 100,589,404 (+2,871,015 over master; +6,013 from this + fix). R4 reached a race with 0 dispatch misses. - Closes `bios-kseg1-fetch-charge` in the segment-aware ledger (PR #419). - OPEN: full LLE boot (`bios_hle = false`) cycle parity against live Beetle. There is no oracle binary on the Mac. diff --git a/recompiler/src/full_function_emitter.cpp b/recompiler/src/full_function_emitter.cpp index a64d4dab..143eb387 100644 --- a/recompiler/src/full_function_emitter.cpp +++ b/recompiler/src/full_function_emitter.cpp @@ -772,10 +772,13 @@ bool FullFunctionEmitter::emit_function( // to the dirty-RAM interp (cpu->pc) and Beetle. // - CACHED runtime PC: emitted only at cache-line LEADERS: a block leader (any // branch/dispatch entry — a possibly-cold cache entry; cross-function targets - // are inserted into block_leaders above) OR a 16-byte-line start (addr&0xC==0, - // a sequential line crossing). Intra-line followers reached by fall-through are - // guaranteed hits (the leader refilled the line to its end) → no call (+0). The - // relocation preserves bits[3:0], so the line-leader test is space-independent. + // are inserted into block_leaders above) OR a 16-byte-line start of the RUNTIME + // PC (pc&0xC==0, a sequential line crossing). Intra-line followers reached by + // fall-through are guaranteed hits (the leader refilled the line to its end) → + // no call (+0). The line test must use the runtime PC: a copy window need not + // preserve bits[3:0]. OpenBIOS copies its kernel from ROM 0x1FC1E4D4 to RAM + // 0x500, so a ROM-address test put every line crossing of the kernel one + // instruction early (a hit) and left the real crossing uncharged. // - UNCACHED runtime PC (psx_fetch_uncached: the ROM run in place at KSEG1): no // fetch ever fills a line, so there is no follower to elide. Every instruction // pays +4 and clears the load give-back, exactly as the interpreter (a fetch at @@ -785,7 +788,7 @@ bool FullFunctionEmitter::emit_function( if (!per_insn_cycles) return; const uint32_t pc = relocate_ra(rom_addr); if (!psx_fetch_uncached(pc) && - !(block_leaders.count(rom_addr) || (rom_addr & 0xCu) == 0)) return; + !(block_leaders.count(rom_addr) || (pc & 0xCu) == 0)) return; out += fmt::format("#ifdef PSX_ENABLE_BLOCK_CYCLES\n psx_icache_fetch(cpu, 0x{:08X}u);\n#endif\n", pc); }; diff --git a/recompiler/tests/test_uncached_fetch_charge.py b/recompiler/tests/test_uncached_fetch_charge.py index 476393f4..67a4b29f 100644 --- a/recompiler/tests/test_uncached_fetch_charge.py +++ b/recompiler/tests/test_uncached_fetch_charge.py @@ -20,9 +20,14 @@ instruction's (cycles, give-back cleared) pair must be the same both ways: model psx_icache.c (both entry points) equals a transcription of Beetle ReadInstruction on synthetic sequences: uncached repeat, cached cold then warm, KSEG0 -> KUSEG alias refill, partial refill. - bios psxrecomp-bios on bios/OpenBIOS.toml. Every straight-line KSEG1 - run (the ROM in place) is replayed, and every KSEG1 instruction - must carry a fetch naming its own runtime PC. + bios psxrecomp-bios on bios/OpenBIOS.toml. Every straight-line run from + a label is replayed from a cold cache: KSEG1 runs (ROM in place) + and cached runs (kernel at 0x500+, shell at 0x80030000+) alike. + Every KSEG1 instruction must carry a fetch naming its own runtime + PC. Cached runs pin the line-start test to the runtime PC: + OpenBIOS copies its kernel from ROM 0x1FC1E4D4 to RAM 0x500, so a + ROM-address test charged each kernel line crossing one + instruction early (a hit) and missed the real one. stubs The A0/B0/C0 call-vector native stubs charge one fetch per executed word, so a KSEG1 call pays like the interpreter. @@ -347,7 +352,7 @@ def main(): if not run["from_label"]: first = next((i for i, (_, t) in enumerate(insns) if t is not None), len(insns)) insns = [x for i, x in enumerate(insns) if i >= first or uncached(x[0])] - if not insns or not all(uncached(pc) for pc, _ in insns): + if not insns: continue for pc, tag in insns: if uncached(pc):