Files
retroDE_ps2/docs/ch291_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

146 lines
5.3 KiB
Markdown

# Ch291 closeout — paired syscall 0x16 HLE; verdict flips back to opcode (SYNC)
**Status:** Closed. **Verdict from re-running qbert.elf:**
`elf_first_unsupported_opcode (pc=0x00112994 instr=0x0000000F)`
opcode 0x00 SPECIAL + funct 0x0F = **MIPS SYNC** (memory ordering
barrier).
qbert advanced 27,950 → **27,954 retires (+4)** through the paired
enable call. The runner observer captured both halves of the
paired sequence and confirmed Codex's prediction was exact:
```
syscall_0x12 = seen=1 count=1 first_pc=0x00112a54 $a0=0x05 $a1=0x00112ab0 $a2=0x00000000 $a3=0x001328c0 → $v0=0
syscall_0x16 = seen=1 count=1 first_pc=0x00112a74 $a0=0x05 $a1=0x00112ab0 $a2=0x00000000 $a3=0x001328c0 → $v0=0
```
**Args literally identical between 0x12 and 0x16.** The
"Add*Handler + Enable*Handler" hypothesis is confirmed.
## What landed
### Dispatcher case — `rtl/ee/ee_core_stub.sv`
The 7th case in the Ch273 HLE switch:
```sv
32'h0000_0016: begin
regfile[2] <= 32'd0;
gpr128[2] <= 128'd0;
pc <= pc + 32'd4;
retire_pulse <= 1'b1;
state <= S_IFETCH_REQ;
end
```
Per Codex: do NOT call the handler, do NOT synthesize DMAC
completion or interrupt yet. Just accept the enable.
### TB extension — `tb_ee_core_syscall_hle.sv`
Same mechanical pattern as Ch285/Ch289/Ch290. The TB now covers
seven known syscall numbers (3C / 3D / 40 / 64 / 78 / 12 / 16) plus
the unknown-halt path.
### Runner observer — `tb_ee_core_elf_runner.sv`
Third observer in the library (after 0x78 and 0x12). The SUMMARY
block now has three named-syscall lines, each with first PC + args
+ return.
## The paired-call signal, confirmed
| Field | Syscall 0x12 (Ch290) | Syscall 0x16 (Ch291) |
|-------|---------------------|----------------------|
| PC | 0x00112A54 | 0x00112A74 |
| $a0 | 0x00000005 | 0x00000005 |
| $a1 | 0x00112AB0 | 0x00112AB0 |
| $a2 | 0x00000000 | 0x00000000 |
| $a3 | 0x001328C0 | 0x001328C0 |
| count | 1 | 1 |
| $v0 | 0 | 0 |
PCs are 0x20 = 32 bytes = 8 instructions apart. Between them
qbert likely just loaded the same args back into $a0..$a3 from a
saved-arg block or kept them in place. The shape is unambiguous:
**register handler with `Add*Handler` then activate with
`Enable*Handler`, both for handler slot 5 with fn pointer
0x00112AB0 and context pointer 0x001328C0.**
The runner observer's "paired count=1" output is the kind of
visibility that justified the Ch289-introduced pattern. Without
it, knowing whether qbert called 0x16 with the same args as 0x12
would require re-reading the trace file or hierarchically peeking
at registers from a custom debug TB.
## qbert progression
| Chapter | Blocker | retire_count |
|---|---|---|
| Post-Ch289 (syscall 0x78) | syscall 0x12 | 27,930 |
| Post-Ch290 (syscall 0x12) | syscall 0x16 | 27,950 |
| **Post-Ch291 (syscall 0x16)** | **SYNC (instr=0x0000000F) at 0x00112994** | **27,954** |
The +4 retires are: syscall 0x16 retire + jump back into a code
block ending with SYNC. PC jumped *backward* from 0x00112A74 (the
syscall) to 0x00112994 (the SYNC). This is the typical
post-registration pattern: return from the kernel-call wrapper,
issue a memory barrier to ensure the registration is visible
globally, then proceed.
## Ch292 framing — MIPS SYNC
Instr `0x0000000F` decodes as:
- opcode 0x00 (SPECIAL)
- funct 0x0F (= 15)
- rs/rt/rd/sa all 0
MIPS SYNC: architecturally, a memory-ordering barrier. In our
model, with no out-of-order memory access and no
multiprocessor coherence to worry about, SYNC is a semantic NOP.
Concrete Ch292 scope (mirrors Ch286's narrow EI accept):
1. `localparam FUNC_SYNC = 6'h0F;` — wait, this clashes with the
already-reserved `FUNC_*` namespace. May need
`FUNC_SYNC` to be added cleanly or use a different name.
2. `is_sync = is_special && (func == FUNC_SYNC)` decode.
3. Add `!is_sync` to the SPECIAL nop-class exclusion (the funct
"anything not in {0x00..., DSLL, ADD/ADDU/SUB/SUBU, ..., MFHI/MFLO,
MULT/MULTU/DIV/DIVU, SLL, SRL, SRA, SLLV, SRLV, SRAV}" branch).
4. Accept in default execute path: PC += 4, retire fires, no
GPR/HI/LO writeback (none of the writeback predicates match
`is_sync`).
5. Focused TB: execute SYNC, verify no trap + PC advances + no
register damage.
~5 RTL edits. Should be a one-shot chapter.
## Pattern review (21 chapters)
The runner observer library now tracks three syscalls:
| $v1 | Tracked | First args observed |
|-----|---------|----------------------|
| 0x78 | Ch289 | (0, 0x00130000, 0x20000000, 0x001328C0) |
| 0x12 | Ch290 | (5, 0x00112AB0, 0, 0x001328C0) |
| 0x16 | Ch291 | (5, 0x00112AB0, 0, 0x001328C0) — identical to 0x12 |
The shared `$a3 = 0x001328C0` across syscalls 0x78, 0x12, and
0x16 is a strong hint that this is a **global context pointer**
likely qbert's main kernel-state struct or thread control block.
## Files changed
- `rtl/ee/ee_core_stub.sv` — one new HLE case.
- `sim/tb/integration/tb_ee_core_syscall_hle.sv` — extended with
syscall 0x16 case.
- `sim/tb/integration/tb_ee_core_elf_runner.sv` — syscall_0x16
observer + SUMMARY line.
No new TB, no new Makefile target; regression count unchanged at
**175/175**.
## Regression
**175/175 PASS** (unchanged from Ch290; no new TB).