timing: mult/div completion-stall in the dirty-RAM interpreter

Completes the mult/div completion-stall to the SECOND backend. The
compiled emitters (code_generator.cpp, strict_translator.cpp) already set
muldiv_ts_done on MULT/MULTU/DIV/DIVU and stall MFHI/MFLO to it; the
dirty-RAM interpreter (the path Tomba2 overlays run on) charged 0 for
mult/div, under-counting vs compiled and drifting the shared guest-cycle
timeline (the same class as the delay-slot under-charge previously fixed).

Mirror the compiled emitter exactly via the shared helpers (cpu_state.h):
  MULT  -> psx_muldiv_set(cpu, psx_mult_latency_s(gpr[rs]))
  MULTU -> psx_muldiv_set(cpu, psx_mult_latency_u(gpr[rs]))
  DIV/DIVU -> psx_muldiv_set(cpu, 37)
  MFHI/MFLO -> psx_muldiv_stall(cpu)
all under PSX_ENABLE_BLOCK_CYCLES. The latency values are already
oracle-validated EXACT on rulers #1/#2 via the compiled path; this makes
the interp apply the identical model (one shared cost fn for both backends,
per CLAUDE.md). The interp charges base cost after exec_one (vs the compiled
"+1 at top"), but the set/stall offset cancels identically, so totals match.

Validated: Tomba2 boots through to the intro FMV with no regression (the
interp executes overlay mult/div throughout boot). Runtime-only, no regen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-06-27 12:17:21 -07:00
parent 5b7ae52415
commit 75d5d1a7cc
+18
View File
@@ -1079,6 +1079,9 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) {
case 0x0F: /* SYNC */
return 0;
case 0x10: /* MFHI */
#ifdef PSX_ENABLE_BLOCK_CYCLES
psx_muldiv_stall(cpu); /* stall to mult/div completion (faithful) */
#endif
cpu->gpr[rd] = cpu->hi;
cpu->gpr[0] = 0;
return 0;
@@ -1086,6 +1089,9 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) {
cpu->hi = cpu->gpr[rs];
return 0;
case 0x12: /* MFLO */
#ifdef PSX_ENABLE_BLOCK_CYCLES
psx_muldiv_stall(cpu); /* stall to mult/div completion (faithful) */
#endif
cpu->gpr[rd] = cpu->lo;
cpu->gpr[0] = 0;
return 0;
@@ -1096,12 +1102,18 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) {
int64_t r = (int64_t)(int32_t)cpu->gpr[rs] * (int64_t)(int32_t)cpu->gpr[rt];
cpu->lo = (uint32_t)r;
cpu->hi = (uint32_t)((uint64_t)r >> 32);
#ifdef PSX_ENABLE_BLOCK_CYCLES
psx_muldiv_set(cpu, psx_mult_latency_s(cpu->gpr[rs])); /* completion deadline */
#endif
return 0;
}
case 0x19: { /* MULTU */
uint64_t r = (uint64_t)cpu->gpr[rs] * (uint64_t)cpu->gpr[rt];
cpu->lo = (uint32_t)r;
cpu->hi = (uint32_t)(r >> 32);
#ifdef PSX_ENABLE_BLOCK_CYCLES
psx_muldiv_set(cpu, psx_mult_latency_u(cpu->gpr[rs])); /* completion deadline */
#endif
return 0;
}
case 0x1A: { /* DIV */
@@ -1117,6 +1129,9 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) {
cpu->lo = (uint32_t)(a / b);
cpu->hi = (uint32_t)(a % b);
}
#ifdef PSX_ENABLE_BLOCK_CYCLES
psx_muldiv_set(cpu, 37u); /* DIV completion deadline (fixed) */
#endif
return 0;
}
case 0x1B: /* DIVU */
@@ -1127,6 +1142,9 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) {
cpu->lo = cpu->gpr[rs] / cpu->gpr[rt];
cpu->hi = cpu->gpr[rs] % cpu->gpr[rt];
}
#ifdef PSX_ENABLE_BLOCK_CYCLES
psx_muldiv_set(cpu, 37u); /* DIVU completion deadline (fixed) */
#endif
return 0;
case 0x20: /* ADD - overflow traps are delegated if they occur. */
case 0x21: /* ADDU rd, rs, rt */