Files
retroDE_ps2/rtl/memory/ee_memory_map_stub.sv
thejayman77 ec82764bef Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression
(272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps,
and all dump-derived textures/traces) is excluded via .gitignore and stays local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:10:50 -04:00

1229 lines
60 KiB
Systemverilog
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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