# Ch261 closeout — synthetic IOP responder skeleton + arbitration fix

**Status:** Closed. All Codex Ch261 acceptance criteria met. Regression
green at **157 / 157** (was 155 pre-Ch261, +1 for the collision TB,
+1 for the SIF-landing TB).

## Codex Ch261 acceptance — line-by-line

| Codex requirement                                            | Status | Where                                          |
|--------------------------------------------------------------|--------|------------------------------------------------|
| Focused collision check: CPU + DMA different addresses same cycle; DMA gets its word first, CPU later gets its own word | ✅     | `sim/tb/iop/tb_iop_memory_map_collision.sv` |
| Ch261 SIF landing TB passes with intended payload            | ✅     | `sim/tb/integration/tb_iop_responder_ee_ram_landing.sv` |
| Full regression green                                        | ✅     | `make run` → 157 PASS                          |
| Noisy per-beat diagnostics stripped after collision test exists | ✅  | `tb_iop_responder_ee_ram_landing.sv` (removed `[diag-beat]`, `[diag] iop_ram`, `[diag] DMAC regs`) |

## What landed

### RTL fix — `rtl/iop/iop_memory_map_stub.sv`

Replaced the silent-corruption arbitration with a **one-entry
deferred-CPU-RAM-read slot** exactly per Codex's spec:

- **DMA wins** the RAM port on any CPU+DMA collision (immediate).
- **CPU's read address latches** into `cpu_pend_addr` / `cpu_pend_valid`.
- On the next non-DMA cycle, the deferred read services from the
  pending slot.
- `iop_rd_valid` stays LOW for the deferred CPU read until the
  slot actually fires; then pulses normally — CPU sees its own
  data on the right cycle, just one cycle later than it would
  without contention.
- **Single-entry safe** because every existing CPU client of the
  map (`iop_exec_stub`, `iop_core_stub`, `iop_fetch_stub`) is
  request-then-wait-for-valid; no second outstanding read can be
  in flight from the same client.
- **Sim-only overflow detector** (`$error` under
  ``ifndef SYNTHESIS``) catches any future client that breaks the
  single-outstanding-read assumption.
- The pre-Ch261 comment that called the collision "documented, not
  guarded" was removed.

### New focused TB — `sim/tb/iop/tb_iop_memory_map_collision.sv`

Directly drives the map's CPU- and DMA-read ports (no exec stub, no
DMAC core), so no future change to clients can mask this regression.
Three scenarios:

1. **Collision** — both reads on the same cycle, different addresses.
   Asserts DMA gets `DMA_SENTINEL` next cycle, CPU gets `CPU_SENTINEL`
   the cycle after, `iop_rd_valid` stays low during the deferral.
2. **Solo CPU read** — no DMA contention. CPU sentinel arrives next
   cycle, no deferral.
3. **Solo DMA read** — no CPU contention. DMA sentinel arrives next
   cycle, no spurious CPU activity.

### Ch261 SIF landing TB — `sim/tb/integration/tb_iop_responder_ee_ram_landing.sv`

Restored to its natural shape — full `WRITE INTC_MASK / MADR / BCR /
CHCR=start / WAIT_IRQ / W1C INTC_STAT / READ DONE_COUNT / HALT`
script. The arbitration fix makes the previously-fatal CPU/DMA
collision (exec stub fetching WAIT_IRQ at the same cycle as DMA's
beat 0) resolve correctly: DMA gets its real first-beat data, CPU's
fetch services one cycle later.

Result: landed qword = `0xCAFEF00D12345678C0FFEE00DEADBEEF` —
exactly the expected pattern, all four payload sentinels in place,
1 DMA_DONE event, 1 halt event, `eebr_last_seen` latched. Clean
PASS in ~1.5 ms sim time, well under the 5 ms watchdog.

Diagnostic prints (`[diag-beat]`, `[diag] iop_ram words`,
`[diag] DMAC regs`) all stripped per Codex's framing — the
collision TB is now the standing arbitration regression, this TB
is the standing IOP-responder-architecture regression.

### Makefile

Both new TBs added to:
- Per-target rules: `tb_iop_memory_map_collision`,
  `tb_iop_responder_ee_ram_landing`.
- `.PHONY` list.
- `run:` master list.

(Matches `[feedback-makefile-two-lists]` — the run-list addition
that's easy to miss otherwise.)

## What we proved (Codex's Ch261 goal in one paragraph)

The existing IOP-side stubs (`iop_exec_stub` + `iop_memory_map_stub`
+ `iop_ram_stub` + `iop_dmac_reg_stub` + `intc_stub`) can be
composed with the SIF egress chain (`sif_dma_ee_ram_bridge_stub` +
`ee_ram_stub`) to produce ONE explicit EE-visible side effect — a
known 128-bit qword landing in EE RAM at a fixed offset —
autonomously from a single `go_i` pulse, with no BIOS image, no
long watchdog, deterministic ~1.5 ms runtime. The IOP responder
architecture is real and works.

## Unexpected bonus: a real bug, found and fixed

The Ch261 SIF-landing TB surfaced what the previous TBs in the IOP
chain (`tb_iop_self_driven`, `tb_iop_autonomous_two_xfers`) never
could because they only verified event counts, not DMA payload
data. The map's pre-Ch261 arbitration silently routed CPU's data
to the DMA path on collision — a latent silent-corruption bug.
Ch261 ends with that bug fixed, locked down by the focused
collision TB, and the comment in the map updated so the next
reader knows the path is now guarded.

## Files changed

- `rtl/iop/iop_memory_map_stub.sv` — deferred-CPU-slot arbitration.
- `sim/tb/iop/tb_iop_memory_map_collision.sv` — NEW focused TB.
- `sim/tb/integration/tb_iop_responder_ee_ram_landing.sv` — NEW
  composition TB (restored to natural script + diagnostics
  stripped).
- `sim/Makefile` — new per-target rules + `.PHONY` + `run:`
  entries for both TBs.
- `docs/ch261_arbitration_bug_brief.md` — finding writeup (kept for
  archaeology; Codex's pick from it became the implementation).
- `docs/ch261_closeout.md` — this file.

## What's next (for Codex's Ch262 call)

Per Codex's Ch261 framing, Ch262 should "wire that responder into
the BIOS-long setup and ask one question." Candidates that fall
out of the Ch261 result:

1. **Plug the synthetic IOP responder into the BIOS-long TB** as
   a peer that writes a sentinel into a kernel-data region BIOS
   polls (`0x80030000`+ per Ch218 v5 capture). Question: does
   BIOS escape the Ch215 treadmill when the polled region
   actually mutates between syscall #8 cycles?
2. **Asserted-source-from-the-responder INTC**: hook the
   responder's DMA-done pulse into the EE-side INTC view (via
   the Ch259 `iop_intc_inject_src_i` port, now actually driven by
   a real responder rather than a constant plusarg). Question: is
   the BIOS dispatch path satisfied by a real source pulse + a
   responder that ack-clears, vs Ch259's static-bit experiment?
3. **Keep responder isolated, add the second side effect (SIF
   mailbox flag)** — proves the responder can produce *two*
   different EE-visible side effects on its own. Lighter than
   wiring into BIOS-long.

I think (1) is the natural Ch262 — the BIOS-long arc is paused
waiting for exactly this kind of producer. (2) is the chapter
after, layering the INTC signaling on top of the RAM-write
producer. (3) is a smaller hold-pattern if Codex wants more
isolated proof before opening BIOS-long again.

Standing by for Codex's call.
