// retroDE_ps2 — ee_memory_map_stub
//
// EE-visible address decode. Wave 2.7 revision added a DMAC read-master
// port and a RAM region that routes to ee_ram_stub. Wave 3 reverse-
// direction addition: a bridge write-master port for SIF egress landing
// (128-bit), mirroring the IOP map's bridge port but in the EE→RAM
// write direction. EE-core chapter 3 addition: a CPU-side EE DMAC
// channel-2 (GIF) register write port, decoded at phys[28:12] ==
// 17'h1_000A (i.e. 0x1000_A000-0x1000_AFFF, 4 KiB per-channel window).
// Only ch2 is decoded — other EE DMAC channels are intentionally not
// routed yet. Mirrors the IOP map's single-channel ch9 decode pattern.
// EE-core chapter 4 addition: a read counterpart at the same decode.
// The DMAC response joins the existing EE CPU read pipeline alongside
// BIOS and EE RAM, 1-cycle latency, so software can poll CHCR.start
// or DONE_COUNT instead of burning fixed delay slots. EE-core chapter
// 5 addition: an EE INTC register window at phys[28:12] == 17'h1_000F
// (0x1000_F000-0x1000_FFFF) with both read and write, so the EE core
// can program INTC_MASK, observe INTC_STAT, and ack via W1C — mirrors
// iop_memory_map_stub's IOP INTC decode but at the EE physical slot.
// Chapter 8 addition (first machine-state bring-up): an "EE bootstrap
// MMIO" window at phys[28:16] == 13'h1F80 (0x1F80_0000-0x1F80_FFFF,
// 64 KiB) covering MCH / SBUS / RDRAM-init registers the BIOS probes
// very early. Without decode, CPU reads there returned 0xDEADBEEF and
// that poison eventually wedged the core in a self-referential list
// walk. Routed to ee_bootstrap_mmio_stub with latched read/write
// semantics so read/modify/write sequences stay self-consistent.
//
// Contract refs:
//   docs/stub_module_plan.md               (Wave 1, item 3)
//   docs/wave25_memory_backed_dma_plan.md  (DMAC as memory client)
//   docs/wave26_multi_beat_dma_plan.md     (multi-beat transfers)
//   docs/contracts/memory.md
//
// Address semantics:
//   - EE fetch / CPU data port (32-bit data, CPU-visible virtual address):
//     kseg0/kseg1 aliasing is modelled via `phys = ee_addr[28:0]` for
//     both reads and writes. Regions decoded off the stripped phys:
//       BIOS window        phys[28:22] == 7'h7F    (reads only)
//       EE RAM window      phys[28:25] == 4'h0     (reads and writes)
//       EE DMAC ch2 (GIF)  phys[28:12] == 17'h1_000A (reads and writes)
//                          → offset phys[7:0] passes to dmac_reg_stub
//                          (CHCR=0x00, MADR=0x10, QWC=0x20, TADR=0x30,
//                           DONE_COUNT=0x40)
//       EE INTC            phys[28:12] == 17'h1_000F (reads and writes)
//                          → offset phys[7:0] passes to intc_stub
//                          (INTC_STAT=0x00, INTC_MASK=0x10)
//       EE bootstrap MMIO  phys[28:16] == 13'h1F80   (reads and writes)
//                          → offset phys[15:0] passes to
//                          ee_bootstrap_mmio_stub (latched reg file)
//     Anything else on this port is UNMAPPED.
//   - DMAC read port (128-bit data): treats its address as a physical bus
//     address directly. No kseg strip. RAM window decoded at
//     dmac_addr[31:25] == 7'b0 (i.e., 0x00000000 - 0x01FFFFFF, 32 MiB).
//   - Bridge write port (128-bit data): SIF egress bridge lands qwords
//     at physical addresses in the same RAM window (bridge_wr_addr
//     [31:25] == 7'b0). No kseg strip; writes bypass CPU side entirely.
//     Caller drives its own master_id for trace attribution (convention:
//     5 = SIF EE-side bridge).
//
// 32-bit ↔ 128-bit bridging for CPU RAM access:
//   EE RAM is 128-bit qword-addressable. A 32-bit CPU read selects one
//   of four lanes by ee_rd_addr[3:2] on the response side. A 32-bit CPU
//   write broadcasts ee_wr_data into the matching lane of the 128-bit
//   write bus, with ram_wr_be[15:0] masked so only the four bytes of
//   that lane are enabled (other lanes pass through as no-op). This is
//   the same technique the sif_dma_ee_ram_bridge uses to build a qword
//   from streamed 32-bit beats, but per-access instead of accumulated.
//
// Arbitration (for the shared 128-bit ram_rd / ram_wr ports):
//   Reads:  CPU wins over DMAC on same-cycle collision.
//   Writes: CPU wins over bridge on same-cycle collision.
//   TBs sequence masters in time; priority is defensive — on a real
//   collision the loser's response data is undefined, documented not
//   guarded. Mirrors the IOP map's CPU-vs-bridge write arbitration.
//
// Region ids (shared with ee_ram_stub and bios_rom_stub):
//   0    BIOS
//   1    EE_RAM
//   7    EE_DMAC (chapter 3 addition; avoids collision with IOP region ids
//         2..6 used by iop_memory_map_stub)
//   8    EE_INTC (chapter 5 addition)
//   9    EE_MISC_MMIO (chapter 8 addition; 0x1F80_xxxx bootstrap window)
//   10   EE_BIU (chapter 9 addition; 0xFFFE_0xxx window)
//   11   USEG_SHADOW (ch34 addition; first 4 MiB of useg, disjoint
//        backing from kseg0 RAM after the ch33 de-aliasing)
//   0xFF UNMAPPED
//
// Master ids:
//   0 = EE_IFETCH
//   1 = DMAC
//   5 = SIF EE-side bridge (reverse-direction landing)
//
// Trace semantics:
//   Map-layer events describe request routing (what was asked for, where it
//   was sent). Arg1 is intentionally 0 when the request is routed to a
//   backing store that will emit its own delivery event. `ee_ram_stub`
//   emits the delivery MEM READ with the returned data — the two events
//   together show the full ownership hop.
//
// Latency assumption (Wave 2.7):
//   This map assumes fixed one-cycle backing-store latency. `bios_rd_valid`
//   and `ram_rd_valid` are not consulted by the response pipeline — the
//   map asserts `*_rd_valid` one cycle after the request unconditionally.
//   All Wave 2.5/2.7 backing stubs honour that contract. If a later backing
//   store introduces wait states, the map must grow proper response
//   handshaking (tracking outstanding requests and gating `*_rd_valid` on
//   the downstream `_rd_valid`). Flagged for the next memory-side expansion.
//
// Trace payload schemas:
//   MAP.MEM READ      arg0=addr arg1=0            arg2=master_id arg3=region_id
//   MAP.MEM UNMAPPED  arg0=addr arg1=0xDEADBEEF   arg2=master_id arg3=0xFF
//   (Wave 1 EE-write UNMAPPED unchanged: flags bit 0 indicates write)

`timescale 1ns/1ps

module ee_memory_map_stub
    import trace_pkg::*;
#(
    // Ch64 — narrow visibility experiment. When enabled, CPU useg
    // stores whose phys falls in [CPU_USEG_TO_RAM_MIRROR_LO,
    // CPU_USEG_TO_RAM_MIRROR_HI) are mirrored into ee_ram via the
    // shared RAM write path, IN ADDITION to the existing write into
    // useg_shadow_mem. This restores a narrow slice of the kseg0<->
    // useg aliasing that Ch33's separate-backings simplification
    // removed. Kept off by default; enabled by TB parameter override
    // (CH64_USEG_MIRROR=1 in the bios-smoke build). Ch63 proved the
    // BIOS writes memcpy source code via useg stores, so the memcpy
    // at pc=0x30014 reading via kseg0 sees zero. A narrow mirror
    // restores visibility for the affected address range only.
    parameter bit          CPU_USEG_TO_RAM_MIRROR_EN = 1'b0,
    parameter logic [28:0] CPU_USEG_TO_RAM_MIRROR_LO = 29'h0004_4000,
    parameter logic [28:0] CPU_USEG_TO_RAM_MIRROR_HI = 29'h0004_4700,

    // Ch65 — symmetric narrow mirror for the destination direction.
    // When enabled, CPU kseg0/kseg1 stores that hit the RAM decode
    // AND whose phys lies in [CPU_KSEG0_TO_USEG_SHADOW_MIRROR_LO,
    // _HI) are ALSO committed to useg_shadow_mem via the existing
    // byte-enable write path, in addition to the normal ee_ram
    // write. This restores the second half of kseg0<->useg aliasing
    // for the memcpy destination region. Default off; enabled by TB
    // parameter override (CH65_KSEG_MIRROR=1 in the bios-smoke
    // build). Ch64 proved this BIOS pattern: build code via useg,
    // copy via kseg0, execute via useg.
    parameter bit          CPU_KSEG0_TO_USEG_SHADOW_MIRROR_EN = 1'b0,
    parameter logic [28:0] CPU_KSEG0_TO_USEG_SHADOW_MIRROR_LO = 29'h001A_4000,
    parameter logic [28:0] CPU_KSEG0_TO_USEG_SHADOW_MIRROR_HI = 29'h001A_5000,

    // Ch70 — coherent RAM-alias mode. Promotes the two narrow Ch64/
    // Ch65 mirror experiments into a single bidirectional aliasing
    // policy for the [LO, HI) phys window. When enabled, CPU writes
    // anywhere in the window mirror into the OTHER backing
    // (useg→ee_ram and kseg→useg_shadow), restoring the kseg0↔useg
    // aliasing real PS2 hardware has via the TLB while keeping
    // MMIO/DMAC/INTC/BIU/BIOS regions de-aliased per Ch33's intent
    // (those decode predicates short-circuit RAM eligibility, so
    // the alias never mirrors into them). Default off for
    // regression compatibility. Default window covers the first
    // 4 MiB — wide enough for the BIOS's RAM-resident copied code
    // (Ch64..Ch69 evidence) without touching kseg0 RAM beyond the
    // useg_shadow's 4 MiB span.
    parameter bit          CPU_RAM_ALIAS_EN = 1'b0,
    parameter logic [28:0] CPU_RAM_ALIAS_LO = 29'h0000_0000,
    parameter logic [28:0] CPU_RAM_ALIAS_HI = 29'h0040_0000,

    // Ch145 — useg-shadow backing size. Default 1M words = 4 MiB
    // matches the original ch34 contract (useg first 4 MiB has its
    // own backing, disjoint from kseg0 RAM). For hardware builds
    // where useg is unused (e.g., the Ch123 PSMCT32 raster demo
    // runs the bootlet entirely from BIOS at 0xBFC0_0000 and the
    // GIF payload from RAM at phys 0x100 — no useg traffic at all),
    // override this to a small value (e.g., 1024 words = 4 KiB) to
    // shrink the inferred BRAM footprint by ~1024×. No address
    // decode changes; the index `ee_rd_phys[21:2]` is naturally
    // bounded by the smaller array on smaller sizes (any access
    // beyond returns whatever the synth tool maps; for the Ch123
    // demo no such access ever happens). Keep the default to leave
    // all existing TBs byte-identical.
    parameter int unsigned USEG_SHADOW_WORDS_PARAM = 1024 * 1024,

    // Ch296 — useg-shadow backing enable. Default 1 = current behavior
    // EXACTLY (the `useg_shadow_mem` array and all its read/write logic
    // are instantiated; every existing BIOS/qbert/useg sim is byte-
    // identical). When 0, the array and its read/write logic are fully
    // removed from elaboration (wrapped in a generate-if), so the ~33k
    // flip-flops Quartus bit-blasts the array into on the board build
    // disappear. With the shadow disabled, useg READS return the
    // unmapped value (0xDEADBEEF via the existing read-mux fall-through,
    // because `ee_rd_is_useg_shadow` is forced to 0) and useg WRITES are
    // dropped (no-op). Every other region (BIOS, EE-RAM, GS-priv MMIO,
    // DMAC, INTC, BIU, bootstrap MMIO) is unchanged. The board PSMCT32
    // raster demo never issues useg traffic (bootlet runs from BIOS at
    // 0xBFC0_0000, GIF payload from EE-RAM via DMAC), so disabling the
    // shadow on the board path is free. ONLY the board/synth top sets 0;
    // sim TBs and the BIOS/qbert path keep the default 1.
    parameter bit          USEG_SHADOW_ENABLE = 1'b1
) (
    input  logic          clk,
    input  logic          rst_n,

    // ------------------------------------------------------------------
    // EE fetch port (32-bit data, virtual address with kseg aliasing)
    // ------------------------------------------------------------------
    input  logic          ee_rd_en,
    input  logic [31:0]   ee_rd_addr,
    output logic [31:0]   ee_rd_data,
    output logic          ee_rd_valid,

    input  logic          ee_wr_en,
    input  logic [31:0]   ee_wr_addr,
    input  logic [31:0]   ee_wr_data,
    input  logic [3:0]    ee_wr_be,

    // ------------------------------------------------------------------
    // DMAC read port (128-bit data, physical address)
    // ------------------------------------------------------------------
    input  logic          dmac_rd_en,
    input  logic [31:0]   dmac_rd_addr,
    output logic [127:0]  dmac_rd_data,
    output logic          dmac_rd_valid,

    // ------------------------------------------------------------------
    // Bridge write port (Wave 3 reverse-direction landing). 128-bit data,
    // physical address. Caller provides its own master_id. Only EE RAM
    // region is mapped from this port in current scope.
    // ------------------------------------------------------------------
    input  logic          bridge_wr_en,
    input  logic [31:0]   bridge_wr_addr,
    input  logic [127:0]  bridge_wr_data,
    input  logic [15:0]   bridge_wr_be,
    input  logic [7:0]    bridge_master_id,

    // ------------------------------------------------------------------
    // Downstream to bios_rom_stub (EE fetch path)
    // ------------------------------------------------------------------
    output logic          bios_rd_en,
    output logic [21:0]   bios_rd_addr,
    input  logic [31:0]   bios_rd_data,
    input  logic          bios_rd_valid,

    // ------------------------------------------------------------------
    // Downstream to ee_ram_stub (DMAC read path).
    // Address is presented as a full-width RAM-local offset; consumers may
    // truncate to match their own backing-store address width.
    // ------------------------------------------------------------------
    output logic          ram_rd_en,
    output logic [24:0]   ram_rd_addr,
    input  logic [127:0]  ram_rd_data,
    input  logic          ram_rd_valid,

    // ------------------------------------------------------------------
    // Downstream RAM write port (Wave 3). Driven by bridge_wr_*; no
    // other master writes to EE RAM through this map in current scope.
    // ------------------------------------------------------------------
    output logic          ram_wr_en,
    output logic [24:0]   ram_wr_addr,
    output logic [127:0]  ram_wr_data,
    output logic [15:0]   ram_wr_be,
    output logic [7:0]    ram_master_id,

    // ------------------------------------------------------------------
    // Downstream EE DMAC ch2 (GIF) register write port (chapter 3).
    // Fires when the CPU writes within 0x1000_A000-0x1000_AFFF. 8-bit
    // offset matches dmac_reg_stub's register surface (CHCR/MADR/QWC/
    // TADR at 0x00/0x10/0x20/0x30).
    // ------------------------------------------------------------------
    output logic          ee_dmac_ch2_wr_en,
    output logic [7:0]    ee_dmac_ch2_wr_addr,
    output logic [31:0]   ee_dmac_ch2_wr_data,

    // ------------------------------------------------------------------
    // Downstream EE DMAC ch2 (GIF) register read port (chapter 4).
    // 1-cycle latency mirrors BIOS and EE RAM reads. reg_rd_valid is
    // not consulted by the response pipeline — the map asserts
    // ee_rd_valid one cycle after the CPU request unconditionally,
    // same convention as the other backing stubs.
    // ------------------------------------------------------------------
    output logic          ee_dmac_ch2_rd_en,
    output logic [7:0]    ee_dmac_ch2_rd_addr,
    input  logic [31:0]   ee_dmac_ch2_rd_data,
    input  logic          ee_dmac_ch2_rd_valid,

    // ------------------------------------------------------------------
    // Downstream EE INTC register ports (chapter 5). Same shape as the
    // DMAC register ports: 8-bit shared offset surface, write-then-
    // read response pipeline joins the EE CPU mux. Offsets INTC_STAT
    // at 0x00 and INTC_MASK at 0x10 match intc_stub's default.
    // ------------------------------------------------------------------
    output logic          ee_intc_wr_en,
    output logic [7:0]    ee_intc_wr_addr,
    output logic [31:0]   ee_intc_wr_data,

    output logic          ee_intc_rd_en,
    output logic [7:0]    ee_intc_rd_addr,
    input  logic [31:0]   ee_intc_rd_data,
    input  logic          ee_intc_rd_valid,

    // ------------------------------------------------------------------
    // Downstream EE bootstrap-MMIO ports (chapter 8). 16-bit window
    // offset (0x1F80_0000-0x1F80_FFFF, 64 KiB), 1-cycle read latency
    // matching the rest of the stub ecosystem. Routed to
    // ee_bootstrap_mmio_stub which latches writes and returns the
    // last-written value on read (so BIOS read/modify/write sequences
    // stay self-consistent instead of laundering 0xDEADBEEF into
    // data structures).
    // ------------------------------------------------------------------
    output logic          ee_misc_mmio_wr_en,
    output logic [15:0]   ee_misc_mmio_wr_addr,
    output logic [31:0]   ee_misc_mmio_wr_data,
    output logic [3:0]    ee_misc_mmio_wr_be,

    output logic          ee_misc_mmio_rd_en,
    output logic [15:0]   ee_misc_mmio_rd_addr,
    input  logic [31:0]   ee_misc_mmio_rd_data,
    input  logic          ee_misc_mmio_rd_valid,

    // ------------------------------------------------------------------
    // Ch112 — GS privileged-MMIO window (0x1200_0000-0x1200_FFFF,
    // 64 KiB). Routed to ee_gs_priv_bridge_stub, which accumulates
    // 32-bit half-writes per 8-byte slot and fires a 64-bit
    // gs_stub.reg_wr_* pulse on each EE half-write. The map
    // decode is symmetric with the misc-MMIO path: top 13 bits of
    // the 29-bit physical address tag the window. Read side is
    // not modelled at this scope (the Ch111 bridge is write-only,
    // matching the limited read coverage of the GS priv block).
    // TBs that don't model GS-priv MMIO can leave these ports
    // unconnected (named-port instantiation tolerates omitted
    // outputs).
    // ------------------------------------------------------------------
    output logic          ee_gs_priv_wr_en,
    output logic [15:0]   ee_gs_priv_wr_addr,
    output logic [31:0]   ee_gs_priv_wr_data,
    output logic [3:0]    ee_gs_priv_wr_be,

    // ------------------------------------------------------------------
    // EE BIU / cache-control window at 0xFFFE_0000-0xFFFE_0FFF (4 KiB).
    // Narrow, dedicated stub — kept architecturally separate from the
    // 0x1F80_xxxx bootstrap MMIO window so each decode surface has its
    // own region tag and its own evidence trail.
    // ------------------------------------------------------------------
    output logic          ee_biu_wr_en,
    output logic [11:0]   ee_biu_wr_addr,
    output logic [31:0]   ee_biu_wr_data,
    output logic [3:0]    ee_biu_wr_be,

    output logic          ee_biu_rd_en,
    output logic [11:0]   ee_biu_rd_addr,
    input  logic [31:0]   ee_biu_rd_data,
    input  logic          ee_biu_rd_valid,

    // ------------------------------------------------------------------
    // Trace (shared; priority EE > DMAC on collision)
    // ------------------------------------------------------------------
    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_BIOS         = 64'd0;
    localparam logic [63:0] REGION_EE_RAM       = 64'd1;
    localparam logic [63:0] REGION_EE_DMAC      = 64'd7;
    localparam logic [63:0] REGION_EE_INTC      = 64'd8;
    localparam logic [63:0] REGION_EE_MISC_MMIO = 64'd9;
    localparam logic [63:0] REGION_EE_BIU       = 64'd10;
    // Ch33: dedicated tag for the useg-shadow backing (first 4 MiB of
    // useg, disjoint from kseg0 RAM). Kept as a separate region so
    // trace/post-run analysis can distinguish shadow hits from real
    // ee_ram hits.
    localparam logic [63:0] REGION_USEG_SHADOW  = 64'd11;
    // Ch112: GS-privileged-MMIO window (0x1200_0000-0x1200_FFFF).
    // Routed to ee_gs_priv_bridge_stub.
    localparam logic [63:0] REGION_EE_GS_PRIV   = 64'd12;
    // Ch287: EE DMAC global control/status registers at 0x1000_E000.
    // Hosts D_CTRL/D_STAT/D_PCR/D_SQWC/D_RBSR/D_RBOR. Instantiated
    // INTERNALLY (ee_dmac_ctrl_stub) so existing TBs don't need to
    // route new ports.
    localparam logic [63:0] REGION_EE_DMAC_CTRL = 64'd13;
    // Ch288: EE DMAC per-channel register surfaces for channels NOT
    // covered by a dedicated transfer-FSM stub. ch0 VIF0, ch1 VIF1,
    // ch3 IPU_FROM, ch4 IPU_TO, ch5 SIF0 — each a 4 KiB window at
    // 0x1000_8000 + N*0x1000. Latched CHCR/MADR/QWC/TADR only; no
    // FSM. ch2 GIF stays on its dedicated external port.
    localparam logic [63:0] REGION_EE_DMAC_PASSIVE = 64'd14;
    localparam logic [63:0] REGION_UNMAPPED     = 64'hFF;
    localparam logic [63:0] MASTER_EE_IFETCH = 64'd0;   // EE CPU (fetch + data)
    localparam logic [63:0] MASTER_DMAC      = 64'd1;

    // EE DMAC channel-2 (GIF) MMIO base — only channel decoded in this
    // chapter. phys[28:12] == EE_DMAC_CH2_BASE[28:12] identifies the
    // 4 KiB per-channel register window at 0x1000_A000.
    localparam logic [28:0] EE_DMAC_CH2_BASE = 29'h1000_A000;
    // EE INTC MMIO base at 0x1000_F000.
    localparam logic [28:0] EE_INTC_BASE     = 29'h1000_F000;
    // Ch287 — EE DMAC global control/status base at 0x1000_E000
    // (D_CTRL/D_STAT/D_PCR/D_SQWC/D_RBSR/D_RBOR). Window is 4 KiB to
    // match the discrimination pattern of the existing DMAC ch2 / INTC
    // bases; the live registers occupy only the first 256 bytes
    // (8-bit reg_offset), the remainder is silent within the window
    // (writes dropped + traced, reads return 0).
    localparam logic [28:0] EE_DMAC_CTRL_BASE = 29'h1000_E000;

    // EE bootstrap-MMIO window covers the full 0x1F80_0000-0x1F80_FFFF
    // range (64 KiB). Top 13 bits of the 29-bit physical address are
    // the discriminator — see ee_bootstrap_mmio_stub.sv for the
    // per-register latching semantics inside.
    localparam logic [12:0] EE_MISC_MMIO_TAG = 13'h1F80;

    // Ch112: GS privileged-MMIO window (PMODE/SMODE/DISPFB/DISPLAY/
    // BGCOLOR/HWREG/etc.) at 0x1200_0000-0x1200_FFFF (64 KiB).
    // Routed to ee_gs_priv_bridge_stub.
    localparam logic [12:0] EE_GS_PRIV_TAG   = 13'h1200;

    // EE BIU/cache-control window at 0xFFFE_0000-0xFFFE_0FFF (4 KiB,
    // phys 0x1FFE_0000-0x1FFE_0FFF after kseg1-strip). Narrow by design
    // — chapter 9 covers a single architecturally-distinct surface the
    // real BIOS touches for BIU/cache config. See ee_biu_mmio_stub.sv.
    localparam logic [16:0] EE_BIU_MMIO_TAG = 17'h1FFE0;

    // ------------------------------------------------------------------
    // Decode: EE CPU side (kseg-stripped), bridge side (physical),
    // DMAC side (physical).
    // ------------------------------------------------------------------

    logic [28:0] ee_rd_phys;
    logic [28:0] ee_wr_phys;
    logic        ee_rd_is_bios;
    logic        ee_rd_is_ram;
    logic        ee_rd_is_dmac_ch2;
    logic        ee_rd_is_dmac_ctrl;     // Ch287
    logic        ee_rd_is_dmac_passive;  // Ch288 (ch0/1/3/4/5)
    logic        ee_rd_is_intc;
    logic        ee_rd_is_misc_mmio;
    logic        ee_rd_is_biu;
    logic        ee_wr_is_ram;
    logic        ee_wr_is_dmac_ch2;
    logic        ee_wr_is_dmac_ctrl;     // Ch287
    logic        ee_wr_is_dmac_passive;  // Ch288
    logic        ee_wr_is_intc;
    logic        ee_wr_is_misc_mmio;
    logic        ee_wr_is_biu;
    logic        ee_wr_is_gs_priv;
    logic [21:0] ee_bios_offset;
    logic [24:0] ee_rd_ram_offset;
    logic [24:0] ee_wr_ram_offset;

    // ------------------------------------------------------------------
    // Chapter 33 — useg de-aliasing experiment. Prior behavior: phys =
    // addr[28:0] strip treated useg (0x0000_0000-0x7FFF_FFFF) and kseg0
    // (0x8000_0000-0x9FFF_FFFF) as the same physical word. Real MIPS
    // has useg TLB-mapped, so that alias is only "correct" when the
    // BIOS has programmed a TLB entry. Ch32 proved BIOS issues ZERO
    // TLBWI/TLBWR/TLBP/TLBR pre-ENTRY14 — so real HW's useg is
    // unmapped in that window, and our alias is too permissive.
    //
    // Experimental fix: route useg accesses whose phys lies in
    // [0, 4 MiB) to a dedicated in-map shadow backing, DISJOINT from
    // the kseg0/kseg1 RAM. Keeps useg reads/writes self-consistent
    // (unlike a zero/sink model) so any genuine useg-internal work
    // the BIOS does still behaves, but de-aliases the ch12 memcpy
    // from the ch31 walker's kseg0 read.
    //
    // 4 MiB shadow = 1M × 32-bit words. Big but cheap in sim.
    //   USEG = addr[31]==0 (0x0000_0000..0x7FFF_FFFF)
    //   kseg0/kseg1/kseg2/3 keep their existing phys[28:0]-strip
    //   behavior.
    // ------------------------------------------------------------------
    logic ee_rd_is_useg;
    logic ee_wr_is_useg;
    logic ee_rd_is_useg_shadow;
    logic ee_wr_is_useg_shadow;

    assign ee_rd_phys          = ee_rd_addr[28:0];
    assign ee_wr_phys          = ee_wr_addr[28:0];
    assign ee_rd_is_useg       = (ee_rd_addr[31] == 1'b0);
    assign ee_wr_is_useg       = (ee_wr_addr[31] == 1'b0);
    // Shadow covers first 4 MiB: phys[28:22] == 0 → phys in [0, 0x3FFFFF].
    // That's enough for the low-RAM init region the BIOS touches via
    // useg (ch12 memcpy at phys 0x1E28, ch23 table ops at phys
    // 0x200..0x3FF, etc.). Accesses beyond 4 MiB to useg fall through
    // to existing decode (will mostly land UNMAPPED).
    // When USEG_SHADOW_ENABLE=0 the shadow decode is forced low so useg
    // reads fall through to the 0xDEADBEEF unmapped mux arm and useg
    // writes are dropped (the write logic below is also generate-removed).
    // ee_rd_is_useg / ee_wr_is_useg are NOT gated — they still exclude
    // useg from the RAM decode (`!ee_*_is_useg`), preserving the Ch33
    // de-aliasing intent regardless of this knob.
    assign ee_rd_is_useg_shadow = USEG_SHADOW_ENABLE && ee_rd_is_useg && (ee_rd_phys[28:22] == 7'd0);
    // Forward-decl: cpu_rd_hit_ram is declared further down (line ~593).
    // Used inside the ram-rd arbitration block and in the DMAC
    // response pipeline (Ch115 — see comment block below).
    assign ee_wr_is_useg_shadow = USEG_SHADOW_ENABLE && ee_wr_is_useg && (ee_wr_phys[28:22] == 7'd0);
    assign ee_rd_is_bios       = (ee_rd_phys[28:22] == 7'h7F);
    // Exclude useg from RAM decode only — the TLB-aliasing wedge is
    // specifically about useg-store-to-RAM conflating with
    // kseg0-load-from-RAM. Useg aliases of MMIO/DMAC/INTC/BIU windows
    // remain routed as before; the existing test benches exercise
    // those via useg addressing (e.g. synthetic DMAC program at
    // 0x1000A010 with addr[31]=0) and must keep working.
    assign ee_rd_is_ram        = !ee_rd_is_useg && (ee_rd_phys[28:25] == 4'h0);
    assign ee_rd_is_dmac_ch2   = (ee_rd_phys[28:12] == EE_DMAC_CH2_BASE[28:12]);
    assign ee_rd_is_dmac_ctrl  = (ee_rd_phys[28:12] == EE_DMAC_CTRL_BASE[28:12]);  // Ch287
    // Ch288 — passive per-channel surface: ch0/1/3/4/5. Match the
    // 0x1000_8xxx..0x1000_Dxxx span, excluding 0x1000_Axxx (ch2 GIF
    // has its own dedicated stub via ee_dmac_ch2_* ports).
    assign ee_rd_is_dmac_passive =
        (ee_rd_phys[28:16] == 13'h1000) &&
        ((ee_rd_phys[15:12] == 4'h8) || (ee_rd_phys[15:12] == 4'h9) ||
         (ee_rd_phys[15:12] == 4'hB) || (ee_rd_phys[15:12] == 4'hC) ||
         (ee_rd_phys[15:12] == 4'hD));
    assign ee_rd_is_intc       = (ee_rd_phys[28:12] == EE_INTC_BASE[28:12]);
    assign ee_rd_is_misc_mmio  = (ee_rd_phys[28:16] == EE_MISC_MMIO_TAG);
    assign ee_rd_is_biu        = (ee_rd_phys[28:12] == EE_BIU_MMIO_TAG);
    assign ee_wr_is_ram        = !ee_wr_is_useg && (ee_wr_phys[28:25] == 4'h0);
    assign ee_wr_is_dmac_ch2   = (ee_wr_phys[28:12] == EE_DMAC_CH2_BASE[28:12]);
    assign ee_wr_is_dmac_ctrl  = (ee_wr_phys[28:12] == EE_DMAC_CTRL_BASE[28:12]);  // Ch287
    // Ch288 — passive per-channel write decode (mirrors read side).
    assign ee_wr_is_dmac_passive =
        (ee_wr_phys[28:16] == 13'h1000) &&
        ((ee_wr_phys[15:12] == 4'h8) || (ee_wr_phys[15:12] == 4'h9) ||
         (ee_wr_phys[15:12] == 4'hB) || (ee_wr_phys[15:12] == 4'hC) ||
         (ee_wr_phys[15:12] == 4'hD));
    assign ee_wr_is_intc       = (ee_wr_phys[28:12] == EE_INTC_BASE[28:12]);
    assign ee_wr_is_misc_mmio  = (ee_wr_phys[28:16] == EE_MISC_MMIO_TAG);
    assign ee_wr_is_biu        = (ee_wr_phys[28:12] == EE_BIU_MMIO_TAG);
    assign ee_wr_is_gs_priv    = (ee_wr_phys[28:16] == EE_GS_PRIV_TAG);
    assign ee_bios_offset      = ee_rd_phys[21:0];
    assign ee_rd_ram_offset    = ee_rd_phys[24:0];
    assign ee_wr_ram_offset    = ee_wr_phys[24:0];

    // ------------------------------------------------------------------
    // Bridge RAM decode (general — not useg-shadow specific, referenced
    // by the RAM-write arbitration and trace below). Kept at module
    // scope so it survives when the useg-shadow backing is generate-
    // removed (USEG_SHADOW_ENABLE=0).
    // ------------------------------------------------------------------
    logic        bridge_is_ram;
    logic [24:0] bridge_ram_offset;
    assign bridge_is_ram     = (bridge_wr_addr[31:25] == 7'd0);
    assign bridge_ram_offset = bridge_wr_addr[24:0];

    // ------------------------------------------------------------------
    // Useg-shadow read response. Declared at module scope because the EE
    // CPU read mux references `useg_shadow_rd_data` unconditionally. The
    // procedural driver lives inside the generate-if below — when the
    // shadow is disabled the else-arm ties this to 0 and the mux never
    // selects it anyway (ee_rd_was_useg_shadow stays 0).
    // ------------------------------------------------------------------
    logic [31:0] useg_shadow_rd_data;

    // ------------------------------------------------------------------
    // Useg-shadow backing: 4 MiB = 1M words. Separate from kseg0/kseg1
    // RAM. 1-cycle read latency, per-byte write-enable.
    //
    // Ch296 — the entire backing array plus its CPU-read, CPU-write, and
    // bridge-write logic is wrapped in a generate-if on USEG_SHADOW_ENABLE.
    // When disabled (board/synth build), the ~33k flip-flops Quartus bit-
    // blasts `useg_shadow_mem` into are fully removed: useg reads return
    // the unmapped value via the read mux (ee_rd_is_useg_shadow is forced
    // 0), useg writes are dropped, and `useg_shadow_rd_data` is tied 0.
    // ------------------------------------------------------------------
    generate
    if (USEG_SHADOW_ENABLE) begin : g_useg_shadow
        localparam int USEG_SHADOW_WORDS = USEG_SHADOW_WORDS_PARAM;  // Ch145 — parameter-driven (default 1M = 4 MiB)
        logic [31:0] useg_shadow_mem [0:USEG_SHADOW_WORDS-1];
        initial begin
            for (int i = 0; i < USEG_SHADOW_WORDS; i++) useg_shadow_mem[i] = 32'd0;
        end

        logic [19:0] useg_rd_idx;
        logic [19:0] useg_wr_idx;
        assign useg_rd_idx = ee_rd_phys[21:2];
        assign useg_wr_idx = ee_wr_phys[21:2];

        logic        useg_shadow_rd_valid;
        logic [31:0] useg_shadow_rd_data_q;
        always_ff @(posedge clk) begin
            if (!rst_n) begin
                useg_shadow_rd_valid  <= 1'b0;
                useg_shadow_rd_data_q <= 32'd0;
            end else begin
                useg_shadow_rd_valid <= ee_rd_en && ee_rd_is_useg_shadow;
                if (ee_rd_en && ee_rd_is_useg_shadow)
                    useg_shadow_rd_data_q <= useg_shadow_mem[useg_rd_idx];
            end
        end
        assign useg_shadow_rd_data = useg_shadow_rd_data_q;

        // Ch65 — CPU kseg0 store mirror hit. Parallels the Ch64
        // useg->ram mirror in the opposite direction. Disjoint from
        // ee_wr_is_useg_shadow by construction (kseg0 writes set
        // ee_wr_is_ram, not ee_wr_is_useg_shadow). useg_wr_idx already
        // extracts ee_wr_phys[21:2], which is the right word index for
        // both useg-stripped and kseg0-stripped phys in the first 4 MiB.
        // Ch70 unified alias also covers the kseg→useg_shadow direction
        // for any CPU RAM write whose phys is in [CPU_RAM_ALIAS_LO,
        // CPU_RAM_ALIAS_HI). Subsumes the Ch65 narrow knob when enabled.
        logic cpu_wr_kseg_alias_hit;
        logic cpu_wr_kseg_shadow_mirror_hit;
        assign cpu_wr_kseg_alias_hit =
            CPU_RAM_ALIAS_EN && ee_wr_en && ee_wr_is_ram &&
            (ee_wr_phys >= CPU_RAM_ALIAS_LO) &&
            (ee_wr_phys <  CPU_RAM_ALIAS_HI);
        assign cpu_wr_kseg_shadow_mirror_hit =
            cpu_wr_kseg_alias_hit ||
            (CPU_KSEG0_TO_USEG_SHADOW_MIRROR_EN && ee_wr_en && ee_wr_is_ram &&
             (ee_wr_phys >= CPU_KSEG0_TO_USEG_SHADOW_MIRROR_LO) &&
             (ee_wr_phys <  CPU_KSEG0_TO_USEG_SHADOW_MIRROR_HI));

        always_ff @(posedge clk) begin
            if (rst_n && ee_wr_en &&
                (ee_wr_is_useg_shadow || cpu_wr_kseg_shadow_mirror_hit)) begin
                if (ee_wr_be[0]) useg_shadow_mem[useg_wr_idx][ 7: 0] <= ee_wr_data[ 7: 0];
                if (ee_wr_be[1]) useg_shadow_mem[useg_wr_idx][15: 8] <= ee_wr_data[15: 8];
                if (ee_wr_be[2]) useg_shadow_mem[useg_wr_idx][23:16] <= ee_wr_data[23:16];
                if (ee_wr_be[3]) useg_shadow_mem[useg_wr_idx][31:24] <= ee_wr_data[31:24];
            end
        end

        // Ch55: producer-side install seam. Bridge writes whose byte address
        // falls in the narrow coordinated-install window [0x80..0x1FF] also
        // shadow into useg_shadow_mem, so the EE CPU (which reads useg
        // exception-entry words via the shadow backing) observes bytes a
        // real external producer (SIF/IOP or BootROM/CDVD handoff) deposits
        // into physical RAM. ee_ram is still written unchanged through
        // bridge_wr_hit_ram below — this is purely additive mirroring.
        //
        // Narrowed per Ch55 Codex audit finding 1: earlier draft shadowed
        // all 4 MiB of low RAM, which would let unrelated future bridge
        // writes become visible through useg reads and muddy diagnostics.
        // The shared-surface install the BIOS needs lives only in
        // [0x80..0x1FF]; anything else goes through the regular
        // bridge→ee_ram path, disjoint from useg_shadow, matching the
        // Ch33 de-aliasing intent. If a future chapter needs a wider
        // mirror, raise BRIDGE_USEG_SHADOW_END here explicitly.
        localparam logic [31:0] BRIDGE_USEG_SHADOW_BASE = 32'h0000_0080;
        localparam logic [31:0] BRIDGE_USEG_SHADOW_END  = 32'h0000_0200;

        logic        bridge_wr_in_install_window;
        logic        bridge_wr_is_useg_shadow;
        logic [19:0] bridge_useg_wr_idx0;
        logic [19:0] bridge_useg_wr_idx1;
        logic [19:0] bridge_useg_wr_idx2;
        logic [19:0] bridge_useg_wr_idx3;
        assign bridge_wr_in_install_window =
            (bridge_wr_addr >= BRIDGE_USEG_SHADOW_BASE) &&
            (bridge_wr_addr <  BRIDGE_USEG_SHADOW_END);
        assign bridge_wr_is_useg_shadow =
            bridge_wr_en && bridge_is_ram && bridge_wr_in_install_window;
        assign bridge_useg_wr_idx0 = bridge_wr_addr[21:2];
        assign bridge_useg_wr_idx1 = bridge_wr_addr[21:2] + 20'd1;
        assign bridge_useg_wr_idx2 = bridge_wr_addr[21:2] + 20'd2;
        assign bridge_useg_wr_idx3 = bridge_wr_addr[21:2] + 20'd3;

        always_ff @(posedge clk) begin
            if (rst_n && bridge_wr_is_useg_shadow) begin
                if (bridge_wr_be[ 0]) useg_shadow_mem[bridge_useg_wr_idx0][ 7: 0] <= bridge_wr_data[  7:  0];
                if (bridge_wr_be[ 1]) useg_shadow_mem[bridge_useg_wr_idx0][15: 8] <= bridge_wr_data[ 15:  8];
                if (bridge_wr_be[ 2]) useg_shadow_mem[bridge_useg_wr_idx0][23:16] <= bridge_wr_data[ 23: 16];
                if (bridge_wr_be[ 3]) useg_shadow_mem[bridge_useg_wr_idx0][31:24] <= bridge_wr_data[ 31: 24];
                if (bridge_wr_be[ 4]) useg_shadow_mem[bridge_useg_wr_idx1][ 7: 0] <= bridge_wr_data[ 39: 32];
                if (bridge_wr_be[ 5]) useg_shadow_mem[bridge_useg_wr_idx1][15: 8] <= bridge_wr_data[ 47: 40];
                if (bridge_wr_be[ 6]) useg_shadow_mem[bridge_useg_wr_idx1][23:16] <= bridge_wr_data[ 55: 48];
                if (bridge_wr_be[ 7]) useg_shadow_mem[bridge_useg_wr_idx1][31:24] <= bridge_wr_data[ 63: 56];
                if (bridge_wr_be[ 8]) useg_shadow_mem[bridge_useg_wr_idx2][ 7: 0] <= bridge_wr_data[ 71: 64];
                if (bridge_wr_be[ 9]) useg_shadow_mem[bridge_useg_wr_idx2][15: 8] <= bridge_wr_data[ 79: 72];
                if (bridge_wr_be[10]) useg_shadow_mem[bridge_useg_wr_idx2][23:16] <= bridge_wr_data[ 87: 80];
                if (bridge_wr_be[11]) useg_shadow_mem[bridge_useg_wr_idx2][31:24] <= bridge_wr_data[ 95: 88];
                if (bridge_wr_be[12]) useg_shadow_mem[bridge_useg_wr_idx3][ 7: 0] <= bridge_wr_data[103: 96];
                if (bridge_wr_be[13]) useg_shadow_mem[bridge_useg_wr_idx3][15: 8] <= bridge_wr_data[111:104];
                if (bridge_wr_be[14]) useg_shadow_mem[bridge_useg_wr_idx3][23:16] <= bridge_wr_data[119:112];
                if (bridge_wr_be[15]) useg_shadow_mem[bridge_useg_wr_idx3][31:24] <= bridge_wr_data[127:120];
            end
        end
    end else begin : g_no_useg_shadow
        // USEG_SHADOW_ENABLE=0 — no backing array, no read/write logic.
        // useg reads fall through to the 0xDEADBEEF unmapped mux arm
        // (ee_rd_is_useg_shadow is forced 0); useg writes are dropped.
        assign useg_shadow_rd_data = 32'd0;
    end
    endgenerate

    logic        dmac_is_ram;
    logic [24:0] dmac_ram_offset;
    assign dmac_is_ram     = (dmac_rd_addr[31:25] == 7'd0);
    assign dmac_ram_offset = dmac_rd_addr[24:0];

    // ------------------------------------------------------------------
    // BIOS routing (CPU fetch only — BIOS is read-only from every port)
    // ------------------------------------------------------------------

    assign bios_rd_en   = ee_rd_en && ee_rd_is_bios;
    assign bios_rd_addr = ee_bios_offset;

    // ------------------------------------------------------------------
    // Shared 128-bit RAM read port. CPU wins over DMAC on collision.
    // CPU 32-bit read selects a lane from the 128-bit qword on the
    // response side.
    // ------------------------------------------------------------------

    logic cpu_rd_hit_ram;
    logic dmac_rd_hit_ram;
    assign cpu_rd_hit_ram  = ee_rd_en   && ee_rd_is_ram;
    assign dmac_rd_hit_ram = dmac_rd_en && dmac_is_ram;

    // ------------------------------------------------------------------
    // Ch115 — CPU vs DMAC RAM-read arbitration with DMAC retry.
    //
    // PRE-Ch115 BUG: when both `cpu_rd_hit_ram` and
    // `dmac_rd_hit_ram` fired on the same cycle, the map gave
    // CPU priority on `ram_rd_addr` but ALSO returned `dmac_rd_valid`
    // next cycle as if the DMAC had been serviced — so the DMAC
    // saw the CPU's data instead of its own. With a RAM-resident
    // EE bootlet busy-fetching while DMAC streamed a payload from
    // the same RAM, this silently corrupted the DMAC payload
    // (reproducer: tb_ee_ram_cpu_dmac_contention).
    //
    // FIX: keep CPU priority on the RAM bus, but when the DMAC's
    // request is contended out, BUFFER the DMAC's address into a
    // 1-deep retry slot here in the map. On the next cycle that
    // is NOT cpu-rd-hit-ram, replay the buffered DMAC read. The
    // DMAC's `mem_rd_en` is a one-shot pulse and `dmac_reg_stub`
    // waits in S_FETCH_WAIT for `mem_rd_valid`, so deferring the
    // valid response by one (or more) cycles is safe — the DMAC
    // simply waits longer before advancing to S_ACTIVE_SEND. The
    // CPU's path is unchanged: its request always fires this cycle.
    // ------------------------------------------------------------------
    logic        dmac_rd_buf_q;
    logic [24:0] dmac_rd_buf_addr_q;
    logic        dmac_rd_serviced_now;

    // CPU priority remains absolute. DMAC fires when CPU isn't
    // hitting RAM AND there's a request to service (either fresh
    // this cycle or buffered from a prior contended cycle).
    assign dmac_rd_serviced_now = !cpu_rd_hit_ram &&
                                  (dmac_rd_buf_q || dmac_rd_hit_ram);

    assign ram_rd_en   = cpu_rd_hit_ram || dmac_rd_serviced_now;
    assign ram_rd_addr = cpu_rd_hit_ram ? ee_rd_ram_offset
                       : dmac_rd_buf_q  ? dmac_rd_buf_addr_q
                                        : dmac_ram_offset;

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            dmac_rd_buf_q      <= 1'b0;
            dmac_rd_buf_addr_q <= '0;
        end else begin
            // Set buffer on a fresh contention cycle (DMAC issued a
            // request that lost to the CPU). Only latch when the
            // buffer is empty — the DMAC won't issue a new request
            // while one is outstanding (it waits in S_FETCH_WAIT),
            // so we never need to enqueue more than one.
            if (!dmac_rd_buf_q && cpu_rd_hit_ram && dmac_rd_hit_ram) begin
                dmac_rd_buf_q      <= 1'b1;
                dmac_rd_buf_addr_q <= dmac_ram_offset;
            end
            // Clear buffer the cycle the buffered request actually
            // gets the bus.
            else if (dmac_rd_buf_q && dmac_rd_serviced_now) begin
                dmac_rd_buf_q      <= 1'b0;
            end
        end
    end

    // ------------------------------------------------------------------
    // Shared 128-bit RAM write port. CPU wins over bridge on collision.
    // CPU 32-bit data expands to a 128-bit lane; ram_wr_be masks so
    // only the four bytes of that lane are enabled.
    // ------------------------------------------------------------------

    logic cpu_wr_hit_ram;
    logic bridge_wr_hit_ram;
    assign cpu_wr_hit_ram    = ee_wr_en     && ee_wr_is_ram;
    assign bridge_wr_hit_ram = bridge_wr_en && bridge_is_ram;

    // Ch64 useg-mirror hit: the CPU write is targeting a useg address
    // whose phys falls in the parameterized window. When enabled, the
    // same data also lands in ee_ram (lane-expanded below). Disjoint
    // from cpu_wr_hit_ram because ee_wr_is_useg and ee_wr_is_ram are
    // mutually exclusive by construction.
    //
    // Ch70 unified alias: the same write also mirrors into ee_ram if
    // CPU_RAM_ALIAS_EN is set and the phys is in [CPU_RAM_ALIAS_LO,
    // CPU_RAM_ALIAS_HI). This subsumes the Ch64 narrow knob when
    // enabled.
    logic cpu_wr_useg_mirror_hit;
    logic cpu_wr_useg_alias_hit;
    assign cpu_wr_useg_alias_hit =
        CPU_RAM_ALIAS_EN && ee_wr_en && ee_wr_is_useg_shadow &&
        (ee_wr_phys >= CPU_RAM_ALIAS_LO) &&
        (ee_wr_phys <  CPU_RAM_ALIAS_HI);
    assign cpu_wr_useg_mirror_hit =
        cpu_wr_useg_alias_hit ||
        (CPU_USEG_TO_RAM_MIRROR_EN && ee_wr_en && ee_wr_is_useg_shadow &&
         (ee_wr_phys >= CPU_USEG_TO_RAM_MIRROR_LO) &&
         (ee_wr_phys <  CPU_USEG_TO_RAM_MIRROR_HI));

    logic cpu_wr_to_ram_any;
    assign cpu_wr_to_ram_any = cpu_wr_hit_ram || cpu_wr_useg_mirror_hit;

    logic [1:0]   cpu_wr_lane;
    logic [127:0] cpu_wr_data_exp;
    logic [15:0]  cpu_wr_be_exp;
    assign cpu_wr_lane = ee_wr_addr[3:2];
    always_comb begin
        cpu_wr_data_exp = 128'd0;
        cpu_wr_be_exp   = 16'd0;
        case (cpu_wr_lane)
            2'd0: begin
                cpu_wr_data_exp[31:0]   = ee_wr_data;
                cpu_wr_be_exp[3:0]      = ee_wr_be;
            end
            2'd1: begin
                cpu_wr_data_exp[63:32]  = ee_wr_data;
                cpu_wr_be_exp[7:4]      = ee_wr_be;
            end
            2'd2: begin
                cpu_wr_data_exp[95:64]  = ee_wr_data;
                cpu_wr_be_exp[11:8]     = ee_wr_be;
            end
            2'd3: begin
                cpu_wr_data_exp[127:96] = ee_wr_data;
                cpu_wr_be_exp[15:12]    = ee_wr_be;
            end
        endcase
    end

    assign ram_wr_en     = cpu_wr_to_ram_any || bridge_wr_hit_ram;
    // cpu_wr_hit_ram and cpu_wr_useg_mirror_hit are mutually
    // exclusive; ee_wr_phys[24:0] is the same extraction as
    // ee_wr_ram_offset, so one address expression covers both CPU
    // cases.
    assign ram_wr_addr   = cpu_wr_to_ram_any ? ee_wr_phys[24:0] : bridge_ram_offset;
    assign ram_wr_data   = cpu_wr_to_ram_any ? cpu_wr_data_exp  : bridge_wr_data;
    assign ram_wr_be     = cpu_wr_to_ram_any ? cpu_wr_be_exp    : bridge_wr_be;
    assign ram_master_id = cpu_wr_to_ram_any ? 8'd0             : bridge_master_id;

    // ------------------------------------------------------------------
    // EE DMAC ch2 register write port (chapter 3, write-only)
    // ------------------------------------------------------------------

    assign ee_dmac_ch2_wr_en   = ee_wr_en && ee_wr_is_dmac_ch2;
    assign ee_dmac_ch2_wr_addr = ee_wr_phys[7:0];
    assign ee_dmac_ch2_wr_data = ee_wr_data;

    assign ee_dmac_ch2_rd_en   = ee_rd_en && ee_rd_is_dmac_ch2;
    assign ee_dmac_ch2_rd_addr = ee_rd_phys[7:0];

    assign ee_intc_wr_en   = ee_wr_en && ee_wr_is_intc;
    assign ee_intc_wr_addr = ee_wr_phys[7:0];
    assign ee_intc_wr_data = ee_wr_data;

    assign ee_intc_rd_en   = ee_rd_en && ee_rd_is_intc;
    assign ee_intc_rd_addr = ee_rd_phys[7:0];

    // ------------------------------------------------------------------
    // Ch287 — EE DMAC global control/status stub, instantiated
    // INTERNALLY so existing TBs don't need to route new ports.
    // Surface: D_CTRL/D_STAT/D_PCR/D_SQWC/D_RBSR/D_RBOR in the
    // 0x1000_E000-0x1000_E0FF window (broader 4 KiB window decoded by
    // EE_DMAC_CTRL_BASE[28:12] match; the live registers occupy the
    // first 256 bytes via reg_offset = ee_*_phys[7:0]).
    // ------------------------------------------------------------------
    logic        ee_dmac_ctrl_wr_en;
    logic [7:0]  ee_dmac_ctrl_wr_addr;
    logic [31:0] ee_dmac_ctrl_wr_data;
    logic        ee_dmac_ctrl_rd_en;
    logic [7:0]  ee_dmac_ctrl_rd_addr;
    logic [31:0] ee_dmac_ctrl_rd_data;
    logic        ee_dmac_ctrl_rd_valid;
    // Trace from the inner stub is currently unwired (the outer
    // map already emits one trace event per CPU access via the
    // EV_READ/EV_WRITE arms below; merging two trace sources here
    // would require a mux that doesn't exist yet). Future chapters
    // that need DMAC-level retire detail can plumb these into
    // trace_sink_stub directly from the TB hierarchy.
    logic                ee_dmac_ctrl_ev_valid;
    trace_pkg::subsys_e  ee_dmac_ctrl_ev_subsys;
    trace_pkg::event_e   ee_dmac_ctrl_ev_event;
    logic [63:0]         ee_dmac_ctrl_ev_arg0;
    logic [63:0]         ee_dmac_ctrl_ev_arg1;
    logic [63:0]         ee_dmac_ctrl_ev_arg2;
    logic [63:0]         ee_dmac_ctrl_ev_arg3;
    logic [31:0]         ee_dmac_ctrl_ev_flags;

    assign ee_dmac_ctrl_wr_en   = ee_wr_en && ee_wr_is_dmac_ctrl;
    assign ee_dmac_ctrl_wr_addr = ee_wr_phys[7:0];
    assign ee_dmac_ctrl_wr_data = ee_wr_data;
    assign ee_dmac_ctrl_rd_en   = ee_rd_en && ee_rd_is_dmac_ctrl;
    assign ee_dmac_ctrl_rd_addr = ee_rd_phys[7:0];

    ee_dmac_ctrl_stub u_dmac_ctrl (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en(ee_dmac_ctrl_wr_en),
        .reg_offset(ee_dmac_ctrl_wr_en ? ee_dmac_ctrl_wr_addr
                                       : ee_dmac_ctrl_rd_addr),
        .reg_wr_data(ee_dmac_ctrl_wr_data),
        .reg_rd_en(ee_dmac_ctrl_rd_en),
        .reg_rd_data(ee_dmac_ctrl_rd_data),
        .reg_rd_valid(ee_dmac_ctrl_rd_valid),
        .ev_valid(ee_dmac_ctrl_ev_valid),
        .ev_subsys(ee_dmac_ctrl_ev_subsys),
        .ev_event(ee_dmac_ctrl_ev_event),
        .ev_arg0(ee_dmac_ctrl_ev_arg0),
        .ev_arg1(ee_dmac_ctrl_ev_arg1),
        .ev_arg2(ee_dmac_ctrl_ev_arg2),
        .ev_arg3(ee_dmac_ctrl_ev_arg3),
        .ev_flags(ee_dmac_ctrl_ev_flags)
    );

    // ------------------------------------------------------------------
    // Ch288 — Passive per-channel surface (ch0/1/3/4/5). Hosts
    // CHCR/MADR/QWC/TADR per channel; latched, no FSM, no D_STAT
    // side effects. Same internal-instance pattern as Ch287.
    // ------------------------------------------------------------------
    logic        ee_dmac_passive_wr_en;
    logic [15:0] ee_dmac_passive_wr_addr;
    logic [31:0] ee_dmac_passive_wr_data;
    logic        ee_dmac_passive_rd_en;
    logic [15:0] ee_dmac_passive_rd_addr;
    logic [31:0] ee_dmac_passive_rd_data;
    logic        ee_dmac_passive_rd_valid;
    logic                ee_dmac_passive_ev_valid;
    trace_pkg::subsys_e  ee_dmac_passive_ev_subsys;
    trace_pkg::event_e   ee_dmac_passive_ev_event;
    logic [63:0]         ee_dmac_passive_ev_arg0;
    logic [63:0]         ee_dmac_passive_ev_arg1;
    logic [63:0]         ee_dmac_passive_ev_arg2;
    logic [63:0]         ee_dmac_passive_ev_arg3;
    logic [31:0]         ee_dmac_passive_ev_flags;

    assign ee_dmac_passive_wr_en   = ee_wr_en && ee_wr_is_dmac_passive;
    assign ee_dmac_passive_wr_addr = ee_wr_phys[15:0];
    assign ee_dmac_passive_wr_data = ee_wr_data;
    assign ee_dmac_passive_rd_en   = ee_rd_en && ee_rd_is_dmac_passive;
    assign ee_dmac_passive_rd_addr = ee_rd_phys[15:0];

    ee_dmac_passive_chan_stub u_dmac_passive (
        .clk(clk), .rst_n(rst_n),
        .reg_wr_en(ee_dmac_passive_wr_en),
        .chan_addr(ee_dmac_passive_wr_en ? ee_dmac_passive_wr_addr
                                         : ee_dmac_passive_rd_addr),
        .reg_wr_data(ee_dmac_passive_wr_data),
        .reg_rd_en(ee_dmac_passive_rd_en),
        .reg_rd_data(ee_dmac_passive_rd_data),
        .reg_rd_valid(ee_dmac_passive_rd_valid),
        .ev_valid(ee_dmac_passive_ev_valid),
        .ev_subsys(ee_dmac_passive_ev_subsys),
        .ev_event(ee_dmac_passive_ev_event),
        .ev_arg0(ee_dmac_passive_ev_arg0),
        .ev_arg1(ee_dmac_passive_ev_arg1),
        .ev_arg2(ee_dmac_passive_ev_arg2),
        .ev_arg3(ee_dmac_passive_ev_arg3),
        .ev_flags(ee_dmac_passive_ev_flags)
    );

    assign ee_misc_mmio_wr_en   = ee_wr_en && ee_wr_is_misc_mmio;
    assign ee_misc_mmio_wr_addr = ee_wr_phys[15:0];
    assign ee_misc_mmio_wr_data = ee_wr_data;
    assign ee_misc_mmio_wr_be   = ee_wr_be;

    // Ch112 — GS privileged-MMIO write routing.
    assign ee_gs_priv_wr_en     = ee_wr_en && ee_wr_is_gs_priv;
    assign ee_gs_priv_wr_addr   = ee_wr_phys[15:0];
    assign ee_gs_priv_wr_data   = ee_wr_data;
    assign ee_gs_priv_wr_be     = ee_wr_be;

    assign ee_misc_mmio_rd_en   = ee_rd_en && ee_rd_is_misc_mmio;
    assign ee_misc_mmio_rd_addr = ee_rd_phys[15:0];

    assign ee_biu_wr_en   = ee_wr_en && ee_wr_is_biu;
    assign ee_biu_wr_addr = ee_wr_phys[11:0];
    assign ee_biu_wr_data = ee_wr_data;
    assign ee_biu_wr_be   = ee_wr_be;

    assign ee_biu_rd_en   = ee_rd_en && ee_rd_is_biu;
    assign ee_biu_rd_addr = ee_rd_phys[11:0];

    // ------------------------------------------------------------------
    // EE CPU read response pipeline (one-cycle latency, matches the rest
    // of the stub ecosystem). Latch which region answered plus the lane
    // index for RAM reads.
    // ------------------------------------------------------------------

    logic       ee_rd_pending;
    logic       ee_rd_was_bios;
    logic       ee_rd_was_ram;
    logic       ee_rd_was_dmac_ch2;
    logic       ee_rd_was_dmac_ctrl;     // Ch287
    logic       ee_rd_was_dmac_passive;  // Ch288
    logic       ee_rd_was_intc;
    logic       ee_rd_was_misc_mmio;
    logic       ee_rd_was_biu;
    logic       ee_rd_was_useg_shadow;
    logic [1:0] ee_rd_ram_lane;

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            ee_rd_pending          <= 1'b0;
            ee_rd_was_bios         <= 1'b0;
            ee_rd_was_ram          <= 1'b0;
            ee_rd_was_dmac_ch2     <= 1'b0;
            ee_rd_was_dmac_ctrl    <= 1'b0;
            ee_rd_was_dmac_passive <= 1'b0;
            ee_rd_was_intc         <= 1'b0;
            ee_rd_was_misc_mmio    <= 1'b0;
            ee_rd_was_biu          <= 1'b0;
            ee_rd_was_useg_shadow  <= 1'b0;
            ee_rd_ram_lane         <= 2'd0;
        end else begin
            ee_rd_pending <= ee_rd_en;
            if (ee_rd_en) begin
                ee_rd_was_bios         <= ee_rd_is_bios;
                ee_rd_was_ram          <= ee_rd_is_ram;
                ee_rd_was_dmac_ch2     <= ee_rd_is_dmac_ch2;
                ee_rd_was_dmac_ctrl    <= ee_rd_is_dmac_ctrl;    // Ch287
                ee_rd_was_dmac_passive <= ee_rd_is_dmac_passive; // Ch288
                ee_rd_was_intc         <= ee_rd_is_intc;
                ee_rd_was_misc_mmio    <= ee_rd_is_misc_mmio;
                ee_rd_was_biu          <= ee_rd_is_biu;
                ee_rd_was_useg_shadow  <= ee_rd_is_useg_shadow;
                ee_rd_ram_lane         <= ee_rd_addr[3:2];
            end
        end
    end

    logic [31:0] ee_rd_ram_slice;
    always_comb begin
        case (ee_rd_ram_lane)
            2'd0:    ee_rd_ram_slice = ram_rd_data[31:0];
            2'd1:    ee_rd_ram_slice = ram_rd_data[63:32];
            2'd2:    ee_rd_ram_slice = ram_rd_data[95:64];
            2'd3:    ee_rd_ram_slice = ram_rd_data[127:96];
            default: ee_rd_ram_slice = 32'd0;
        endcase
    end

    assign ee_rd_valid = ee_rd_pending;
    assign ee_rd_data  = ee_rd_was_bios         ? bios_rd_data
                       : ee_rd_was_ram          ? ee_rd_ram_slice
                       : ee_rd_was_useg_shadow  ? useg_shadow_rd_data
                       : ee_rd_was_dmac_ch2     ? ee_dmac_ch2_rd_data
                       : ee_rd_was_dmac_ctrl    ? ee_dmac_ctrl_rd_data     // Ch287
                       : ee_rd_was_dmac_passive ? ee_dmac_passive_rd_data  // Ch288
                       : ee_rd_was_intc         ? ee_intc_rd_data
                       : ee_rd_was_misc_mmio    ? ee_misc_mmio_rd_data
                       : ee_rd_was_biu          ? ee_biu_rd_data
                                                : 32'hDEADBEEF;

    // ------------------------------------------------------------------
    // DMAC read response pipeline (128-bit). Ch115 — `dmac_rd_pending`
    // now sets ONLY when the DMAC's read actually hits the RAM bus
    // this cycle (`dmac_rd_serviced_now`), so a contended-out request
    // doesn't return false data with the CPU's contents (the
    // pre-Ch115 corruption bug). For non-RAM dmac_rd_en hits (other
    // dmac_is_* targets), the existing behavior is preserved.
    // ------------------------------------------------------------------

    logic dmac_rd_pending;
    logic dmac_rd_was_ram;
    logic dmac_rd_actually_serviced;

    // The DMAC's pending response fires when:
    //   (a) it actually got the RAM bus this cycle (RAM target), OR
    //   (b) its request hit a non-RAM target (dmac_rd_en && !dmac_is_ram —
    //       no contention possible there).
    // Otherwise it stays in S_FETCH_WAIT, waiting for the buffered
    // replay (RAM case) or the original request (non-RAM case).
    assign dmac_rd_actually_serviced =
        dmac_rd_serviced_now ||
        (dmac_rd_en && !dmac_is_ram);

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            dmac_rd_pending <= 1'b0;
            dmac_rd_was_ram <= 1'b0;
        end else begin
            dmac_rd_pending <= dmac_rd_actually_serviced;
            if (dmac_rd_actually_serviced)
                dmac_rd_was_ram <= dmac_rd_serviced_now;
        end
    end

    assign dmac_rd_valid = dmac_rd_pending;
    assign dmac_rd_data  = dmac_rd_was_ram
                            ? ram_rd_data
                            : 128'hDEAD_BEEF_DEAD_BEEF_DEAD_BEEF_DEAD_BEEF;

    // ------------------------------------------------------------------
    // Trace emission — one event per cycle, priority:
    //   EE read > EE write > DMAC read
    // Two paths are unlikely to be active simultaneously in Wave 2.7
    // testbenches (each TB exercises a single master); priority here is
    // defensive.
    // ------------------------------------------------------------------

    always_ff @(posedge clk) begin
        if (!rst_n) begin
            ev_valid  <= 1'b0;
            ev_subsys <= SUBSYS_MEM;
            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 (ee_rd_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_MEM;
            if (ee_rd_is_bios) begin
                ev_event <= EV_READ;
                ev_arg1  <= 64'd0;
                ev_arg3  <= REGION_BIOS;
            end else if (ee_rd_is_ram) begin
                ev_event <= EV_READ;
                ev_arg1  <= 64'd0;
                ev_arg3  <= REGION_EE_RAM;
            end else if (ee_rd_is_dmac_ch2) begin
                ev_event <= EV_READ;
                ev_arg1  <= 64'd0;
                ev_arg3  <= REGION_EE_DMAC;
            end else if (ee_rd_is_dmac_ctrl) begin   // Ch287
                ev_event <= EV_READ;
                ev_arg1  <= 64'd0;
                ev_arg3  <= REGION_EE_DMAC_CTRL;
            end else if (ee_rd_is_dmac_passive) begin   // Ch288
                ev_event <= EV_READ;
                ev_arg1  <= 64'd0;
                ev_arg3  <= REGION_EE_DMAC_PASSIVE;
            end else if (ee_rd_is_intc) begin
                ev_event <= EV_READ;
                ev_arg1  <= 64'd0;
                ev_arg3  <= REGION_EE_INTC;
            end else if (ee_rd_is_misc_mmio) begin
                ev_event <= EV_READ;
                ev_arg1  <= 64'd0;
                ev_arg3  <= REGION_EE_MISC_MMIO;
            end else if (ee_rd_is_biu) begin
                ev_event <= EV_READ;
                ev_arg1  <= 64'd0;
                ev_arg3  <= REGION_EE_BIU;
            end else if (ee_rd_is_useg_shadow) begin
                // Ch34: useg-shadow hits get their own region tag so
                // post-run analysis can distinguish them from kseg0
                // RAM hits. Pre-ch33 code conflated the two via the
                // phys=addr[28:0] strip; the shadow is now a separate
                // 4 MiB backing.
                ev_event <= EV_READ;
                ev_arg1  <= 64'd0;
                ev_arg3  <= REGION_USEG_SHADOW;
            end else begin
                ev_event <= EV_UNMAPPED;
                ev_arg1  <= 64'hDEADBEEF;
                ev_arg3  <= REGION_UNMAPPED;
            end
            ev_arg0   <= {32'd0, ee_rd_addr};
            ev_arg2   <= MASTER_EE_IFETCH;
            ev_flags  <= 32'd0;
        end else if (ee_wr_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_MEM;
            if (ee_wr_is_ram) begin
                ev_event <= EV_WRITE;
                ev_arg3  <= REGION_EE_RAM;
            end else if (ee_wr_is_dmac_ch2) begin
                ev_event <= EV_WRITE;
                ev_arg3  <= REGION_EE_DMAC;
            end else if (ee_wr_is_dmac_ctrl) begin   // Ch287
                ev_event <= EV_WRITE;
                ev_arg3  <= REGION_EE_DMAC_CTRL;
            end else if (ee_wr_is_dmac_passive) begin   // Ch288
                ev_event <= EV_WRITE;
                ev_arg3  <= REGION_EE_DMAC_PASSIVE;
            end else if (ee_wr_is_intc) begin
                ev_event <= EV_WRITE;
                ev_arg3  <= REGION_EE_INTC;
            end else if (ee_wr_is_misc_mmio) begin
                ev_event <= EV_WRITE;
                ev_arg3  <= REGION_EE_MISC_MMIO;
            end else if (ee_wr_is_biu) begin
                ev_event <= EV_WRITE;
                ev_arg3  <= REGION_EE_BIU;
            end else if (ee_wr_is_gs_priv) begin
                // Ch112: GS-privileged window (0x1200_xxxx) routed
                // to ee_gs_priv_bridge_stub. Without this branch a
                // valid 0x1200_xxxx write would surface as
                // EV_UNMAPPED at the trace.
                ev_event <= EV_WRITE;
                ev_arg3  <= REGION_EE_GS_PRIV;
            end else if (ee_wr_is_useg_shadow) begin
                // Ch34: dedicated useg-shadow tag (see read-side note).
                ev_event <= EV_WRITE;
                ev_arg3  <= REGION_USEG_SHADOW;
            end else begin
                ev_event <= EV_UNMAPPED;
                ev_arg3  <= REGION_UNMAPPED;
            end
            ev_arg0   <= {32'd0, ee_wr_addr};
            ev_arg1   <= {32'd0, ee_wr_data};
            ev_arg2   <= MASTER_EE_IFETCH;
            ev_flags  <= 32'h0000_0001;
        end else if (dmac_rd_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_MEM;
            ev_event  <= dmac_is_ram ? EV_READ : EV_UNMAPPED;
            ev_arg0   <= {32'd0, dmac_rd_addr};
            ev_arg1   <= dmac_is_ram ? 64'd0 : 64'hDEADBEEF;
            ev_arg2   <= MASTER_DMAC;
            ev_arg3   <= dmac_is_ram ? REGION_EE_RAM : REGION_UNMAPPED;
            ev_flags  <= 32'd0;
        end else if (bridge_wr_en) begin
            ev_valid  <= 1'b1;
            ev_subsys <= SUBSYS_MEM;
            ev_event  <= bridge_is_ram ? EV_WRITE : EV_UNMAPPED;
            ev_arg0   <= {32'd0, bridge_wr_addr};
            ev_arg1   <= bridge_wr_data[63:0];
            ev_arg2   <= {56'd0, bridge_master_id};
            ev_arg3   <= bridge_is_ram ? REGION_EE_RAM : REGION_UNMAPPED;
            ev_flags  <= 32'h0000_0001;
        end else begin
            ev_valid <= 1'b0;
        end
    end

endmodule : ee_memory_map_stub
