# Ch288 closeout — DMAC passive per-channel surface; MMIO clear, syscall 0x78 surfaces **Status:** Closed. **Verdict from re-running qbert.elf:** `elf_first_unhandled_syscall (pc=0x00112AA4 $v1=0x78 (=120))`. qbert advanced 27,912 → **27,920 retires (+8)** — past the per-channel clear loop and on to another kernel syscall. The standout signal: **`saw_unmapped_mmio = 0`** for the first time since Ch286. The Ch287 + Ch288 combination now covers every EE DMAC MMIO surface qbert touches during its init sweep — the verdict shape flipped back to "unhandled syscall," which means qbert is back in normal control flow and the MMIO era closes (for now). ## What landed Per Codex's "lightweight per-channel register surface, no transfer FSM" framing, Ch288 delivers: ### New module — `rtl/dmac/ee_dmac_passive_chan_stub.sv` A single channel-multiplexed register stub covering five DMAC channels (the unmodeled ones): | Channel | Base | Endpoint | Internal idx | |---------|------------|-----------|--------------| | ch0 | 0x10008000 | VIF0 | 0 | | ch1 | 0x10009000 | VIF1 | 1 | | (ch2) | (0x1000A000) | (GIF) | (skip — dedicated `dmac_reg_stub` on `ee_dmac_ch2_*` ports) | | ch3 | 0x1000B000 | IPU_FROM | 2 | | ch4 | 0x1000C000 | IPU_TO | 3 ← qbert blocker | | ch5 | 0x1000D000 | SIF0 | 4 | Per channel: CHCR / MADR / QWC / TADR (4 latched 32-bit registers at offsets 0x00/0x10/0x20/0x30). Writes latch. Reads return last latched value. Reset = 0. **No transfer FSM. No start-bit side effects. No D_STAT interaction.** The module decodes the channel index from `chan_addr[15:12]`: - 0x8 → idx 0 (ch0) - 0x9 → idx 1 (ch1) - 0xB → idx 2 (ch3) - 0xC → idx 3 (ch4) - 0xD → idx 4 (ch5) - 0xA (= ch2) → silently dropped (chan_valid=0): the real GIF stub lives elsewhere; this module must not shadow it. Unknown register offsets within a valid channel: write dropped, read returns 0. ### Memory-map integration — `rtl/memory/ee_memory_map_stub.sv` Five mechanical edits (the now-standard new-region recipe): 1. `REGION_EE_DMAC_PASSIVE = 64'd14` localparam. 2. `ee_rd_is_dmac_passive` / `ee_wr_is_dmac_passive` predicates: ``` (phys[28:16] == 13'h1000) && ((phys[15:12] == 8) || (== 9) || (== B) || (== C) || (== D)) ``` The `!= 0xA` exclusion keeps ch2 GIF on its dedicated port. 3. Internal instantiation of `ee_dmac_passive_chan_stub`. 4. New `ee_rd_was_dmac_passive` latch + response-mux arm. 5. New trace branches (read AND write) emitting `REGION_EE_DMAC_PASSIVE`. **The Ch287 footgun avoided** — trace branches added at the same time as the predicate, not as a follow-up. ## TB — `tb_ee_dmac_passive_chan_stub.sv` 18 named assertions covering: 1. **ch4 reset reads zero** for all four registers. 2. **ch4 round-trip** writes/readbacks of CHCR/MADR/QWC/TADR with distinct values. 3. **Channel independence:** write to ch5; verify ch4 values unchanged; verify ch5 readback. 4. **ch2 filter:** write to chan_nibble=0xA returns 0 on read (this stub does NOT shadow ch2 — that's `dmac_reg_stub`'s territory). 5. **ch0 / ch1 / ch3 reset** verifies multi-channel initialization. 6. **Unknown register offset** on a valid channel: read returns 0, write doesn't damage the channel; the next valid read still works. Result: `errors=0 PASS` (18/18 sub-checks). ## Makefile + regression - `tb_ee_dmac_passive_chan_stub` target. - `rtl/dmac/ee_dmac_passive_chan_stub.sv` added to RTL_SRCS. - TB added to both PHONY list and `run:` master list. - Regression: 174 → **175**. ## qbert progression | Chapter | Blocker | retire_count | unmapped_mmio? | |---|---|---|:---:| | Post-Ch286 (EI) | 0x1000E010 D_STAT (unmapped MMIO) | 27,907 | YES | | Post-Ch287 (DMAC ctrl stub) | 0x1000C000 ch4 base (unmapped MMIO) | 27,912 | YES | | **Post-Ch288 (DMAC passive)** | **syscall $v1=0x78 at PC 0x00112AA4** | **27,920** | **NO** | The MMIO era (Ch287..Ch288) ran for just two chapters and added ~+13 retires worth of init-sweep coverage. With the per-channel clear loop satisfied, qbert advanced to a SECOND kernel syscall beyond the Ch285 $v1=0x40 — namely $v1=0x78 (120). Argument snapshot at halt: - `$a0 = 0x00000000` (zero / null) - `$a1 = 0x00130000` (heap-ish or code-ish) - `$a2 = 0x20000000` (high bit set; kseg0+0 = "uncached pointer" base in PS2 convention) - `$a3 = 0x001328c0` (code/data pointer-looking) Per the Ch285 framing principle ("don't over-trust the SDK name; model the observed behavior"), the right Ch289 move is probably another narrow case: `$v0 = 0; PC += 4` and see what happens. If qbert misbranches, return the `$a2` arg pattern instead. PS2 syscall 120 in the standard table is commonly cited as one of the GS-control or threading-related calls; Codex can pick the right stub-return semantics. ## Ch289 framing Two narrow options for Codex: - (a) **Add `$v1 == 0x78` to the existing HLE dispatcher** with `$v0 = 0, PC += 4`. Trivial; one switch case. - (b) **Identify the exact PS2 kernel service for syscall 120** and pick a context-aware return value (e.g. echo $a2 if it's a "register and return previous" pattern). I lean (a) for the first pass — matches the Ch285 precedent. If qbert misbranches downstream, revisit and try $a2 or $a1 as the return. ## Files changed - `rtl/dmac/ee_dmac_passive_chan_stub.sv` — new module (~160 LOC). - `rtl/memory/ee_memory_map_stub.sv` — predicate, internal instance, mux arm, trace branches. - `sim/tb/dmac/tb_ee_dmac_passive_chan_stub.sv` — new focused TB. - `sim/Makefile` — RTL_SRCS entry, new tb target, both regression lists. ## Pattern review (18 chapters) | Ch | Blocker | Edits | Pattern | |-----|--------------|-------|---------| | 271..286 | opcodes | various | opcode-era | | 287 | DMAC ctrl MMIO | ~30 | NEW MMIO stub + map routing | | **288** | **DMAC passive per-channel** | **~30** | **REUSE Ch287 internal-stub pattern** | The internal-stub pattern from Ch287 paid off in Ch288: the same predicate-instance-mux-trace mechanical sequence dropped a second MMIO region into place without disturbing the 88 TBs that use ee_memory_map_stub. The chapter cost stayed flat at ~30 edits across one new RTL file + one map extension + one TB. The trace-branch addition was done correctly at the same time as the predicate (the Ch287 footgun avoided). ## Regression **175/175 PASS** (was 174/174 in Ch287; +1 for the new tb_ee_dmac_passive_chan_stub).