Files
retroDE_ps2/docs/ch288_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

6.4 KiB

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