Files
retroDE_ps2/docs/ch293_closeout.md
thejayman77 ec82764bef Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression
(272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps,
and all dump-derived textures/traces) is excluded via .gitignore and stays local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:10:50 -04:00

7.1 KiB
Raw Permalink Blame History

Ch293 closeout — syscall 0x7A HLE; the opcode-trap era ENDS

Status: Closed. Verdict from re-running qbert.elf: elf_timeout_with_hot_pc (watchdog after 50000000 ns, 1661413 retires, hot_pc=0x0011242C count=29/256) — for the first time qbert is not hitting an opcode trap, an unmapped MMIO, or an unhandled syscall. It's running real code in a steady-state loop.

The 60× retire jump

Chapter retire_count delta verdict
Post-Ch292 (SYNC) 27,968 unhandled_syscall (0x7A)
Post-Ch293 (syscall 0x7A) 1,661,413 +1,633,445 timeout_with_hot_pc

That's a 60× advance in a single chapter. The 27k retires it took us 23 chapters (Ch271..Ch292) to accumulate is now barely 1.6% of where we are.

What changed

The mechanical Ch289-pattern extension landed exactly:

Dispatcher case — rtl/ee/ee_core_stub.sv

32'h0000_007A: begin
    regfile[2]   <= 32'd0;
    gpr128[2]    <= 128'd0;
    pc           <= pc + 32'd4;
    retire_pulse <= 1'b1;
    state        <= S_IFETCH_REQ;
end

The 8th case in the Ch273 HLE switch. ~10 LOC.

TB extension — tb_ee_core_syscall_hle.sv

Same mechanical pattern (slots / latch / assertion / display). The TB now covers eight known syscalls (3C / 3D / 40 / 64 / 78 / 12 / 16 / 7A) plus the unknown-halt path.

Runner observer — tb_ee_core_elf_runner.sv

Fourth observer in the library (after 0x78, 0x12, 0x16). The SUMMARY block now self-documents all four tracked syscalls.

What the runner showed

syscall_0x78 = seen=1 count=1      first_pc=0x00112aa4
syscall_0x12 = seen=1 count=1      first_pc=0x00112a54
syscall_0x16 = seen=1 count=1      first_pc=0x00112a74
syscall_0x7A = seen=1 count=181494 first_pc=0x00110994  ← !!!

count=181,494 for syscall 0x7A. qbert is in a loop calling SyncDCache on the order of every 9 retires. At halt-time the observer's first-occurrence $a0=0x80000000 but the live halt- time $a0=0x00000004 — qbert is iterating sync ranges (likely "sync this address" with the address changing each iteration).

hot_pc = 0x0011242C (count 29/256) is the loop center. The 181k SyncDCache calls suggest the loop body is something like:

loop:
    <modify data at addr>
    syscall SyncDCache(addr)
    advance addr
    branch back to loop

Or:

loop:
    <poll some completion bit>
    syscall SyncDCache(stale_cache_line)
    branch back to loop if not done

Without examining the disassembly at 0x0011242c we can't tell which. But the SUMMARY's "qbert reached entry and ran real code" language is now literally correct — this isn't the runner's boilerplate "expected verdict for synthetic" case; this is real qbert.elf execution.

What this means for the project

The opcode-trap whack-a-mole phase is over. Ch271..Ch292 exhaustively added every R5900 opcode and MMIO surface qbert needs to reach init quiescence. Ch293's tiny addition (syscall 0x7A HLE) was the last brick.

The next blocker is not "implement opcode X" or "stub MMIO Y" — it's "qbert is waiting on something we haven't modelled." The possibilities, ranked by likelihood:

  1. DMAC interrupt delivery. Ch290/291 registered + enabled a handler on DMAC channel 5 (SIF0). The handler at 0x00112AB0 was never called because the model has no interrupt-delivery path from DMAC completion → COP0 Cause/Status → handler invocation. qbert may be polling for handler-side state that never updates.
  2. VBLANK / VSYNC. PS2 game loops typically wait for VBLANK to advance frame state. The model has no VBLANK generator yet (GS PCRTC stub doesn't emit the VSYNC interrupt).
  3. A specific kernel-state poll. qbert might be reading a global flag (e.g., a thread-control-block field) that some missing kernel service should update.
  4. A combination — most PS2 game main loops wait on multiple signals (VBLANK + DMAC-complete + thread-message).

Ch294 framing — investigation, not mechanical extension

The opcode-by-opcode + syscall-by-syscall mechanical recipe that served Ch271..Ch293 is no longer the right approach. The next chapter needs to:

  1. Disassemble the loop body at hot_pc = 0x0011242C (and immediately adjacent PCs in the ring) to understand what qbert is checking each iteration.
  2. Trace what memory addresses / MMIO addresses qbert reads in the loop. The runner already emits a per-retire trace; the trace file at sim/traces/ee_core_elf_runner_core.trace should show every read with EA + region.
  3. Identify the specific service that's missing — most likely an interrupt-delivery path or a VBLANK generator.

Concrete first investigation step: read the qbert.elf disassembly around 0x00112400..0x00112460 (~16 instructions covering the hot_pc and its likely branch targets). This will identify the exact wait condition.

Codex should frame Ch294 as an investigation chapter — like the Ch263..Ch269 BIOS-treadmill autopsies — not as another mechanical extension. The right output is a "what is qbert waiting on" answer + a concrete proposal for the minimal model change that breaks the wait.

Cumulative HLE coverage at the inflection point

$v1 Probable name Return Chapter qbert call count
0x3C EndOfHeap SYSCALL_HEAP_END Ch273 not observed
0x3D InitMainThread 0 Ch273 not observed
0x40 SetV*Handler? 0 Ch285 not observed
0x64 FlushCache 0 Ch273 not observed
0x78 (kernel setup) 0 Ch289 1
0x12 AddDmacHandler? 0 Ch290 1
0x16 EnableDmacHandler? 0 Ch291 1
0x7A SyncDCache? 0 Ch293 181,494

The count=1 entries are init-time calls. count=181,494 is the "main loop is grinding" signal — and it's only that one syscall. Whatever the loop is, SyncDCache is its central operation.

Files changed

  • rtl/ee/ee_core_stub.sv — one new HLE case (~10 LOC).
  • sim/tb/integration/tb_ee_core_syscall_hle.sv — extended with syscall 0x7A.
  • sim/tb/integration/tb_ee_core_elf_runner.sv — syscall_0x7A observer + SUMMARY line.

No new TB, no new Makefile target; regression count unchanged at 176/176.

Pattern review (23 chapters)

Phase Chapters Description
Opcode-blocker era Ch271..Ch286 New R5900 opcodes, one per chapter
MMIO era Ch287..Ch288 DMAC ctrl + per-channel surfaces
Syscall HLE era Ch273, 285, 289, 290, 291, 293 Six narrow $v0=0 extensions
Narrow-NOP era Ch286 (EI), Ch292 (SYNC) Side-effect-free accepts
Investigation era Ch294+ Find what qbert is waiting on

The "opcode + MMIO + syscall HLE" toolkit accumulated over the previous 23 chapters has now exhaustively covered everything qbert demands during its init phase. The remaining work is model fidelity: making the system actually deliver the asynchronous events (interrupts, VBLANK, scheduled threads) that real PS2 hardware provides.

Regression

176/176 PASS (unchanged from Ch292; no new TB).