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

165 lines
6.4 KiB
Markdown

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