# 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` ```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: syscall SyncDCache(addr) advance addr branch back to loop ``` Or: ``` loop: 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).