# 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:
    <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).
