Files
retroDE_ps2/docs/ch261_closeout.md
thejayman77 ec82764bef Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression
(272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps,
and all dump-derived textures/traces) is excluded via .gitignore and stays local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:10:50 -04:00

7.0 KiB

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.