Files
psxrecomp/runtime/tests/test_psx_icache_fastpath.c
Shokunin 91cd6db705 guest_cycle_scale: one CPU charge for every tier, declarative gate, rollback
Review of #561. Guest time now has two entry points (psx_cycles.h): the
CPU charge psx_cpu_charge() (instruction base, load fudge/region/completion,
i-cache refills, BIOS HLE costs), which applies [timing] guest_cycle_scale,
and the unscaled device advance psx_advance_cycles() (DMA, idle skips,
mul/div and GTE deadline waits). The emitters' block-mode charges emit
psx_cpu_charge; overlay DLLs accumulate it raw and publish through the
cpu_charge callback (was advance_cycles, same slot) into the same host
function, so nothing is scaled at the DLL boundary. The batched
psx_cyc_charge path, the dirty-RAM interpreter, psx_cyc_readmem, i-cache
refills and bios_hle.c all charge through it. At scale 1 it is exactly
psx_advance_cycles.

Gate in psxrecomp: [timing] guest_cycle_scale_gate, RAM predicates judged
at every VBlank edge (deterministic, works online, no mod needed), plus
psx_mod_set_guest_cycle_scale_gate() in mod_plugins.h for custom logic or
to tie the scale to a mod (guest_cycle_scale_gated = true; every mod
session reset shuts it). The carried fraction and both gate states are a
boot_state section (BS_SEC_GCS, written only when the scale is not 1), so
savestates and the netplay rollback snapshot re-simulate the same cycles.
psx_guest_cycle_scale_gate_test pins tier identity, the gates and rollback
re-simulation against the real psx_cycles.c.

OpenBIOS regenerated (emitter stamp); retail SCPH1001 is not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:41:39 -07:00

97 lines
3.5 KiB
C

#include "cpu_state.h"
#include "psx_icache.h"
#include <stdio.h>
#include <string.h>
int g_ls_replay_active = 0;
static uint64_t test_cycles = 0;
void psx_advance_cycles(uint32_t cycles) { test_cycles += cycles; }
/* I-cache refills are CPU work: they go through the CPU charge. */
void psx_cpu_charge(uint32_t cycles) { psx_advance_cycles(cycles); }
static int expect(int condition, const char *message) {
if (condition) return 1;
fprintf(stderr, "FAIL: %s\n", message);
return 0;
}
int main(void) {
CPUState cpu;
memset(&cpu, 0, sizeof(cpu));
psx_icache_reset();
g_psx_icache_active = 1;
test_cycles = 0;
cpu.read_absorb_which = 1;
cpu.read_absorb[0] = 77u;
cpu.read_absorb[1] = 99u;
psx_icache_fetch_interp(&cpu, 0x80010000u);
if (!expect(test_cycles == 7u, "word-0 cold refill cost")) return 1;
if (!expect(cpu.read_absorb_which == 0u && cpu.read_absorb[1] == 0u &&
cpu.read_absorb[0] == 77u,
"true miss clears only the selected load give-back")) return 1;
cpu.read_absorb_which = 1;
cpu.read_absorb[1] = 55u;
psx_icache_fetch_interp(&cpu, 0x80010000u);
psx_icache_fetch_interp(&cpu, 0x80010004u);
if (!expect(test_cycles == 7u, "steady tag hits are cycle-free")) return 1;
if (!expect(cpu.read_absorb_which == 1u && cpu.read_absorb[1] == 55u,
"tag hits preserve load give-back")) return 1;
psx_icache_reset();
g_psx_icache_active = 1;
test_cycles = 0;
psx_icache_fetch_interp(&cpu, 0x80010008u);
if (!expect(test_cycles == 5u, "word-2 partial refill cost")) return 1;
psx_icache_fetch_interp(&cpu, 0x80010000u);
if (!expect(test_cycles == 12u, "earlier word remains invalid after partial refill")) return 1;
psx_icache_reset();
g_psx_icache_active = 1;
test_cycles = 0;
psx_icache_fetch_interp(&cpu, 0xA0010000u);
psx_icache_fetch_interp(&cpu, 0xA0010000u);
if (!expect(test_cycles == 8u, "KSEG1 remains uncached")) return 1;
psx_icache_reset();
g_psx_icache_active = 1;
test_cycles = 0;
psx_icache_fetch_interp(&cpu, 0x80010000u);
psx_icache_fetch_interp(&cpu, 0x00010000u);
if (!expect(test_cycles == 14u,
"KUSEG/KSEG0 aliases replace full virtual tags")) return 1;
psx_icache_reset();
if (!expect(g_psx_icache_tv[0] == 1u,
"reset makes a warmed line cold")) return 1;
g_psx_icache_active = 1;
test_cycles = 0;
psx_icache_fetch(&cpu, 0x80010000u);
if (!expect(test_cycles == 7u,
"compiled-code compatibility wrapper preserves refill")) return 1;
psx_icache_reset();
g_psx_icache_active = 1;
test_cycles = 0;
g_ls_replay_active = 1;
cpu.read_absorb_which = 1;
cpu.read_absorb[1] = 44u;
psx_icache_fetch_interp(&cpu, 0x80010000u);
g_ls_replay_active = 0;
if (!expect(test_cycles == 0u, "lockstep replay does not mutate cache")) return 1;
if (!expect(g_psx_icache_tv[0] == 1u, "replay preserved cold tag")) return 1;
if (!expect(cpu.read_absorb_which == 1u && cpu.read_absorb[1] == 44u,
"replay preserves load give-back")) return 1;
g_psx_icache_active = 0;
psx_icache_fetch_interp(&cpu, 0x80010000u);
if (!expect(test_cycles == 0u, "disabled cache model is free")) return 1;
if (!expect(cpu.read_absorb_which == 1u && cpu.read_absorb[1] == 44u,
"disabled cache preserves load give-back")) return 1;
puts("PASS: interpreter I-cache hit path preserves miss/refill semantics");
return 0;
}