From 2ef47bd38811f70d829d47ef14d4c7a22d3a4997 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Fri, 26 Jun 2026 20:59:19 -0700 Subject: [PATCH] =?UTF-8?q?feat(timing):=20Stage-2=20#1a=20=E2=80=94=20dat?= =?UTF-8?q?a-load=20cost=20in=20psx=5Finstr=5Fbase=5Fcycles=20(=CE=94-vali?= =?UTF-8?q?dated)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Transcribed from Beetle PS_CPU::ReadMemory: a CPU load adds +2 cycles (LWC2 +1); stores posted (~0). psx_instr_base_cycles now returns 3 for LB/LH/LWL/LW/LBU/LHU/LWR, 2 for LWC2, 1 else. Flows to BOTH backends via the single-source seam. Δ GATE (cyc_watch @0x80017FC4, native vs Beetle per-iteration): 46 -> 56 (toward Beetle 91) — load cost closed ~10 cyc; remaining ~35 = later components (I-cache fetch, mult/div, GTE, region/absorb refinements). FMV still streams (no regression). Approximation noted in the header: no scratchpad-free / load-delay-absorb / region yet (Δ-gated next). Co-Authored-By: Claude Opus 4.8 --- runtime/include/psx_instr_cost.h | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/runtime/include/psx_instr_cost.h b/runtime/include/psx_instr_cost.h index 37639e89..3006cb63 100644 --- a/runtime/include/psx_instr_cost.h +++ b/runtime/include/psx_instr_cost.h @@ -31,12 +31,32 @@ extern "C" { #endif -/* CPU instruction base cycle cost (execute cycles only; excludes data-access - * wait states, which the memory path charges). `insn` is the raw 32-bit MIPS - * word. Stage 1: identity. */ +/* Guest CPU cycle cost for one retired instruction. `insn` is the raw 32-bit MIPS + * word. Transcribed from the in-tree Beetle oracle (mednafen psx/cpu.cpp) + psx-spx; + * each component verified against Beetle via the cyc_watch DELTA gate + * (FAITHFUL_TIMING_PLAN.md §3c). Both backends consume this one function. + * + * STAGE 2 component #1 — data-memory access (the dominant ~2x gap): + * Beetle PS_CPU::ReadMemory: a normal CPU load adds `lts += 2` (LWC loads +1); + * stores are POSTED (WriteMemory adds no CPU stall); scratchpad reads add 0. + * So: load opcode = 1 (execute) + 2 (data access) = 3; LWC2 = 2; store/other = 1. + * NOT YET MODELED (later Δ-gated components, will adjust here / in the mem path): + * - scratchpad-vs-RAM region (scratchpad load should be 1, not 3), + * - the read-after-load "fudge" (+0/2) and load-delay ABSORB (next-insn discount), + * - I-cache instruction-fetch timing, mult/div latency, GTE per-command costs. + * This RAM-load base is an upper bound on the load component (no absorb yet); the + * Δ gate shows whether we under/overshoot and what to add next. */ static inline uint32_t psx_instr_base_cycles(uint32_t insn) { - (void)insn; - return 1u; + uint32_t op = insn >> 26; + switch (op) { + case 0x20: case 0x21: case 0x22: case 0x23: /* LB LH LWL LW */ + case 0x24: case 0x25: case 0x26: /* LBU LHU LWR */ + return 3u; /* 1 execute + 2 data-access */ + case 0x32: /* LWC2 (GTE load): +1 */ + return 2u; + default: + return 1u; + } } #ifdef __cplusplus