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

9.4 KiB
Raw Blame History

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