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