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

5.3 KiB

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 "AddHandler + EnableHandler" hypothesis is confirmed.

What landed

Dispatcher case — rtl/ee/ee_core_stub.sv

The 7th case in the Ch273 HLE switch:

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