mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
91cd6db705
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>
97 lines
3.5 KiB
C
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;
|
|
}
|