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