// 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 && (&reg_wr_be))
            ? reg_wr_data[15:0] : 16'd0;
    wire         iop_intc_mask_wr_en =
        reg_wr_en && wr_idx == OFFSET_1074_WIDX && (&reg_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
