// retroDE_ps2 — iop_memory_map_stub
//
// IOP-side memory map. Gives IOP-visible addresses architectural meaning.
// Wave 3 first-pass scope is deliberately narrow: only the IOP-RAM window
// is routed; every other address decodes as UNMAPPED with deterministic
// fault data. SIF registers, IOP I/O, SPU2, CDVD, and IOP-side BIOS are
// all intentionally deferred — slots noted in comments so the map can
// grow without re-shaping its interface.
//
// Contract refs:
//   docs/contracts/iop.md         (IOP-local address decode)
//   docs/contracts/memory.md      (IOP RAM lives at phys 0x00000000-
//                                  0x001FFFFF, 2 MiB)
//
// Address semantics:
//   IOP CPU side is MIPS R3000-class. kseg0/kseg1 aliases
//   (0x80000000/0xA0000000 mirrors of 0x00000000) are modelled via
//   `phys = iop_addr[28:0]`, consistent with how ee_memory_map_stub
//   strips kseg for EE fetches. Physical window decode then works on
//   the low 29 bits.
//
//   Bridge-side write port (Wave 3 addition): the SIF-to-IOP-RAM bridge
//   writes directly at physical offsets — no kseg strip. The map decodes
//   its address bits directly against the same region rules.
//
//   DMA read-master port (Wave 3 reverse-direction addition): the IOP
//   DMAC (ch9 SIF0 egress, and any other future channel) fetches source
//   bytes through this port. Physical addressing; RAM-only decode in
//   current scope. Caller provides its own master_id (convention: 4 =
//   IOP_DMAC).
//
// Arbitration (Wave 3 scope):
//   Two potential write masters on the RAM path: the IOP CPU port and
//   the bridge port. Two potential read masters on the RAM path: the
//   IOP CPU port and the DMA read master. Collisions within the same
//   cycle are not expected in the current TBs (CPU programming / readback
//   phases are separate from DMA transfer phases). Policy if they ever
//   collide: CPU wins. Documented here rather than hidden in priority
//   ordering; RAM port is mux'd accordingly.
//
// Region decode (current):
//   - IOP RAM window:           phys[28:21] == 8'b00000000
//                               (0x0000_0000 - 0x001F_FFFF, 2 MiB)
//                               → route to iop_ram_stub (offset phys[20:0])
//   - SIF registers (IOP side): phys[28:24] == 5'b11101
//                               (0x1D00_0000 block) → route to the SIF
//                               register shell with offset phys[7:0]. The
//                               mailbox stub's register surface covers
//                               offsets 0x00/0x10/0x20/0x30.
//   - IOP DMAC channel 9:       phys[28:4] == 25'h01F8_0152
//                               (0x1F80_1520 - 0x1F80_152F, 16 bytes)
//                               → route to iop_dmac_reg_stub with 4-bit
//                               offset phys[3:0]. Channel 9 is SIF0
//                               (IOP→EE) in the real PS2 DMAC map; other
//                               channels are intentionally not decoded.
//   - IOP INTC:                 phys[28:4] == 25'h01F8_0107
//                               (0x1F80_1070 - 0x1F80_107F, 16 bytes)
//                               → route to intc_stub with 8-bit offset
//                               phys[7:0]. Matches the real PS2 IOP INTC
//                               placement (I_STAT / I_MASK).
//   - Shared BIOS ROM:          phys[28:22] == 7'b1111111
//                               (0x1FC0_0000 - 0x1FFF_FFFF, 4 MiB)
//                               → route to bios_rom_stub with 22-bit
//                               offset phys[21:0]. kseg1 aliasing maps
//                               0xBFC0_0000 fetches to this window via
//                               the standard [28:0] strip. The IOP core
//                               reset vector normally points here.
//                               Writes to BIOS decode as UNMAPPED
//                               (read-only ROM).
//   - everything else: UNMAPPED, reads return 32'hDEADBEEF
//
// Future regions (reserved in comments, not wired):
//   - Other IOP DMAC channels:   0x1F80_1080-0x1F80_156F (partial block)
//   - IOP timers / SIO:          elsewhere in 0x1F80_0000 block
//   - SPU2:                      0x1F90_0000 block
//
// Trace semantics (matches ee_memory_map_stub's request-routing pattern):
//   Map-layer events describe routing (what was asked for, where it was
//   sent). Arg1 is 0 when the request is routed to a backing store that
//   will emit its own delivery event; 0xDEADBEEF on unmapped reads; the
//   actual write data on unmapped writes (so the TB can see what software
//   tried to write).
//
// Latency assumption (mirrors ee_memory_map_stub note):
//   Assumes fixed one-cycle backing-store latency. `ram_rd_valid` is not
//   consulted — the map asserts its own `iop_rd_valid` one cycle after
//   request unconditionally. All Wave 3 backing stubs honour that. If a
//   later backing store introduces wait states, the map must grow proper
//   response handshaking.
//
// Trace payload schema:
//   IOP READ      arg0=addr arg1=0            arg2=master_id arg3=region_id
//   IOP WRITE     arg0=addr arg1=wr_data      arg2=master_id arg3=region_id
//   IOP UNMAPPED  arg0=addr arg1=0xDEADBEEF   arg2=master_id arg3=0xFF
//     region_id: 2 = IOP_RAM, 3 = SIF_REGS, 4 = IOP_DMAC, 5 = IOP_INTC,
//                6 = IOP_BIOS
//     master_id: 2 = IOP_CPU, 3 = SIF bridge (writes), 4 = IOP_DMAC (reads)
//     flags bit 0: 1 = write, 0 = read

`timescale 1ns/1ps

module iop_memory_map_stub
    import trace_pkg::*;
(
    input  logic          clk,
    input  logic          rst_n,

    // ------------------------------------------------------------------
    // IOP CPU-side request interface (32-bit data, virtual address)
    // ------------------------------------------------------------------
    input  logic          iop_rd_en,
    input  logic [31:0]   iop_rd_addr,
    output logic [31:0]   iop_rd_data,
    output logic          iop_rd_valid,

    input  logic          iop_wr_en,
    input  logic [31:0]   iop_wr_addr,
    input  logic [31:0]   iop_wr_data,
    input  logic [3:0]    iop_wr_be,

    // Caller-provided master id for trace attribution. Conventional:
    //   0 = TB direct, 2 = IOP CPU (once a fetch stub exists).
    input  logic [7:0]    master_id,

    // ------------------------------------------------------------------
    // Bridge-side write port (Wave 3). Physical addresses; no kseg strip.
    // Used by sif_dma_iop_ram_bridge_stub and similar DMA-side masters.
    // Caller provides its own master_id (convention: 3 = SIF bridge).
    // ------------------------------------------------------------------
    input  logic          bridge_wr_en,
    input  logic [31:0]   bridge_wr_addr,
    input  logic [31:0]   bridge_wr_data,
    input  logic [3:0]    bridge_wr_be,
    input  logic [7:0]    bridge_master_id,

    // ------------------------------------------------------------------
    // DMA read-master port (Wave 3). Physical addressing; intended for
    // IOP DMAC ch9 reads out of IOP RAM. One-cycle read latency, same
    // pipeline shape as the CPU read. Caller provides its own master_id
    // (convention: 4 = IOP_DMAC).
    // ------------------------------------------------------------------
    input  logic          dma_rd_en,
    input  logic [31:0]   dma_rd_addr,
    input  logic [7:0]    dma_master_id,
    output logic [31:0]   dma_rd_data,
    output logic          dma_rd_valid,

    // ------------------------------------------------------------------
    // Downstream to iop_ram_stub.
    // Address presented as a 21-bit offset within the 2 MiB IOP RAM
    // window; consumers may truncate to match their backing-store width.
    // ------------------------------------------------------------------
    output logic          ram_rd_en,
    output logic [20:0]   ram_rd_addr,
    input  logic [31:0]   ram_rd_data,
    input  logic          ram_rd_valid,

    output logic          ram_wr_en,
    output logic [20:0]   ram_wr_addr,
    output logic [31:0]   ram_wr_data,
    output logic [3:0]    ram_wr_be,
    output logic [7:0]    ram_master_id,

    // ------------------------------------------------------------------
    // Downstream to the SIF register shell (sif_mailbox_stub IOP-side
    // port). Low byte of the physical address is presented; writes go
    // out with the CPU's data/be; reads come back with 1-cycle latency
    // consistent with the rest of the stub ecosystem.
    // ------------------------------------------------------------------
    output logic          sif_rd_en,
    output logic [7:0]    sif_rd_addr,
    input  logic [31:0]   sif_rd_data,
    input  logic          sif_rd_valid,

    output logic          sif_wr_en,
    output logic [7:0]    sif_wr_addr,
    output logic [31:0]   sif_wr_data,

    // ------------------------------------------------------------------
    // Downstream to the IOP DMAC register shell (channel 9). 4-bit
    // offset; data path uses the CPU write data. Read returns with
    // one-cycle latency like the rest of the stub ecosystem.
    // ------------------------------------------------------------------
    output logic          iop_dmac_rd_en,
    output logic [3:0]    iop_dmac_rd_addr,
    input  logic [31:0]   iop_dmac_rd_data,
    input  logic          iop_dmac_rd_valid,

    output logic          iop_dmac_wr_en,
    output logic [3:0]    iop_dmac_wr_addr,
    output logic [31:0]   iop_dmac_wr_data,

    // ------------------------------------------------------------------
    // Downstream to the IOP INTC register shell (intc_stub reused).
    // 8-bit offset passed downstream; read returns with one-cycle
    // latency consistent with the rest of the stub ecosystem.
    // ------------------------------------------------------------------
    output logic          iop_intc_rd_en,
    output logic [7:0]    iop_intc_rd_addr,
    input  logic [31:0]   iop_intc_rd_data,
    input  logic          iop_intc_rd_valid,

    output logic          iop_intc_wr_en,
    output logic [7:0]    iop_intc_wr_addr,
    output logic [31:0]   iop_intc_wr_data,

    // ------------------------------------------------------------------
    // Downstream to bios_rom_stub (shared BIOS window).
    // 22-bit byte offset within the 4 MiB window. Writes are never
    // forwarded (BIOS is ROM); the map routes any bios-window write
    // attempt to the UNMAPPED trace event instead.
    // ------------------------------------------------------------------
    output logic          bios_rd_en,
    output logic [21:0]   bios_rd_addr,
    input  logic [31:0]   bios_rd_data,
    input  logic          bios_rd_valid,

    // ------------------------------------------------------------------
    // Ch234 — bridge-clock-domain pad bitmaps from ps2_hps_bridge
    // (INPUT_P1/P2 latches @ 0x040/0x044). Sync'd into the IOP clock
    // by the internal `sio2_input_stub` instance below. TBs that
    // don't exercise the pad path can tie both ports to `32'd0`.
    // ------------------------------------------------------------------
    input  logic [31:0]   input_p1,
    input  logic [31:0]   input_p2,

    // ------------------------------------------------------------------
    // 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 logic [63:0] REGION_IOP_RAM  = 64'd2;
    localparam logic [63:0] REGION_SIF_REGS = 64'd3;
    localparam logic [63:0] REGION_IOP_DMAC = 64'd4;
    localparam logic [63:0] REGION_IOP_INTC = 64'd5;
    localparam logic [63:0] REGION_IOP_BIOS = 64'd6;
    localparam logic [63:0] REGION_PAD_IO   = 64'd7;  // Ch234
    localparam logic [63:0] REGION_UNMAPPED = 64'hFF;

    localparam logic [28:0] DMAC_CH9_BASE = 29'h1F80_1520;
    localparam logic [28:0] IOP_INTC_BASE = 29'h1F80_1070;
    // Ch234 — retroDE-local pad I/O window (256 bytes), deliberately
    // OUTSIDE the real SIO2 range (0x1F80_8200..0x1F80_82FF) so a
    // faithful SIO2 emulation chapter can land later without collision.
    localparam logic [28:0] PAD_IO_BASE   = 29'h1F80_8500;

    // ------------------------------------------------------------------
    // Region decode (combinational, shared for read + write)
    // ------------------------------------------------------------------

    // CPU-side decode (virtual → physical via kseg strip)
    logic [28:0] rd_phys_addr;
    logic [28:0] wr_phys_addr;
    logic        rd_is_ram;
    logic        rd_is_sif;
    logic        rd_is_dmac;
    logic        rd_is_intc;
    logic        rd_is_bios;
    logic        rd_is_pad;     // Ch234
    logic        cpu_wr_is_ram;
    logic        cpu_wr_is_sif;
    logic        cpu_wr_is_dmac;
    logic        cpu_wr_is_intc;
    logic        cpu_wr_is_bios;
    logic        cpu_wr_is_pad; // Ch234
    logic [20:0] rd_ram_offset;
    logic [20:0] cpu_wr_ram_offset;

    assign rd_phys_addr      = iop_rd_addr[28:0];
    assign wr_phys_addr      = iop_wr_addr[28:0];
    assign rd_is_ram         = (rd_phys_addr[28:21] == 8'd0);
    assign rd_is_sif         = (rd_phys_addr[28:24] == 5'b11101);
    assign rd_is_dmac        = (rd_phys_addr[28:4]  == DMAC_CH9_BASE[28:4]);
    assign rd_is_intc        = (rd_phys_addr[28:4]  == IOP_INTC_BASE[28:4]);
    assign rd_is_bios        = (rd_phys_addr[28:22] == 7'b1111111);
    // Ch234 — pad I/O region is 256 bytes at PAD_IO_BASE, so the
    // decode is bits [28:8] (= 21 high bits of the 29-bit phys addr).
    assign rd_is_pad         = (rd_phys_addr[28:8]  == PAD_IO_BASE[28:8]);
    assign cpu_wr_is_ram     = (wr_phys_addr[28:21] == 8'd0);
    assign cpu_wr_is_sif     = (wr_phys_addr[28:24] == 5'b11101);
    assign cpu_wr_is_dmac    = (wr_phys_addr[28:4]  == DMAC_CH9_BASE[28:4]);
    assign cpu_wr_is_intc    = (wr_phys_addr[28:4]  == IOP_INTC_BASE[28:4]);
    assign cpu_wr_is_bios    = (wr_phys_addr[28:22] == 7'b1111111);
    assign cpu_wr_is_pad     = (wr_phys_addr[28:8]  == PAD_IO_BASE[28:8]);
    assign rd_ram_offset     = rd_phys_addr[20:0];
    assign cpu_wr_ram_offset = wr_phys_addr[20:0];

    // Bridge-side decode (physical, no strip). Bridge writes are routed
    // to IOP RAM only — no SIF destination from the bridge side yet.
    logic        bridge_wr_is_ram;
    logic [20:0] bridge_wr_ram_offset;

    assign bridge_wr_is_ram     = (bridge_wr_addr[28:21] == 8'd0);
    assign bridge_wr_ram_offset = bridge_wr_addr[20:0];

    // DMA-side read decode (physical, no strip). Scope covers RAM only.
    logic        dma_rd_is_ram;
    logic [20:0] dma_rd_ram_offset;

    assign dma_rd_is_ram     = (dma_rd_addr[28:21] == 8'd0);
    assign dma_rd_ram_offset = dma_rd_addr[20:0];

    // RAM routing. Ch261 — DMA wins the port on CPU+DMA collision; the
    // CPU's read address is latched into a one-entry pending slot and
    // serviced on the next RAM cycle that the DMA does not consume.
    // Pre-Ch261 the comment above this block was "CPU read wins over
    // DMA read on same-cycle collision" but the silent consequence was
    // the DMA path sampling `ram_rd_data` from the CPU's address —
    // silent DMA data corruption. The Ch261 SIF-landing TB found it.
    //
    // Single-entry slot is sufficient because every existing CPU
    // client of this map is request-then-wait-for-valid (no second
    // outstanding read in flight): exec stub, iop_core_stub, fetch
    // stub all stall in their own wait state until `iop_rd_valid`
    // asserts. A sim-only overflow assertion below catches any future
    // client that breaks that assumption.
    logic cpu_rd_hit;
    logic dma_rd_hit;
    logic cpu_dma_collision;
    assign cpu_rd_hit        = iop_rd_en && rd_is_ram;
    assign dma_rd_hit        = dma_rd_en && dma_rd_is_ram;
    assign cpu_dma_collision = cpu_rd_hit && dma_rd_hit;

    // One-entry deferred CPU-RAM-read slot.
    logic        cpu_pend_valid;
    logic [20:0] cpu_pend_addr;

    // Service priority (mutually exclusive):
    //   serve_dma     — DMA wins the bus any cycle it asks
    //   serve_cpu_def — deferred CPU read services on the next non-DMA cycle
    //   serve_cpu_now — live CPU read services when neither of the above fires
    logic serve_dma;
    logic serve_cpu_def;
    logic serve_cpu_now;
    assign serve_dma     = dma_rd_hit;
    assign serve_cpu_def = !dma_rd_hit && cpu_pend_valid;
    assign serve_cpu_now = !dma_rd_hit && !cpu_pend_valid && cpu_rd_hit;

    assign ram_rd_en   = serve_dma || serve_cpu_def || serve_cpu_now;
    assign ram_rd_addr = serve_dma     ? dma_rd_ram_offset
                       : serve_cpu_def ? cpu_pend_addr
                                       : rd_ram_offset;

    // Slot update: latch on collision, clear on service.
    always_ff @(posedge clk) begin
        if (!rst_n) begin
            cpu_pend_valid <= 1'b0;
            cpu_pend_addr  <= 21'd0;
        end else begin
            if (cpu_dma_collision && !cpu_pend_valid) begin
                cpu_pend_valid <= 1'b1;
                cpu_pend_addr  <= rd_ram_offset;
            end else if (serve_cpu_def) begin
                cpu_pend_valid <= 1'b0;
            end
        end
    end

`ifndef SYNTHESIS
    // Overflow detector: a second CPU+DMA collision while the slot is
    // already pending means we'd drop the new CPU read silently. The
    // current set of CPU clients can't trigger this (single outstanding
    // read each), but future producers should fail loudly here.
    always_ff @(posedge clk) begin
        if (rst_n && cpu_dma_collision && cpu_pend_valid) begin
            $error("[iop_memory_map_stub] Ch261 deferred-CPU-slot overflow: cpu_dma_collision while cpu_pend_valid (live addr=0x%05h pending addr=0x%05h)",
                   rd_ram_offset, cpu_pend_addr);
        end
    end
`endif

    // SIF register-shell routing. Low byte of the physical address is
    // presented downstream (mailbox uses 8-bit offsets).
    assign sif_rd_en   = iop_rd_en && rd_is_sif;
    assign sif_rd_addr = rd_phys_addr[7:0];
    assign sif_wr_en   = iop_wr_en && cpu_wr_is_sif;
    assign sif_wr_addr = wr_phys_addr[7:0];
    assign sif_wr_data = iop_wr_data;

    // IOP DMAC ch9 routing. Low 4 bits of the physical address select
    // among MADR / BCR / CHCR (and any other in-block offsets).
    assign iop_dmac_rd_en   = iop_rd_en && rd_is_dmac;
    assign iop_dmac_rd_addr = rd_phys_addr[3:0];
    assign iop_dmac_wr_en   = iop_wr_en && cpu_wr_is_dmac;
    assign iop_dmac_wr_addr = wr_phys_addr[3:0];
    assign iop_dmac_wr_data = iop_wr_data;

    // IOP INTC routing. Low byte of the physical address selects
    // INTC_STAT (0x00) or INTC_MASK (0x10).
    assign iop_intc_rd_en   = iop_rd_en && rd_is_intc;
    assign iop_intc_rd_addr = rd_phys_addr[7:0];
    assign iop_intc_wr_en   = iop_wr_en && cpu_wr_is_intc;
    assign iop_intc_wr_addr = wr_phys_addr[7:0];
    assign iop_intc_wr_data = iop_wr_data;

    // Ch234 — pad-I/O region wiring. The map owns a single internal
    // `sio2_input_stub` instance; the bridge's INPUT_P1/P2 latches
    // flow into it directly. `pad_rd_*` / `pad_wr_*` are the
    // map↔stub handshake (4-bit word offset within the 256-byte
    // region, captured from phys_addr[5:2]).
    wire        pad_rd_en;
    wire [3:0]  pad_rd_addr;
    wire [31:0] pad_rd_data;
    wire        pad_rd_valid;
    wire        pad_wr_en;
    wire [3:0]  pad_wr_addr;
    wire [31:0] pad_wr_data;

    assign pad_rd_en   = iop_rd_en && rd_is_pad;
    assign pad_rd_addr = rd_phys_addr[5:2];
    assign pad_wr_en   = iop_wr_en && cpu_wr_is_pad;
    assign pad_wr_addr = wr_phys_addr[5:2];
    assign pad_wr_data = iop_wr_data;

    sio2_input_stub u_sio2_input (
        .clk      (clk),
        .rst_n    (rst_n),
        .input_p1 (input_p1),
        .input_p2 (input_p2),
        .rd_en    (pad_rd_en),
        .rd_addr  (pad_rd_addr),
        .rd_data  (pad_rd_data),
        .rd_valid (pad_rd_valid),
        .wr_en    (pad_wr_en),
        .wr_addr  (pad_wr_addr),
        .wr_data  (pad_wr_data)
    );

    // BIOS ROM routing. 22-bit byte offset within the 4 MiB window.
    // No write path — BIOS is read-only.
    assign bios_rd_en   = iop_rd_en && rd_is_bios;
    assign bios_rd_addr = rd_phys_addr[21:0];

    // Write-path arbitration for the RAM side: CPU wins on same-cycle
    // collision. Neither TB nor current design exercises collision;
    // priority is defensive. SIF writes are a separate port and don't
    // contend with RAM writes.
    logic cpu_wr_hit;
    logic bridge_wr_hit;
    assign cpu_wr_hit    = iop_wr_en    && cpu_wr_is_ram;
    assign bridge_wr_hit = bridge_wr_en && bridge_wr_is_ram;

    assign ram_wr_en     = cpu_wr_hit || bridge_wr_hit;
    assign ram_wr_addr   = cpu_wr_hit ? cpu_wr_ram_offset : bridge_wr_ram_offset;
    assign ram_wr_data   = cpu_wr_hit ? iop_wr_data        : bridge_wr_data;
    assign ram_wr_be     = cpu_wr_hit ? iop_wr_be          : bridge_wr_be;
    assign ram_master_id = cpu_wr_hit ? master_id          : bridge_master_id;

    // ------------------------------------------------------------------
    // Read response pipeline
    //   cycle N  : iop_rd_en high, request routed downstream (or unmapped)
    //   cycle N+1: iop_rd_valid high, data from RAM or fault
    // ------------------------------------------------------------------

    logic rd_pending;
    logic rd_was_ram;
    logic rd_was_sif;
    logic rd_was_dmac;
    logic rd_was_intc;
    logic rd_was_bios;
    logic rd_was_pad;  // Ch234

    // Ch261 — rd_pending only pulses when the CPU read is ACTUALLY
    // serviced this cycle. Three cases:
    //   1. Non-RAM CPU read: always serviced (separate decode paths,
    //      no arbitration). Pulse rd_pending normally.
    //   2. RAM CPU read, no collision: serviced this cycle (serve_cpu_now
    //      fires above). Pulse rd_pending.
    //   3. RAM CPU read in collision: deferred (cpu_pend_valid latches).
    //      Do NOT pulse rd_pending — iop_rd_valid stays low until the
    //      deferred read finally fires (serve_cpu_def).
    //   4. Deferred RAM read finally serviced (serve_cpu_def): pulse
    //      rd_pending with rd_was_ram=1; the data arrives next cycle.
    always_ff @(posedge clk) begin
        if (!rst_n) begin
            rd_pending  <= 1'b0;
            rd_was_ram  <= 1'b0;
            rd_was_sif  <= 1'b0;
            rd_was_dmac <= 1'b0;
            rd_was_intc <= 1'b0;
            rd_was_bios <= 1'b0;
            rd_was_pad  <= 1'b0;
        end else if (serve_cpu_def) begin
            // Deferred RAM read serviced this cycle — data next cycle.
            rd_pending  <= 1'b1;
            rd_was_ram  <= 1'b1;
            rd_was_sif  <= 1'b0;
            rd_was_dmac <= 1'b0;
            rd_was_intc <= 1'b0;
            rd_was_bios <= 1'b0;
            rd_was_pad  <= 1'b0;
        end else if (iop_rd_en && !(rd_is_ram && cpu_dma_collision)) begin
            // Normal read path: live RAM read with no collision, OR
            // any non-RAM CPU read (decoded by rd_is_*, routed via
            // independent paths so no arbitration concern).
            rd_pending  <= 1'b1;
            rd_was_ram  <= rd_is_ram;
            rd_was_sif  <= rd_is_sif;
            rd_was_dmac <= rd_is_dmac;
            rd_was_intc <= rd_is_intc;
            rd_was_bios <= rd_is_bios;
            rd_was_pad  <= rd_is_pad;
        end else begin
            // Collision-deferred OR idle cycle. CPU waits for deferred
            // read to fire; iop_rd_valid stays low.
            rd_pending <= 1'b0;
        end
    end

    assign iop_rd_valid = rd_pending;
    assign iop_rd_data  = rd_was_ram  ? ram_rd_data
                        : rd_was_sif  ? sif_rd_data
                        : rd_was_dmac ? iop_dmac_rd_data
                        : rd_was_intc ? iop_intc_rd_data
                        : rd_was_bios ? bios_rd_data
                        : rd_was_pad  ? pad_rd_data
                                      : 32'hDEADBEEF;

    // ------------------------------------------------------------------
    // DMA read response pipeline (separate from CPU pipeline). Ch261 —
    // CPU+DMA collision is now handled cleanly by the deferred-CPU-slot
    // above: DMA wins the port immediately, CPU's read is latched and
    // serviced on the next non-DMA cycle. DMA always gets its own word
    // on its expected timing; no silent corruption.
    // ------------------------------------------------------------------

    logic dma_rd_pending;
    logic dma_rd_was_ram;

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            dma_rd_pending <= 1'b0;
            dma_rd_was_ram <= 1'b0;
        end else begin
            dma_rd_pending <= dma_rd_en;
            if (dma_rd_en) dma_rd_was_ram <= dma_rd_is_ram;
        end
    end

    assign dma_rd_valid = dma_rd_pending;
    assign dma_rd_data  = dma_rd_was_ram ? ram_rd_data : 32'hDEADBEEF;

    // ------------------------------------------------------------------
    // Trace emission — one event per cycle. Priority:
    //   CPU read  >  CPU write  >  DMA read  >  bridge write
    // Masters are expected to be sequenced in TBs; priority is defensive
    // for the rare collision case.
    // ------------------------------------------------------------------

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            ev_valid  <= 1'b0;
            ev_subsys <= SUBSYS_IOP;
            ev_event  <= EV_READ;
            ev_arg0   <= 64'd0;
            ev_arg1   <= 64'd0;
            ev_arg2   <= 64'd0;
            ev_arg3   <= 64'd0;
            ev_flags  <= 32'd0;
        end else if (iop_rd_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_IOP;
            if (rd_is_ram) begin
                ev_event <= EV_READ;
                ev_arg1  <= 64'd0;
                ev_arg3  <= REGION_IOP_RAM;
            end else if (rd_is_sif) begin
                ev_event <= EV_READ;
                ev_arg1  <= 64'd0;
                ev_arg3  <= REGION_SIF_REGS;
            end else if (rd_is_dmac) begin
                ev_event <= EV_READ;
                ev_arg1  <= 64'd0;
                ev_arg3  <= REGION_IOP_DMAC;
            end else if (rd_is_intc) begin
                ev_event <= EV_READ;
                ev_arg1  <= 64'd0;
                ev_arg3  <= REGION_IOP_INTC;
            end else if (rd_is_bios) begin
                ev_event <= EV_READ;
                ev_arg1  <= 64'd0;
                ev_arg3  <= REGION_IOP_BIOS;
            end else if (rd_is_pad) begin
                ev_event <= EV_READ;
                ev_arg1  <= 64'd0;
                ev_arg3  <= REGION_PAD_IO;
            end else begin
                ev_event <= EV_UNMAPPED;
                ev_arg1  <= 64'hDEADBEEF;
                ev_arg3  <= REGION_UNMAPPED;
            end
            ev_arg0  <= {32'd0, iop_rd_addr};
            ev_arg2  <= {56'd0, master_id};
            ev_flags <= 32'd0;
        end else if (iop_wr_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_IOP;
            if (cpu_wr_is_ram) begin
                ev_event <= EV_WRITE;
                ev_arg3  <= REGION_IOP_RAM;
            end else if (cpu_wr_is_sif) begin
                ev_event <= EV_WRITE;
                ev_arg3  <= REGION_SIF_REGS;
            end else if (cpu_wr_is_dmac) begin
                ev_event <= EV_WRITE;
                ev_arg3  <= REGION_IOP_DMAC;
            end else if (cpu_wr_is_intc) begin
                ev_event <= EV_WRITE;
                ev_arg3  <= REGION_IOP_INTC;
            end else if (cpu_wr_is_pad) begin
                ev_event <= EV_WRITE;
                ev_arg3  <= REGION_PAD_IO;
            end else begin
                ev_event <= EV_UNMAPPED;
                ev_arg3  <= REGION_UNMAPPED;
            end
            ev_arg0  <= {32'd0, iop_wr_addr};
            ev_arg1  <= {32'd0, iop_wr_data};
            ev_arg2  <= {56'd0, master_id};
            ev_flags <= 32'h0000_0001;
        end else if (dma_rd_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_IOP;
            ev_event  <= dma_rd_is_ram ? EV_READ : EV_UNMAPPED;
            ev_arg0   <= {32'd0, dma_rd_addr};
            ev_arg1   <= dma_rd_is_ram ? 64'd0 : 64'hDEADBEEF;
            ev_arg2   <= {56'd0, dma_master_id};
            ev_arg3   <= dma_rd_is_ram ? REGION_IOP_RAM : REGION_UNMAPPED;
            ev_flags  <= 32'd0;
        end else if (bridge_wr_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_IOP;
            ev_event  <= bridge_wr_is_ram ? EV_WRITE : EV_UNMAPPED;
            ev_arg0   <= {32'd0, bridge_wr_addr};
            ev_arg1   <= {32'd0, bridge_wr_data};
            ev_arg2   <= {56'd0, bridge_master_id};
            ev_arg3   <= bridge_wr_is_ram ? REGION_IOP_RAM : REGION_UNMAPPED;
            ev_flags  <= 32'h0000_0001;
        end else begin
            ev_valid <= 1'b0;
        end
    end

endmodule : iop_memory_map_stub
