// retroDE_ps2 — ee_bootstrap_mmio_stub // // Latched-register-file stub for the EE "bootstrap MMIO" window at // physical `0x1F80_0000 - 0x1F80_FFFF` (64 KiB). Covers the real // PS2 MCH (memory controller), SBUS gateway, and RDRAM init // registers the BIOS touches very early in boot. This is the // narrowest thing that closes the poisoned-dataflow hole found by // chapter 7.99: before this module existed, the EE map returned // `0xDEADBEEF` for every CPU read in this window, and the BIOS // laundered that poison into a data structure whose later // traversal wedged the core forever. // // Semantics (deliberately simple, not architecturally accurate): // - Full window is a 16 KiB word-addressed register file; all // registers reset/init to 0. // - Writes latch per-byte: for each `wr_be[i]` that is asserted, // `regs[addr[15:2]][8*i +: 8] <= wr_data[8*i +: 8]`. Untouched // byte lanes preserve their existing value. This makes SB/SH // write-through-this-window safe — prior chapters added SB/SH // for BIOS progress, and without be-aware latching a sub-word // store here would clobber the other three (or two) bytes. // - Reads return the currently-latched value, one-cycle latency, // matching the rest of the stub ecosystem. // - No side effects, no per-register behavior (no ready-bit // auto-set, no interrupt generation, no state machines). // // That keeps BIOS read/modify/write sequences self-consistent: // if the BIOS reads reg X, ORs a bit, writes back, it sees the // merged value on the next read. It does NOT emulate real // hardware semantics (e.g. status bits that flip on their own, // interrupt latches, FIFO behavior). If the BIOS tripwire-depends // on any of that, it will reveal itself the same way the 0x14B4 // linked-list wedge did — via a new diagnostic signal, handled // in a future chapter. // // Trace: // Per-access event on SUBSYS_MEM with the region tag // `REGION_EE_MISC_MMIO = 9`. arg0 is the 16-bit offset within // the window (not the full 32-bit address — the map's own // trace already carries the full address; the stub's finer // trace carries the offset so downstream analysis can see // which register was touched without having to mask). arg1 is // the data (write data, or the value being returned on read). // arg3 is the region constant. flags bit 0 = write. // // Size cost: 16384 × 32 bits ≈ 64 KiB of sim memory. Negligible. `timescale 1ns/1ps module ee_bootstrap_mmio_stub import trace_pkg::*; #( // Ch202 — narrow "ready" return for offset 0x1814. Pre-Ch201 the // window returned the latched register value (which initialises to // 0); the BIOS at PC=0xBFC4FB04..FB30 polls this address waiting // for ($read & $mask) != 0 and our zero return left it spinning. // Default = 32'hFFFFFFFF satisfies any non-zero mask the BIOS may // hold in $a0 — wider than a real PS2 GPUSTAT (typical idle = // 0x1C00_0000), but the BIOS has not been observed to USE the // value beyond the bit-test so the wider satisfaction is safe. // A future chapter can narrow this if a side-effect is observed. parameter logic [31:0] MMIO_1814_RDY_VALUE = 32'hFFFF_FFFF, // Ch258 — IOP DMAC PCR realism stub. The IOP DMAC Priority Control // Register lives at phys 0x1F8010F0 (= EE kseg1 0xBF8010F0). Real // PS1/IOP hardware resets this to 0x07654321 (priority 1 for ch0, // 2 for ch1, ... 7 for ch6, with bit[31:24]=0x07 as the enable // mask). Ch218 observer captured BIOS reading this address three // times during the Ch215 longjmp treadmill (PC=0xbfc4d2cc / // 0xbfc4d2dc / 0xbfc4d350), all returning 0 from our latched-zero // stub. Whether the zero return is the cause of the treadmill or // an incidental noise read is open — Ch258's job is to flip the // PCR to its real reset value and re-observe. // // This is a REALISM STUB, not a fix. We are not modelling the // IOP DMA channel priority semantics; we are just declining to // return poison-zero for a named hardware register with a known // reset value. If BIOS escapes the Ch215 treadmill after this // change, great. If it does not, Ch258 closes with "PCR was not // the gate" and we name the next observed blocker. parameter logic [31:0] MMIO_10F0_PCR_VALUE = 32'h0765_4321 ) ( input logic clk, input logic rst_n, // Write port input logic reg_wr_en, input logic [15:0] reg_wr_addr, input logic [31:0] reg_wr_data, input logic [3:0] reg_wr_be, // Read port — 1-cycle latency, matches rest of stub ecosystem input logic reg_rd_en, input logic [15:0] reg_rd_addr, output logic [31:0] reg_rd_data, output logic reg_rd_valid, // Ch259 / Ch260 — DIAGNOSTIC source-injection port for the named // IOP INTC view at 0x1F801070/0x1F801074. DEFAULT IS ZERO in every // existing instantiation (tb_ee_bootstrap_mmio.sv and // tb_ee_core_bios_smoke.sv both tie this to 16'd0 unless the // BIOS-long TB's +IOP_INTC_BOOT_SRC plusarg overrides it). // // When non-zero, each set bit is ORed into I_STAT every cycle so // the assertion survives W1C clears (matches the "real device // asserts the line until serviced" shape, not a one-shot pulse). // // This port exists ONLY as a controlled diagnostic knob. Ch259 // closed the BIOS-mmio-probe arc with the finding that single // synthetic source bits do not break the Ch215 treadmill — the // multi-state IOP/SBUS/kernel activity is needed instead. Any // future use of this port should be similarly scoped (TB-driven, // documented intent, default-zero on instantiation). input logic [15:0] iop_intc_inject_src_i, // Trace output logic ev_valid, output subsys_e ev_subsys, output event_e ev_event, output logic [63:0] ev_arg0, output logic [63:0] ev_arg1, output logic [63:0] ev_arg2, output logic [63:0] ev_arg3, output logic [31:0] ev_flags ); localparam int WORDS = 16384; // 64 KiB / 4 localparam logic [63:0] REGION_EE_MISC_MMIO = 64'd9; logic [31:0] regs [0:WORDS-1]; initial begin for (int i = 0; i < WORDS; i++) regs[i] = 32'd0; end logic [13:0] wr_idx; logic [13:0] rd_idx; assign wr_idx = reg_wr_addr[15:2]; assign rd_idx = reg_rd_addr[15:2]; // Per-byte write latch — honors reg_wr_be so SB/SH through this // window preserves the untouched byte lanes instead of clobbering // the whole 32-bit register. always_ff @(posedge clk) begin if (rst_n && reg_wr_en) begin if (reg_wr_be[0]) regs[wr_idx][ 7: 0] <= reg_wr_data[ 7: 0]; if (reg_wr_be[1]) regs[wr_idx][15: 8] <= reg_wr_data[15: 8]; if (reg_wr_be[2]) regs[wr_idx][23:16] <= reg_wr_data[23:16]; if (reg_wr_be[3]) regs[wr_idx][31:24] <= reg_wr_data[31:24]; end end // Read — 1-cycle latency. Ch202: offset 0x1814 ignores the latched // register and returns MMIO_1814_RDY_VALUE so the BIOS bit-test // poll satisfies (read & mask) != 0 on the first read. Writes to // 0x1814 still latch into regs[]; a future chapter can promote // 0x1814 to a true read-write register if BIOS-write semantics // matter, but the current observed behavior is read-only-status. // Ch258 adds the same shape for offset 0x10F0 (IOP DMAC PCR). // Ch259 promotes 0x1070 (IOP INTC I_STAT) and 0x1074 (I_MASK) // OUT of the anonymous regfile into named INTC behavior — W1C // on STAT writes, plain-write on MASK writes, sticky source // injection from `iop_intc_inject_src_i`. Matches the existing // `rtl/intc/intc_stub.sv` shape exactly so the EE-side view of // the IOP INTC behaves like the IOP-side view does. localparam logic [13:0] OFFSET_1814_WIDX = 14'h0605; // 0x1814 >> 2 (1541) localparam logic [13:0] OFFSET_10F0_WIDX = 14'h043C; // 0x10F0 >> 2 (1084) localparam logic [13:0] OFFSET_1070_WIDX = 14'h041C; // 0x1070 >> 2 (1052) localparam logic [13:0] OFFSET_1074_WIDX = 14'h041D; // 0x1074 >> 2 (1053) // Ch259 — named IOP INTC state. Independent of the anonymous // regs[] (writes to 0x1070/0x1074 still update regs[] via the // generic per-byte latch above, but reads bypass it for these // offsets, matching the Ch202/Ch258 override pattern). logic [15:0] iop_intc_stat_q; logic [15:0] iop_intc_mask_q; wire [15:0] iop_intc_stat_w1c_mask = (reg_wr_en && wr_idx == OFFSET_1070_WIDX && (®_wr_be)) ? reg_wr_data[15:0] : 16'd0; wire iop_intc_mask_wr_en = reg_wr_en && wr_idx == OFFSET_1074_WIDX && (®_wr_be); always_ff @(posedge clk) begin if (!rst_n) begin iop_intc_stat_q <= 16'd0; iop_intc_mask_q <= 16'd0; end else begin // I_STAT: W1C of cleared bits, OR'd with sticky injection. // Assertion-wins on same-cycle W1C+source collision — // matches `intc_stub.sv` lines ~102-110 so we don't // swallow an interrupt that's still held. iop_intc_stat_q <= (iop_intc_stat_q & ~iop_intc_stat_w1c_mask) | iop_intc_inject_src_i; if (iop_intc_mask_wr_en) iop_intc_mask_q <= reg_wr_data[15:0]; end end wire [31:0] iop_intc_stat_read = {16'd0, iop_intc_stat_q | iop_intc_inject_src_i}; wire [31:0] iop_intc_mask_read = {16'd0, iop_intc_mask_q}; always_ff @(posedge clk) begin if (!rst_n) begin reg_rd_data <= 32'd0; reg_rd_valid <= 1'b0; end else begin reg_rd_valid <= reg_rd_en; if (reg_rd_en) begin if (rd_idx == OFFSET_1814_WIDX) reg_rd_data <= MMIO_1814_RDY_VALUE; else if (rd_idx == OFFSET_10F0_WIDX) reg_rd_data <= MMIO_10F0_PCR_VALUE; else if (rd_idx == OFFSET_1070_WIDX) reg_rd_data <= iop_intc_stat_read; else if (rd_idx == OFFSET_1074_WIDX) reg_rd_data <= iop_intc_mask_read; else reg_rd_data <= regs[rd_idx]; end end end // Trace emission — one event per cycle, write wins on same-cycle // collision (mirrors the rd/wr_en mutual-exclusion at the map level; // this is defensive for mechanical safety). always_ff @(posedge clk) begin if (!rst_n) begin ev_valid <= 1'b0; ev_subsys <= SUBSYS_MEM; ev_event <= EV_WRITE; ev_arg0 <= 64'd0; ev_arg1 <= 64'd0; ev_arg2 <= 64'd0; ev_arg3 <= 64'd0; ev_flags <= 32'd0; end else if (reg_wr_en) begin ev_valid <= 1'b1; ev_subsys <= SUBSYS_MEM; ev_event <= EV_WRITE; ev_arg0 <= {48'd0, reg_wr_addr}; ev_arg1 <= {32'd0, reg_wr_data}; ev_arg2 <= 64'd0; ev_arg3 <= REGION_EE_MISC_MMIO; ev_flags <= 32'h0000_0001; end else if (reg_rd_en) begin ev_valid <= 1'b1; ev_subsys <= SUBSYS_MEM; ev_event <= EV_READ; ev_arg0 <= {48'd0, reg_rd_addr}; ev_arg1 <= (rd_idx == OFFSET_1814_WIDX) ? {32'd0, MMIO_1814_RDY_VALUE} : (rd_idx == OFFSET_10F0_WIDX) ? {32'd0, MMIO_10F0_PCR_VALUE} : (rd_idx == OFFSET_1070_WIDX) ? {32'd0, iop_intc_stat_read} : (rd_idx == OFFSET_1074_WIDX) ? {32'd0, iop_intc_mask_read} : {32'd0, regs[rd_idx]}; ev_arg2 <= 64'd0; ev_arg3 <= REGION_EE_MISC_MMIO; ev_flags <= 32'd0; end else begin ev_valid <= 1'b0; end end endmodule : ee_bootstrap_mmio_stub