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