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

511 lines
27 KiB
Markdown
Raw Permalink 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.
# Wave 3 — BIOS / IOP / SBUS reconnaissance + probe arc (Ch256Ch260)
**Status: CLOSED MILESTONE (Ch260).** The Ch256-Ch259 probe arc
exhausted single-register approaches to breaking the Ch215 longjmp
treadmill. The next BIOS attempt does NOT start with another hardcode
— it starts with **IOP-side state-machine modeling**. See "Milestone
closeout" section below for the full conclusion + Ch261+ pivot.
The earlier sections of this document are preserved for posterity;
the chapter-by-chapter notes below reflect what was actually learned
during the arc, not what we initially hypothesised.
---
## Milestone closeout (Ch260)
Four chapters of probe-arc work produced four concrete facts:
1. **Ch256 recon → "primary hypothesis: EE timers."** Wrong.
`tb_ee_core_bios_long` observer eventually surfaced the real
pattern — see Ch257.
2. **Ch257 Ch218 observer landed.** After seven iterations (six more
than necessary, lesson captured in
[[feedback-pause-for-codex-on-iteration-loops]]) the observer was
able to attribute reads to the JAL callee in the longjmp-return
path. Surfaced IOP DMAC PCR + IOP INTC pair as the recurring
polled MMIO targets. Timer hypothesis didn't survive contact.
3. **Ch258 IOP DMAC PCR realism stub (0xBF8010F0 → 0x07654321) —
PCR was not the gate.** The hardcode landed cleanly (verified via
`sim/traces/rtl/ee_bios_smoke_core.trace` showing
`lw $14, 0x10f0($14)` retiring with `$14 = 0x07654321`), BIOS
reads-then-writes the value back as part of a save/modify/restore
pattern, but the treadmill behaviour is byte-identical pre- vs.
post-Ch258.
4. **Ch259 named IOP INTC behavior at `0x1F801070`/`0x1F801074` +
sticky source injection — INTC alone is not enough.**
- Phase 1 (no synthetic source): proper W1C/mask semantics landed.
BIOS exercises the full 14-instruction INTC dance every pass:
probes I_MASK, W1C's I_STAT, polls I_STAT three times, sets up
mask bits 0 and 3, repeats. Every I_STAT read returns 0. Verdict:
`intc_quiet`. Treadmill persists.
- Phase 2 (`+IOP_INTC_BOOT_SRC=0001`): the sticky source IS reaching
the EE (verdict flipped to `intc_pending_observed` — at least one
I_STAT read returned non-zero). BIOS sees the pending bit but
still loops 8 passes with byte-identical retire count
(24,029,051). Pending bit alone is necessary but not sufficient
— the dispatch-through-handler doesn't reach a state where the
longjmp restoration sees changed inputs.
**Conclusion:** the Ch215 treadmill requires **multi-state
IOP/SBUS/kernel activity** — a real IOP responder that produces
firmware-visible side effects (kernel globals written, SIF mailbox
flags toggled, INTC sources asserted in response to actual events).
Single-register hardcodes and single-bit synthetic sources do not
suffice. Ch260 closes the BIOS-mmio probe arc.
### What landed in the tree (kept)
- `rtl/ee/ee_bootstrap_mmio_stub.sv` — three named MMIO behaviors
promoted out of the anonymous regfile, all in production-shaped
form with default-safe semantics:
- **0x1814** (Ch202) — hardcoded `0xFFFFFFFF` (RDRAM-init ready
polling bit).
- **0x10F0** (Ch258) — hardcoded `0x07654321` (IOP DMAC PCR realism
stub, real PS1/IOP reset value).
- **0x1070 / 0x1074** (Ch259) — named IOP INTC view with W1C on
I_STAT, plain-write on I_MASK, sticky `iop_intc_inject_src_i`
diagnostic injection port (default 0).
- `sim/tb/integration/tb_ee_core_bios_smoke.sv` — Ch218 observer
preserved as a compact INTC transaction log, gated behind
`\`ifdef CH259_INTC_DIAG` so routine builds are silent. Re-enable
via `make tb_ee_core_bios_long_intc_diag`.
- `sim/Makefile` — new `tb_ee_core_bios_long_intc_diag` target.
### Where the next attempt starts (Ch261+)
**Do not** open Ch261 as another `ee_bootstrap_mmio_stub` hardcode.
The probe-arc has demonstrated empirically that no single MMIO ready
bit is the gate. The Ch261+ arc opens **IOP-side modeling**:
- **Phase 1 (Ch261 candidate):** a tiny synthetic IOP/SIF responder
in the TB that produces ONE meaningful kernel-visible side effect
per Ch215 pass. The simplest plausible "real side effect" is a
monotonic counter at a fixed kernel-data address (e.g.,
`0x80030000` — the kdata region BIOS scans every pass per the
Ch218 v5 capture). If BIOS's longjmp callee polls that counter
and it advances, the treadmill should break. If it doesn't, we've
isolated which side-effect shape BIOS actually needs.
- **Phase 2 (later):** flesh out the IOP-side stubs (already exist
in `rtl/iop/`, none currently in production) into a responder
that can take SIF mailbox commands and emit INTC pulses. This is
the multi-chapter "real IOP" arc.
Three structural rules captured from the arc, to apply during
Ch261+ (recorded as memory entries):
- **Pause for Codex on the second unexpected result** —
[[feedback-pause-for-codex-on-iteration-loops]].
- **Agents for breadth, source-read for runtime semantics** —
[[feedback-agents-for-breadth-not-runtime]].
- **Don't model IOP/SIF activity by hardcoding a single bit; model
the producer.** A pending-bit hardcode kicks BIOS into a fake
handler path without progressing it.
---
**Purpose:** before re-opening the BIOS treadmill, lock down what the
EE / IOP / SIF / INTC / DMAC stack actually models today versus what the
real BIOS expects, so the next chapter targets a specific dependency
rather than chasing the BIOS opcode-of-the-week.
---
## TL;DR
The real BIOS, run under `tb_ee_core_bios_smoke` with `+BIOS=`, exhibits
**two distinct failure modes** that both blocked the Ch215-Ch218 arc:
1. **The Ch215 longjmp-return treadmill.** After the Ch215 jmp_buf
restore FSM rehydrates 12 GPRs from `0xA000B1E0`, BIOS resumes at the
restored `$ra` and loops 5 times through `0xBFC52340..0xBFC52360`. The
Ch217 verdict captures the smoking gun: across passes, `$a0`, `$a1`,
and the JAL callee's `$v0` are **bit-identical**. The kernel is
asking the same question and getting the same answer every cycle.
"BIOS has no escape signal from this callee" — external state that
should flip between passes is not flipping because our stack does
not model it.
2. **The DEADBEEF wedge in EE-RAM code.** Independent of the longjmp
loop, BIOS code executing from EE RAM at `pc=0x000014C4` issues an
`LW` with a base pointer derived from an earlier UNMAPPED read.
The first UNMAPPED read's return value (the EE map's sentinel for
unmapped regions) ends up in `$6`, the wedge fires `lw $2, 0($6)`
on a poisoned base, that read is also UNMAPPED, and the resulting
value keeps the loop alive forever. The TB has a
`lineage_poison_addr` / `retire_ring` diagnostic that captures
the first-unmapped read's PC + EA on every run.
Both failures share a root cause: **the EE memory map has decode holes
in regions BIOS reaches.** The treadmill is one symptom; the DEADBEEF
wedge is another. Ch257 picks the most likely missing region and lands
a minimal model.
---
## Layer inventory — what exists today
### SIF (Sub-system Interface) — `rtl/sif/`
All 8 SIF modules are functional in simulation, **none are
instantiated in the production hierarchy** (`top_psmct32_raster_demo`,
`de25_nano_psmct32_raster_demo_top`). They live in TBs only.
| Module | Models | Faked / TODO |
|---------------------------------|-------------------------------------------------------|---------------------------------------------------------------|
| `sif_mailbox_stub` | 4-reg MSCOM / SMCOM / MSFLG / SMFLG @ EE 0x1000F200 / IOP 0x1D000000 | No directional / W1C / set-clear semantics; no IRQ; plain RW |
| `sif_mailbox_peer_stub` | TB-side IOP responder: command-echo FSM watching MSFLG | TB only; no real IOP execution |
| `sif_dma_stub` | Qword receive endpoint (128-bit ready/valid) | No consume path; buffer fills and stays full |
| `sif_dma_ack_peer_stub` | EE→IOP combined ctrl+data terminator | One-shot S_DONE; no re-arm |
| `sif_dma_iop_ram_bridge_stub` | 128→32 width adapter, qword → 4×32-bit writes | DEST_BASE_ADDR hardcoded; no ack upstream |
| `sif_dma_ee_ram_bridge_stub` | 32→128 width adapter, accumulates 4 beats | DEST_BASE_ADDR hardcoded; Ch239 rewind for single-slot pads |
| `sif_dma_ee_ack_peer_stub` | IOP→EE combined ctrl+data terminator | One-shot |
| `boot_install_agent_stub` | Synthetic exception-handler streamer → EE RAM | NOT BIOS code; canned `MFC0 / ADDIU / JR / RFE` payload only |
**Critical gap:** the EE memory map has **no decode region for
`0x1000F200-0x1000F2FF`**, so `sceSifInit()`'s mailbox accesses go to
UNMAPPED. Even if the stubs were instantiated, the EE core could not
reach them. The IOP-side map *does* have the SIF decode (`0x1D000000`
block), but the IOP CPU is not running any real firmware.
### IOP — `rtl/iop/`
| Module | Models | Faked / TODO |
|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
| `iop_core_stub` | Minimal R3000 (11 opcodes), COP0 Status/Cause/EPC triple-stack, async exception entry, `STRICT_UNSUPPORTED` trap+latch, reset vector `0xBFC00000` (BIOS) | No TLB, cache, HI/LO, R-type ALU, shifts, mul, div, BD bit, kernel/user enforcement |
| `iop_memory_map_stub` | IOP RAM 0-2 MiB, BIOS ROM 0x1FC00000+, IOP DMAC ch9 0x1F801520, IOP INTC 0x1F801070, SIF 0x1D000000, retroDE pad I/O 0x1F808500 | All other IOP I/O unmapped (SPU2, timers, other DMAC channels, real SIO2); UNMAPPED reads return `0xDEADBEEF` |
| `iop_ram_stub` | 2 MiB SRAM (default 16 KiB in TBs) | None |
| `iop_fetch_stub` | Sequential fetcher (trace-only; superseded by `iop_core_stub`) | — |
| `iop_exec_stub` | 5-opcode micro-op script engine (HALT/WRITE/READ/WAIT_IRQ/BNE; superseded by `iop_core_stub`) | — |
| `iop_dmac_reg_stub` | IOP DMAC ch9 (SIF0 IOP→EE): MADR/BCR/CHCR/DONE_COUNT, 32-bit beats from IOP RAM via memory master | Only ch9 wired; no error reporting |
| `sio2_input_stub` | Sony pad word @ retroDE 0x1F808500; 2-FF CDC from bridge domain | No real SIO2 FIFO at 0x1F808200; no DMAC ch11 |
**Critical gap:** **the IOP has no real BIOS image and never executes
beyond synthetic test programs.** Real BIOS boot expects the IOP to
fetch from `0xBFC00000` (shared BIOS ROM), execute its bootstrap, parse
`IOPBTCONF`, load IRX modules, and signal readiness back to the EE via
SIF. None of that is happening — the IOP-side mailbox writes that
would unstick the EE never come.
### INTC — `rtl/intc/intc_stub.sv`
Generic 16-source controller, reused on both EE (`0x1000F000`) and IOP
(`0x1F801070`) sides. W1C status, plain-write mask. Aggregate `cpu_irq
= |(STAT & MASK)`.
**Faked:** write-to-toggle (XOR) mask semantics not implemented (real
PS2 INTC uses XOR for atomic bit-flip — BIOS code that XORs a mask bit
will see unexpected behavior).
**Wired sources:** only DMAC completion pulses
(`dmac_reg_stub.irq_completion_o`, `iop_dmac_reg_stub.irq_completion_o`).
**Unwired:** VBLANK_START, VBLANK_END, GS_FINISH, TIMER0..3, SBUS,
VIF/VU, SIF0/SIF1 done, real SIO2, CDVD, USB, FW, IPU. The PCRTC's
`frame_seen` toggle exists in the demo wrapper but is not routed to
INTC.
### DMAC — `rtl/dmac/dmac_reg_stub.sv`
EE-side per-channel register shell + state machine. MADR / QWC / CHCR /
TADR / DONE_COUNT at offsets 0/0x10/0x20/0x30/0x40 within a channel
bank. Real 128-bit qword transfers from `ee_ram_stub` via memory
master.
**Faked:** chain mode (TADR recorded but never consulted), all errors
report code 0 (OK), no memory-contention throttling. Only ch2 (GIF)
and ch5 (SIF0) are instantiated in the production demo; channels 0, 1,
3, 4, 6-12 are register-only or absent.
### EE-side MMIO stubs — `rtl/ee/ee_*_mmio_stub.sv`
| Stub | Range | Behavior |
|----------------------------|--------------------------------|-----------------------------------------------------------------------------------------|
| `ee_biu_mmio_stub` | `0x1FFE0000-0x1FFE0FFF` (4 KiB) | Latched RW. Real BIOS writes 0x1FFE0130 for cache/BIU config (Ch9). |
| `ee_bootstrap_mmio_stub` | `0x1F800000-0x1F80FFFF` (64 KiB)| Latched RW + **one special-case**: offset `0x1814` returns hardcoded `0xFFFFFFFF` (Ch202 RDRAM-init ready bit). |
`ee_bootstrap_mmio_stub`'s Ch202 special-case is **the canonical
pattern** for "BIOS polls a hardware register expecting a ready bit;
real RDRAM init never happens; hardcode the return."
---
## The Ch215 longjmp-return treadmill — what the TB knows
`tb_ee_core_bios_smoke` runs the real BIOS dump (4 MiB at
`/home/ubuntu/Downloads/bios.hex`, passed via `+BIOS=` plusarg).
### What BIOS gets through
1. EE reset @ `0xBFC00000`, ROM bootstrap.
2. BIU/cache config writes to `0x1FFE0130` (BIU stub absorbs them).
3. `0x1F80xxxx` MCH/SBUS reads (bootstrap MMIO stub absorbs, special-
case `0x1814` returns `0xFFFFFFFF`).
4. Memory copies from BIOS ROM into EE RAM.
5. Kernel exception-handler install (handled by either
`boot_install_agent_stub` or direct memory writes).
6. **First SYSCALL #8 (`_ReturnFromException` with `$a0=2`)** —
triggers the Ch215 jmp_buf restore FSM. 12 GPRs (`$ra`, `$sp`, `$fp`,
`$s0..$s7`, `$gp`) are loaded from `0xA000B1E0`, `$v0` set to 1
(longjmp-return marker), PC restored from `$ra`.
### Where BIOS stalls
After the Ch215 restore, BIOS resumes at the restored `$ra` and loops
through `0xBFC52340..0xBFC52360`. The TB's Ch217 task captures every
pass through the JAL at `0xBFC52358` (the longjmp-return-handler call).
**Across 5 observed passes**:
- `$a0` and `$a1` to the callee are **bit-identical**.
- The callee's returned `$v0` is **bit-identical**.
Ch217 verdict (from the TB itself):
> `longjmp_return_repeats_due_to_static_state` — BIOS has no escape
> signal from this callee. The callee must be modifying something we
> are missing, OR the BIOS expects some external state (MMIO ready,
> timer, or kernel global) to flip between passes — but our model is
> not providing it.
The JAL target is computed dynamically by the TB
(`{4'hB, instr[25:0], 2'b00}`) and the callee's first 16 instructions
are dumped to the log via Ch217's `CALLED_FUNCTION dump`. **The
specific addresses the callee READS inside its body have not been
captured by the existing TB diagnostic.**
### The DEADBEEF wedge (separate, related)
Parallel to the longjmp loop, BIOS code running in EE RAM at
`pc=0x000014C4` issues an `LW` with base = `$6 = 0xDEADBEEF` (or a
derivative). The effective address `0x3084_FFFF` lands in an unmapped
region; the EE map returns the unmapped-sentinel; the value re-poisons
`$2`; the loop self-perpetuates millions of times.
The TB has full diagnostic for this: `lineage_poison_addr`,
`lineage_poison_data`, `lineage_pc`, `lineage_instr`, plus a 32-deep
retire ring (`retire_ring_pc[*]`, `retire_ring_r2[*]`,
`retire_ring_r6[*]`) that captures the 32 instructions preceding the
first UNMAPPED read. The lineage capture identifies where `$6`'s
poison originated, but the corresponding fix has not landed —
presumably because the ORIGINATING unmapped read's address class has
not yet been claimed by any stub.
---
## What's documented vs. what the kernel actually needs
Existing contracts under `docs/contracts/` are **all Draft**:
- `sif.md` — mailbox/flag exchange + SIF DMA shape; no detailed boot
sequence.
- `iop.md` — IOP as separate peer subsystem; notes IOPBOOT / module-
load NOT modeled.
- `memory.md` — emphasizes BIOS ROM mapping; explicitly does not own
BIOS boot sequencing.
- `intc.md`, `dmac.md`, `ee.md`, `gif_gs.md`, `peripherals.md`,
`platform.md`, `vif_vu.md`, `sio2_pad.md`, `spu2.md` — system
contracts, no BIOS/kernel detail.
The `wave2*_plan.md` documents are all DMA/GIF focused and explicitly
defer SIF/IOP/BIOS. **No existing doc covers the kernel's setjmp /
longjmp / `_ReturnFromException` path.** Ch214-Ch217 reverse-
engineered the layout (12-GPR frame at `0xA000B1E0`, setjmp at
`0xBFC4DB50`, post-setjmp checkpoint at `0xBFC52358`) but only
embedded the findings in the TB itself.
---
## Where the missing signal most likely lives
Cross-referencing the Ch217 verdict's three candidate categories
("MMIO ready, timer, or kernel global") against the layer inventory:
**Kernel global (RAM at `0xA000xxxx`).** Unlikely. The callee at
`0xBFC52358` is in BIOS ROM and reads `$a0`/`$a1` from the restored
GPRs. If the callee polls a kernel global, that global would be in EE
RAM. EE RAM is bidirectionally accessible; if the callee both READS
and WRITES the global, the value would change pass-to-pass. The Ch217
verdict says it does not — so either the callee writes nothing, or
the global lives in an unmapped region.
**Timer (`0x10000000-0x10001FFF`, T0/T1/T2/T3 COUNT/MODE/COMP/HOLD).**
Plausible. PS2 kernel uses one of T0-T3 for the scheduler tick. The
counter is read at fixed addresses; the count value advances with
hardware clocks regardless of CPU activity. **The EE memory map has
NO decode for `0x10000000-0x10001FFF`** — all four timers are
unmapped. A read returns the unmapped sentinel, the kernel reads the
same sentinel every pass, sees no time elapsed, loops.
**MMIO ready bit (EE INTC, SIF mailbox, or bootstrap MMIO).** Also
plausible.
- *EE INTC `0x1000F000`* is mapped (status/mask), but no sources fire
periodically. If the callee polls `INTC_STAT[VBLANK_START]` waiting
for a frame tick, the bit never sets.
- *SIF mailbox `0x1000F200`* is **not mapped at all** on the EE side.
A read returns the unmapped sentinel; if the callee polls
`SMFLG[IOP_READY]` waiting for IOP boot completion, that read is
pure UNMAPPED — and would also trigger the DEADBEEF wedge if the
sentinel is `0xDEADBEEF`.
- *Bootstrap MMIO `0x1F80xxxx`* is mapped (latched + Ch202 special-
case at `0x1814`). If the callee polls a different offset expecting
a ready bit, hardcoded special-case is the proven fix pattern.
---
## Ch257 — scoped callee-body memory-read observer (landed)
**Implementation:**
[`sim/tb/integration/tb_ee_core_bios_smoke.sv`](../sim/tb/integration/tb_ee_core_bios_smoke.sv)
gains a Ch218 observer + verdict-emitter:
- **`ch218_jal_target`** — dynamically decoded from
`peek_instr(0xBFC52358)` (the JAL whose callee Ch217 already
characterized as static-state). Pattern matches Ch217's own decode.
- **`ch218_pc_in_body`** — combinational gate, fires when the live
`core_pc` is in `[jal_target, jal_target + 0x80)`.
- **Capture array** of depth 64. Each entry records `{pass_idx (from
ch217_count), pc, instr, ea, data, rt}` per qualifying EE memory
READ event (`ee_map_ev_valid && EV_READ && SUBSYS_MEM &&
ch218_pc_in_body`). Sampled on every clock so reads within a single
pass through the callee are all captured.
- **Print task `ch218_print_callee_reads`** dumps every captured
read with its instruction mnemonic, then sweeps the array for an
EA that appears across **multiple distinct passes** and returns
**identical data**. That EA is the static-poll candidate.
- **Verdict classifier** with six labels, each one naming the
Ch258 target region directly:
| Verdict label | EA range | Ch258 action |
|------------------------------|--------------------------------|-----------------------------------------------------------|
| `timer_poll_static` | `0x10000000 0x10001FFF` | Land `ee_timer_stub` (PRIMARY) |
| `sif_mailbox_static` | `0x1000F200 0x1000F2FF` | Route `sif_mailbox_stub` ee-side into EE map decode |
| `ee_intc_static` | `0x1000F000 0x1000F1FF` | Wire VBLANK / TIMER / SIF source(s) into `intc_stub.irq_src` |
| `bootstrap_mmio_static` | `0x1F800000 0x1F80FFFF` | Add Ch202-style hardcoded ready at the polled offset |
| `ee_ram_static` | `0x00000000 0x01FFFFFF` | Preload kernel global via `boot_install_agent_stub` |
| `named_region_static` | (any other range) | Report verbatim; pick the next Ch258 hypothesis |
| `no_repeated_read_across_passes` | — | Not enough passes captured, or callee uses scratch only |
| `no_callee_reads` | — | Synthetic-CI mode (callee never reached); not applicable |
- **Wiring:** the print task is called from both the long-run halt
path (after `ch217_print_longjmp_path`) and the timeout path (the
one real-BIOS mode exits through). Synthetic CI mode skips both
call sites because they sit inside `ch213_sc8_seen`-gated blocks —
no regression noise, no behavior change to existing TBs.
**No RTL change.** No production-RTL change. No new TB. Pure TB-side
diagnostic add to an existing TB. The full sim regression stays at
**155 PASS / 0 FAIL** with the observer dormant in every TB except
the one real-BIOS run that the operator triggers manually.
## How to use Ch218 — operator command
```
cd sim
make tb_ee_core_bios_smoke BIOS=/home/ubuntu/Downloads/bios.hex
```
This runs `tb_ee_core_bios_smoke` with `+BIOS=...` so the real
4 MiB BIOS dump is loaded. The TB will eventually hit the Ch215
longjmp treadmill, loop the Ch217 callee multiple times, and
timeout. The timeout-path prints:
```
[ch217] LONGJMP_PATH_DECODE 0xBFC52350..0xBFC52390:
[ch217] ...
[ch217] verdict=longjmp_return_repeats_due_to_static_state ...
[ch218] CALLEE_BODY_READS jal_target=0x?????.??..0x?????.?? captured=N (cap=64)
[ch218] [0] pass=0 pc=0x... instr=0x... ea=0x... data=0x... rt=$. <mnemonic>
[ch218] [1] pass=0 pc=0x... ...
[ch218] ...
[ch218] verdict=<label> (...)
```
Report the `[ch218] verdict=` line. Ch258 picks the table row.
## Ch258 proposal — one narrow next state to model
**Driven by the Ch218 verdict; no decision required before observer
data lands.**
**Primary hypothesis: EE Timer block `0x10000000-0x10001FFF`.** PS2 BIOS
kernels rely heavily on T0-T3 for scheduler ticks; the entire region
is completely unmapped on the EE side (no decode entry in
`ee_memory_map_stub`). Both the longjmp treadmill AND the DEADBEEF
wedge are explained by a single root cause if the kernel polls a timer
count and the unmapped read returns the sentinel that becomes the
DEADBEEF poison.
**If Ch218 verdict = `timer_poll_static`:** land
`rtl/ee/ee_timer_stub.sv` with 4 channels × {`T_COUNT`, `T_MODE`,
`T_COMP`, `T_HOLD`} at base addresses `0x10000000`, `0x10000800`,
`0x10001000`, `0x10001800`. `T_COUNT` is RO and ticks at parameterized
rate (default: design_clk / 16 ≈ 3 MHz at 50 MHz, matching PS2 BUSCLK
/ 16). Writes latch `T_MODE`/`T_COMP`/`T_HOLD`. No IRQ wired yet (
deferred to Ch259 if needed). Wire into `ee_memory_map_stub` between
the BIU and bootstrap MMIO decode regions.
**If Ch218 verdict is anything else:** follow the table in the Ch257
section above. The verdict label names the Ch258 action directly.
**Why not chase SIF/IOP first:** the SIF gap is real and known but
it's a multi-chapter project (EE decode → mailbox in production
hierarchy → IOP BIOS → IOP CPU exercises → mailbox writes land). The
timer gap is a single self-contained stub with one parameter (tick
rate) and four registers per channel. If observation shows the callee
is a timer poll, this unsticks the treadmill in one chapter. If
observation shows otherwise, the named region is the Ch258 target.
**Acceptance for Ch258 (whichever path):**
1. The Ch218 verdict line drives the chapter choice — no judgment
call without data.
2. The chosen stub lands cleanly + the full regression stays
155 PASS / 0 FAIL.
3. Running `tb_ee_core_bios_smoke +BIOS=...` produces observable
forward motion: either Ch217's caller-passes count diverges (BIOS
exited the loop), or a new UNMAPPED region surfaces (BIOS made
progress but hit the next gap).
---
## Open questions deferred to Ch258+
- IOP BIOS execution: does the IOP need a real BIOS dump, or is a
synthetic IOP bootloader plus `IOPBTCONF` parsing enough?
- Real PS2 INTC XOR mask semantics: BIOS code that XORs a mask bit
will misbehave on plain-write `intc_stub`. Lower priority — wait
until BIOS demonstrably hits it.
- VBLANK source wiring (PCRTC `frame_seen` toggle → `intc_stub.irq_src`):
near-term if the treadmill turns out to be VBLANK-driven, but
defer until the observer confirms.
- SIF mailbox in production hierarchy: instantiate
`sif_mailbox_stub` alongside `intc_stub` at the EE-side
`0x1000F200` decode region; ALSO wire IOP-side EE-write port
through to the IOP's memory map for bidirectional handshake.
- Kernel global modeling: if the callee turns out to poll a RAM
address that some IOP-side write should mutate, that's the
trigger to land the IOP in production hierarchy (also a multi-
chapter project).
---
## Files referenced
**RTL:**
- `rtl/sif/sif_mailbox_stub.sv` (all 8 SIF stubs in `rtl/sif/`)
- `rtl/iop/iop_core_stub.sv`, `iop_memory_map_stub.sv`,
`iop_dmac_reg_stub.sv`, `sio2_input_stub.sv`
- `rtl/intc/intc_stub.sv`
- `rtl/dmac/dmac_reg_stub.sv`
- `rtl/ee/ee_biu_mmio_stub.sv`, `ee_bootstrap_mmio_stub.sv`,
`ee_memory_map_stub.sv`, `ee_core_stub.sv` (Ch215 jmp_buf FSM,
STRICT_UNSUPPORTED latch)
**TBs:**
- `sim/tb/integration/tb_ee_core_bios_smoke.sv` (Ch214/216/217
diagnostics, lineage capture, DEADBEEF wedge characterization)
- `sim/tb/integration/tb_iop_core_bios_smoke.sv` (IOP-side mirror,
STRICT_UNSUPPORTED)
**Real BIOS dump:**
- `/home/ubuntu/Downloads/bios.hex` (9 MiB ASCII, ~4 MiB binary,
`$readmemh`-ready)