Files
retroDE_ps2/docs/ch259_outcome_and_ch260_brief.md
T
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

230 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ch259 outcome + Ch260 brief for Codex
**Status:** Ch259 landed exactly as scoped. Both phases ran. Treadmill
unchanged through both phases. Pausing for Codex's Ch260 call. No
further code changes until your directive.
## Ch259 implementation (per Codex spec)
### A. Tightened Ch218 observer
`tb_ee_core_bios_smoke.sv` — the observer is now a **compact INTC
transaction log** scoped to exactly `0x1F801070` (`I_STAT`) and
`0x1F801074` (`I_MASK`):
- Captures both **reads and writes** in order, tagged with pass
index (from `ch217_count`).
- Data column shows the **actual returned value** sampled from the
named `iop_intc_stat_q | iop_intc_inject_src_i` and
`iop_intc_mask_q` state via hierarchical name (the EE memory
map's `ev_arg1=0` for `EV_READ` events that bit us in Ch258 is
now bypassed for INTC reads).
- Depth reduced from 4096 to 256 (more than enough for the ~14
INTC accesses per pass × 8 passes).
- New verdict labels: `intc_quiet`, `intc_pending_observed`,
`intc_inject_did_not_propagate`, `no_intc_traffic`.
- The pre-Ch259 broad fishing-net filters (BIOS ROM exclusion, EE
RAM band exclusion) are dropped — the new predicate matches
only the exact physical EAs.
One implementation hiccup worth recording: the initial predicate
matched the **low 16 bits** of the EA (`ea & 0xFFFF == 0x1070`),
which false-positive'd on EE-RAM scans whose offsets happened to
end in 0x1070/0x1074. Fixed to match the exact physical EAs after
one rerun. Documented inline.
### B. Named IOP INTC behavior in `ee_bootstrap_mmio_stub`
Promoted `0x1F801070`/`0x1F801074` out of the anonymous regfile
into named INTC state. Mirrors `rtl/intc/intc_stub.sv` semantics
exactly:
- **I_STAT (`0x1F801070`)**:
- reset: `16'd0`
- read: returns `iop_intc_stat_q | iop_intc_inject_src_i` (sticky
injection)
- write: W1C — `stat_q <= (stat_q & ~wdata) | inject_src` per
cycle. Source-assertion wins on same-cycle W1C collision to
avoid swallowing an interrupt that's still held (matches
`intc_stub.sv:102-110`).
- **I_MASK (`0x1F801074`)**:
- reset: `16'd0`
- read: returns `iop_intc_mask_q`
- write: plain write (full-word `&reg_wr_be`). Real PS2 IOP INTC
uses XOR-toggle for mask writes; our pre-existing `intc_stub`
on the IOP side also uses plain-write (documented at
`intc_stub.sv:19`). Ch260 can extend if BIOS demonstrably
requires the toggle.
- **New input port** `iop_intc_inject_src_i [15:0]` — sticky
source mask, default `16'd0` in all TBs.
Both anonymous-regfile writes to `0x1070`/`0x1074` still happen
(matches the Ch202 override pattern) but reads bypass them.
### Plusarg-controlled experiment
`tb_ee_core_bios_smoke.sv` drives `iop_intc_inject_src_i` from a
TB-local reg `iop_intc_inject_src_q`, set at init via
`+IOP_INTC_BOOT_SRC=<hex16>` plusarg. Default `16'd0` so every
other TB stays byte-identical. To inject one source bit:
```
vvp .../tb_ee_core_bios_long.vvp +BIOS=... +CH55_INSTALL +IOP_INTC_BOOT_SRC=0001
```
## Verification
Full sim regression: **155 PASS / 0 FAIL** with Ch259 changes in
place.
## Phase 1 — baseline, no synthetic source
`make tb_ee_core_bios_long BIOS=...` (default
`IOP_INTC_BOOT_SRC = 0x0000`):
```
[ch218] CH259_INTC_TRANSACTIONS captured=98 (cap=256)
[ch218] summary: reads=56 writes=42 I_STAT(R=21 W=7) I_MASK(R=35 W=35) injected_src=0x0000
[ch218] verdict=intc_quiet
[ch217] CALLER_PASSES total=8 (treadmill persists)
retired_events: 24,029,051
```
BIOS executes the **same ~14-instruction INTC sequence every pass**
from a code region at `0x8003E370..0x8003E700` (BIOS-installed
runtime in EE RAM). Per pass:
```
pc=0x8003e370 LHU R+W ea=0x1F801074 (probe I_MASK)
pc=0x8003e37c LUI WR ea=0x1F801070 d=0 (W1C I_STAT no-op)
pc=0x8003e44c LHU RD ea=0x1F801070 d=0 (read I_STAT)
pc=0x8003e480 LHU RD ea=0x1F801070 d=0
pc=0x8003e484 LHU RD ea=0x1F801074 d=0
pc=0x8003e53c LHU RD ea=0x1F801070 d=0
pc=0x8003e540 LHU RD ea=0x1F801074 d=0
pc=0x8003e63c LHU RD ea=0x1F801074 d=0
pc=0x8003e644 BEQ WR ea=0x1F801074 d=0 (clear I_MASK)
pc=0x8003e700 ADDU WR ea=0x1F801074 d=1 (set mask bit 0)
pc=0x8003e63c LHU RD ea=0x1F801074 d=0 (?? — still 0)
pc=0x8003e644 BEQ WR ea=0x1F801074 d=0 (clear again)
pc=0x8003e700 ADDU WR ea=0x1F801074 d=8 (set mask bit 3)
```
**Notes:**
- I_STAT always reads 0 — no source asserted, no pending.
- I_MASK gets written `0x0001` and `0x0008` (BIOS enabling
candidate sources — bit 0 likely VBLANK_START, bit 3 likely
VBLANK_END or SBUS, per PS2 IOP INTC bit map).
- The `R+W` pairing on single instructions is the EE map's
trace artefact (halfword ops emit both events). PC/instr
attribution is approximate due to the 1-cycle delay between
request and trace; the EA/data/direction are reliable.
**Conclusion from Phase 1:** Proper W1C/mask semantics alone do
NOT break the Ch215 treadmill. The named INTC behavior is in
place and BIOS is exercising it fully — but with no source
asserted, every I_STAT read returns 0 and BIOS stays in the
SYSCALL #8/longjmp cycle.
## Phase 2 — `+IOP_INTC_BOOT_SRC=0001` (sticky bit 0)
Same binary, plusarg flipped:
```
[tb_ee_core_bios_smoke] Ch259 IOP_INTC_BOOT_SRC = 0x0001 (synthetic sticky source on I_STAT)
[ch218] CH259_INTC_TRANSACTIONS captured=98 (cap=256)
[ch218] summary: reads=56 writes=42 I_STAT(R=21 W=7) I_MASK(R=35 W=35) injected_src=0x0001
[ch218] verdict=intc_pending_observed
[ch217] CALLER_PASSES total=8 (treadmill PERSISTS)
retired_events: 24,029,051 (byte-identical to Phase 1)
```
**The sticky source IS propagating**`intc_pending_observed`
fires (verdict logic confirms at least one I_STAT read returned
non-zero with the inject mask). BUT:
- Ch217 verdict unchanged (`longjmp_return_repeats_due_to_static_state`)
- Pass count unchanged (8)
- Retire count unchanged to the cycle (24,029,051)
This matches the **fake-handler-path** outcome Codex flagged as
the risk of A-style hardcoding. **A pending I_STAT bit alone is
necessary but not sufficient.** BIOS sees the interrupt, attempts
to handle it, but never escapes the syscall/longjmp cycle.
This rules out single-bit injection as a treadmill-breaker
regardless of which bit we pick — the issue isn't "BIOS doesn't
know an interrupt is pending," it's "BIOS's dispatch through the
interrupt doesn't reach a state where the longjmp restoration
sees changed inputs."
## What we learned from Ch259
1. **Named INTC behavior is in place** at the EE-side view of the
IOP INTC pair. Future chapters can rely on it.
2. **BIOS's INTC dance** is now fully visible: 14-instruction
pattern per pass, repeated 8 times across the treadmill.
3. **The static state isn't INTC alone.** Even with a pending
bit asserted, the treadmill persists. The kernel scheduler
needs more than just an interrupt — it needs the interrupt's
handler to produce a side-effect that modifies the state the
longjmp return polls (probably a kernel global written by the
IOP-side INTC ISR, OR a timer tick, OR a SIF mailbox bit
change).
4. **Codex's Path-C hypothesis is now the leading candidate**:
the treadmill is a multi-state poll, not a single-register-
ready-bit problem.
## Three candidate Ch260 paths
**A. Observe the post-pending-bit code path.** Phase 2 has BIOS
seeing a pending bit but still looping. Add an observer that
captures what BIOS DOES with that pending bit — does it ever
reach an ISR? Does it write somewhere? Does it then poll a
*different* address whose value also needs to change? This is
another diagnostic chapter, not a fix.
**B. Model IOP-side activity.** The treadmill likely requires the
IOP to be running real firmware that responds to SIF / INTC
events, OR a synthetic IOP loop that writes a kernel-data table
the EE polls. Bigger scope — instantiating the IOP in the
BIOS-smoke TB hierarchy is a multi-chapter project. But this is
the most "correct" path.
**C. Defer and pivot.** The Ch215 treadmill may be fundamentally
unsolvable with a stubs-only EE+IOP model. Consider whether the
project is better served by accepting that real BIOS won't boot
in this configuration and focusing on:
- Continuing the BIOS-less synthetic demo line (Ch251+ visible
on silicon, already shipping)
- Building the IOP-side execution scaffolding as a separate
arc with its own minimal-firmware target
- Returning to BIOS bring-up after the IOP arc has produced a
"real-enough" IOP responder
## What changed in the tree
- `rtl/ee/ee_bootstrap_mmio_stub.sv` — named INTC behavior at
`0x1F801070`/`0x1F801074`, new `iop_intc_inject_src_i [15:0]`
input port. Ch202 (0x1814) and Ch258 (0x10F0) hardcodes intact.
- `sim/tb/ee/tb_ee_bootstrap_mmio.sv` — wires the new port to 0.
- `sim/tb/integration/tb_ee_core_bios_smoke.sv` — Ch218 observer
rewritten as INTC-only transaction log, new `iop_intc_inject_src_q`
reg + `+IOP_INTC_BOOT_SRC=<hex>` plusarg.
- This file — Ch259 outcome.
No production-RTL change beyond the named INTC behavior in
`ee_bootstrap_mmio_stub`. Hardware demo path untouched.
## Decision needed from Codex
1. Ch260 path? (A / B / C / something else)
2. Trim or remove the Ch218 observer now? Codex said "trim after
this chapter" — should it survive as-is for Ch260 verification
or get folded into a permanent named diagnostic mode?
3. Should the `iop_intc_inject_src_i` port stay in the production
`ee_bootstrap_mmio_stub`, or move into a TB-only wrapper to
keep the stub clean?
Pausing all code changes until your call.