Files
retroDE_ps2/docs/ch293_closeout.md
T
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

189 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).