Files
retroDE_ps2/rtl/iop/iop_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

653 lines
29 KiB
Systemverilog

// retroDE_ps2 — iop_memory_map_stub
//
// IOP-side memory map. Gives IOP-visible addresses architectural meaning.
// Wave 3 first-pass scope is deliberately narrow: only the IOP-RAM window
// is routed; every other address decodes as UNMAPPED with deterministic
// fault data. SIF registers, IOP I/O, SPU2, CDVD, and IOP-side BIOS are
// all intentionally deferred — slots noted in comments so the map can
// grow without re-shaping its interface.
//
// Contract refs:
// docs/contracts/iop.md (IOP-local address decode)
// docs/contracts/memory.md (IOP RAM lives at phys 0x00000000-
// 0x001FFFFF, 2 MiB)
//
// Address semantics:
// IOP CPU side is MIPS R3000-class. kseg0/kseg1 aliases
// (0x80000000/0xA0000000 mirrors of 0x00000000) are modelled via
// `phys = iop_addr[28:0]`, consistent with how ee_memory_map_stub
// strips kseg for EE fetches. Physical window decode then works on
// the low 29 bits.
//
// Bridge-side write port (Wave 3 addition): the SIF-to-IOP-RAM bridge
// writes directly at physical offsets — no kseg strip. The map decodes
// its address bits directly against the same region rules.
//
// DMA read-master port (Wave 3 reverse-direction addition): the IOP
// DMAC (ch9 SIF0 egress, and any other future channel) fetches source
// bytes through this port. Physical addressing; RAM-only decode in
// current scope. Caller provides its own master_id (convention: 4 =
// IOP_DMAC).
//
// Arbitration (Wave 3 scope):
// Two potential write masters on the RAM path: the IOP CPU port and
// the bridge port. Two potential read masters on the RAM path: the
// IOP CPU port and the DMA read master. Collisions within the same
// cycle are not expected in the current TBs (CPU programming / readback
// phases are separate from DMA transfer phases). Policy if they ever
// collide: CPU wins. Documented here rather than hidden in priority
// ordering; RAM port is mux'd accordingly.
//
// Region decode (current):
// - IOP RAM window: phys[28:21] == 8'b00000000
// (0x0000_0000 - 0x001F_FFFF, 2 MiB)
// → route to iop_ram_stub (offset phys[20:0])
// - SIF registers (IOP side): phys[28:24] == 5'b11101
// (0x1D00_0000 block) → route to the SIF
// register shell with offset phys[7:0]. The
// mailbox stub's register surface covers
// offsets 0x00/0x10/0x20/0x30.
// - IOP DMAC channel 9: phys[28:4] == 25'h01F8_0152
// (0x1F80_1520 - 0x1F80_152F, 16 bytes)
// → route to iop_dmac_reg_stub with 4-bit
// offset phys[3:0]. Channel 9 is SIF0
// (IOP→EE) in the real PS2 DMAC map; other
// channels are intentionally not decoded.
// - IOP INTC: phys[28:4] == 25'h01F8_0107
// (0x1F80_1070 - 0x1F80_107F, 16 bytes)
// → route to intc_stub with 8-bit offset
// phys[7:0]. Matches the real PS2 IOP INTC
// placement (I_STAT / I_MASK).
// - Shared BIOS ROM: phys[28:22] == 7'b1111111
// (0x1FC0_0000 - 0x1FFF_FFFF, 4 MiB)
// → route to bios_rom_stub with 22-bit
// offset phys[21:0]. kseg1 aliasing maps
// 0xBFC0_0000 fetches to this window via
// the standard [28:0] strip. The IOP core
// reset vector normally points here.
// Writes to BIOS decode as UNMAPPED
// (read-only ROM).
// - everything else: UNMAPPED, reads return 32'hDEADBEEF
//
// Future regions (reserved in comments, not wired):
// - Other IOP DMAC channels: 0x1F80_1080-0x1F80_156F (partial block)
// - IOP timers / SIO: elsewhere in 0x1F80_0000 block
// - SPU2: 0x1F90_0000 block
//
// Trace semantics (matches ee_memory_map_stub's request-routing pattern):
// Map-layer events describe routing (what was asked for, where it was
// sent). Arg1 is 0 when the request is routed to a backing store that
// will emit its own delivery event; 0xDEADBEEF on unmapped reads; the
// actual write data on unmapped writes (so the TB can see what software
// tried to write).
//
// Latency assumption (mirrors ee_memory_map_stub note):
// Assumes fixed one-cycle backing-store latency. `ram_rd_valid` is not
// consulted — the map asserts its own `iop_rd_valid` one cycle after
// request unconditionally. All Wave 3 backing stubs honour that. If a
// later backing store introduces wait states, the map must grow proper
// response handshaking.
//
// Trace payload schema:
// IOP READ arg0=addr arg1=0 arg2=master_id arg3=region_id
// IOP WRITE arg0=addr arg1=wr_data arg2=master_id arg3=region_id
// IOP UNMAPPED arg0=addr arg1=0xDEADBEEF arg2=master_id arg3=0xFF
// region_id: 2 = IOP_RAM, 3 = SIF_REGS, 4 = IOP_DMAC, 5 = IOP_INTC,
// 6 = IOP_BIOS
// master_id: 2 = IOP_CPU, 3 = SIF bridge (writes), 4 = IOP_DMAC (reads)
// flags bit 0: 1 = write, 0 = read
`timescale 1ns/1ps
module iop_memory_map_stub
import trace_pkg::*;
(
input logic clk,
input logic rst_n,
// ------------------------------------------------------------------
// IOP CPU-side request interface (32-bit data, virtual address)
// ------------------------------------------------------------------
input logic iop_rd_en,
input logic [31:0] iop_rd_addr,
output logic [31:0] iop_rd_data,
output logic iop_rd_valid,
input logic iop_wr_en,
input logic [31:0] iop_wr_addr,
input logic [31:0] iop_wr_data,
input logic [3:0] iop_wr_be,
// Caller-provided master id for trace attribution. Conventional:
// 0 = TB direct, 2 = IOP CPU (once a fetch stub exists).
input logic [7:0] master_id,
// ------------------------------------------------------------------
// Bridge-side write port (Wave 3). Physical addresses; no kseg strip.
// Used by sif_dma_iop_ram_bridge_stub and similar DMA-side masters.
// Caller provides its own master_id (convention: 3 = SIF bridge).
// ------------------------------------------------------------------
input logic bridge_wr_en,
input logic [31:0] bridge_wr_addr,
input logic [31:0] bridge_wr_data,
input logic [3:0] bridge_wr_be,
input logic [7:0] bridge_master_id,
// ------------------------------------------------------------------
// DMA read-master port (Wave 3). Physical addressing; intended for
// IOP DMAC ch9 reads out of IOP RAM. One-cycle read latency, same
// pipeline shape as the CPU read. Caller provides its own master_id
// (convention: 4 = IOP_DMAC).
// ------------------------------------------------------------------
input logic dma_rd_en,
input logic [31:0] dma_rd_addr,
input logic [7:0] dma_master_id,
output logic [31:0] dma_rd_data,
output logic dma_rd_valid,
// ------------------------------------------------------------------
// Downstream to iop_ram_stub.
// Address presented as a 21-bit offset within the 2 MiB IOP RAM
// window; consumers may truncate to match their backing-store width.
// ------------------------------------------------------------------
output logic ram_rd_en,
output logic [20:0] ram_rd_addr,
input logic [31:0] ram_rd_data,
input logic ram_rd_valid,
output logic ram_wr_en,
output logic [20:0] ram_wr_addr,
output logic [31:0] ram_wr_data,
output logic [3:0] ram_wr_be,
output logic [7:0] ram_master_id,
// ------------------------------------------------------------------
// Downstream to the SIF register shell (sif_mailbox_stub IOP-side
// port). Low byte of the physical address is presented; writes go
// out with the CPU's data/be; reads come back with 1-cycle latency
// consistent with the rest of the stub ecosystem.
// ------------------------------------------------------------------
output logic sif_rd_en,
output logic [7:0] sif_rd_addr,
input logic [31:0] sif_rd_data,
input logic sif_rd_valid,
output logic sif_wr_en,
output logic [7:0] sif_wr_addr,
output logic [31:0] sif_wr_data,
// ------------------------------------------------------------------
// Downstream to the IOP DMAC register shell (channel 9). 4-bit
// offset; data path uses the CPU write data. Read returns with
// one-cycle latency like the rest of the stub ecosystem.
// ------------------------------------------------------------------
output logic iop_dmac_rd_en,
output logic [3:0] iop_dmac_rd_addr,
input logic [31:0] iop_dmac_rd_data,
input logic iop_dmac_rd_valid,
output logic iop_dmac_wr_en,
output logic [3:0] iop_dmac_wr_addr,
output logic [31:0] iop_dmac_wr_data,
// ------------------------------------------------------------------
// Downstream to the IOP INTC register shell (intc_stub reused).
// 8-bit offset passed downstream; read returns with one-cycle
// latency consistent with the rest of the stub ecosystem.
// ------------------------------------------------------------------
output logic iop_intc_rd_en,
output logic [7:0] iop_intc_rd_addr,
input logic [31:0] iop_intc_rd_data,
input logic iop_intc_rd_valid,
output logic iop_intc_wr_en,
output logic [7:0] iop_intc_wr_addr,
output logic [31:0] iop_intc_wr_data,
// ------------------------------------------------------------------
// Downstream to bios_rom_stub (shared BIOS window).
// 22-bit byte offset within the 4 MiB window. Writes are never
// forwarded (BIOS is ROM); the map routes any bios-window write
// attempt to the UNMAPPED trace event instead.
// ------------------------------------------------------------------
output logic bios_rd_en,
output logic [21:0] bios_rd_addr,
input logic [31:0] bios_rd_data,
input logic bios_rd_valid,
// ------------------------------------------------------------------
// Ch234 — bridge-clock-domain pad bitmaps from ps2_hps_bridge
// (INPUT_P1/P2 latches @ 0x040/0x044). Sync'd into the IOP clock
// by the internal `sio2_input_stub` instance below. TBs that
// don't exercise the pad path can tie both ports to `32'd0`.
// ------------------------------------------------------------------
input logic [31:0] input_p1,
input logic [31:0] input_p2,
// ------------------------------------------------------------------
// Trace
// ------------------------------------------------------------------
output logic ev_valid,
output subsys_e ev_subsys,
output event_e ev_event,
output logic [63:0] ev_arg0,
output logic [63:0] ev_arg1,
output logic [63:0] ev_arg2,
output logic [63:0] ev_arg3,
output logic [31:0] ev_flags
);
localparam logic [63:0] REGION_IOP_RAM = 64'd2;
localparam logic [63:0] REGION_SIF_REGS = 64'd3;
localparam logic [63:0] REGION_IOP_DMAC = 64'd4;
localparam logic [63:0] REGION_IOP_INTC = 64'd5;
localparam logic [63:0] REGION_IOP_BIOS = 64'd6;
localparam logic [63:0] REGION_PAD_IO = 64'd7; // Ch234
localparam logic [63:0] REGION_UNMAPPED = 64'hFF;
localparam logic [28:0] DMAC_CH9_BASE = 29'h1F80_1520;
localparam logic [28:0] IOP_INTC_BASE = 29'h1F80_1070;
// Ch234 — retroDE-local pad I/O window (256 bytes), deliberately
// OUTSIDE the real SIO2 range (0x1F80_8200..0x1F80_82FF) so a
// faithful SIO2 emulation chapter can land later without collision.
localparam logic [28:0] PAD_IO_BASE = 29'h1F80_8500;
// ------------------------------------------------------------------
// Region decode (combinational, shared for read + write)
// ------------------------------------------------------------------
// CPU-side decode (virtual → physical via kseg strip)
logic [28:0] rd_phys_addr;
logic [28:0] wr_phys_addr;
logic rd_is_ram;
logic rd_is_sif;
logic rd_is_dmac;
logic rd_is_intc;
logic rd_is_bios;
logic rd_is_pad; // Ch234
logic cpu_wr_is_ram;
logic cpu_wr_is_sif;
logic cpu_wr_is_dmac;
logic cpu_wr_is_intc;
logic cpu_wr_is_bios;
logic cpu_wr_is_pad; // Ch234
logic [20:0] rd_ram_offset;
logic [20:0] cpu_wr_ram_offset;
assign rd_phys_addr = iop_rd_addr[28:0];
assign wr_phys_addr = iop_wr_addr[28:0];
assign rd_is_ram = (rd_phys_addr[28:21] == 8'd0);
assign rd_is_sif = (rd_phys_addr[28:24] == 5'b11101);
assign rd_is_dmac = (rd_phys_addr[28:4] == DMAC_CH9_BASE[28:4]);
assign rd_is_intc = (rd_phys_addr[28:4] == IOP_INTC_BASE[28:4]);
assign rd_is_bios = (rd_phys_addr[28:22] == 7'b1111111);
// Ch234 — pad I/O region is 256 bytes at PAD_IO_BASE, so the
// decode is bits [28:8] (= 21 high bits of the 29-bit phys addr).
assign rd_is_pad = (rd_phys_addr[28:8] == PAD_IO_BASE[28:8]);
assign cpu_wr_is_ram = (wr_phys_addr[28:21] == 8'd0);
assign cpu_wr_is_sif = (wr_phys_addr[28:24] == 5'b11101);
assign cpu_wr_is_dmac = (wr_phys_addr[28:4] == DMAC_CH9_BASE[28:4]);
assign cpu_wr_is_intc = (wr_phys_addr[28:4] == IOP_INTC_BASE[28:4]);
assign cpu_wr_is_bios = (wr_phys_addr[28:22] == 7'b1111111);
assign cpu_wr_is_pad = (wr_phys_addr[28:8] == PAD_IO_BASE[28:8]);
assign rd_ram_offset = rd_phys_addr[20:0];
assign cpu_wr_ram_offset = wr_phys_addr[20:0];
// Bridge-side decode (physical, no strip). Bridge writes are routed
// to IOP RAM only — no SIF destination from the bridge side yet.
logic bridge_wr_is_ram;
logic [20:0] bridge_wr_ram_offset;
assign bridge_wr_is_ram = (bridge_wr_addr[28:21] == 8'd0);
assign bridge_wr_ram_offset = bridge_wr_addr[20:0];
// DMA-side read decode (physical, no strip). Scope covers RAM only.
logic dma_rd_is_ram;
logic [20:0] dma_rd_ram_offset;
assign dma_rd_is_ram = (dma_rd_addr[28:21] == 8'd0);
assign dma_rd_ram_offset = dma_rd_addr[20:0];
// RAM routing. Ch261 — DMA wins the port on CPU+DMA collision; the
// CPU's read address is latched into a one-entry pending slot and
// serviced on the next RAM cycle that the DMA does not consume.
// Pre-Ch261 the comment above this block was "CPU read wins over
// DMA read on same-cycle collision" but the silent consequence was
// the DMA path sampling `ram_rd_data` from the CPU's address —
// silent DMA data corruption. The Ch261 SIF-landing TB found it.
//
// Single-entry slot is sufficient because every existing CPU
// client of this map is request-then-wait-for-valid (no second
// outstanding read in flight): exec stub, iop_core_stub, fetch
// stub all stall in their own wait state until `iop_rd_valid`
// asserts. A sim-only overflow assertion below catches any future
// client that breaks that assumption.
logic cpu_rd_hit;
logic dma_rd_hit;
logic cpu_dma_collision;
assign cpu_rd_hit = iop_rd_en && rd_is_ram;
assign dma_rd_hit = dma_rd_en && dma_rd_is_ram;
assign cpu_dma_collision = cpu_rd_hit && dma_rd_hit;
// One-entry deferred CPU-RAM-read slot.
logic cpu_pend_valid;
logic [20:0] cpu_pend_addr;
// Service priority (mutually exclusive):
// serve_dma — DMA wins the bus any cycle it asks
// serve_cpu_def — deferred CPU read services on the next non-DMA cycle
// serve_cpu_now — live CPU read services when neither of the above fires
logic serve_dma;
logic serve_cpu_def;
logic serve_cpu_now;
assign serve_dma = dma_rd_hit;
assign serve_cpu_def = !dma_rd_hit && cpu_pend_valid;
assign serve_cpu_now = !dma_rd_hit && !cpu_pend_valid && cpu_rd_hit;
assign ram_rd_en = serve_dma || serve_cpu_def || serve_cpu_now;
assign ram_rd_addr = serve_dma ? dma_rd_ram_offset
: serve_cpu_def ? cpu_pend_addr
: rd_ram_offset;
// Slot update: latch on collision, clear on service.
always_ff @(posedge clk) begin
if (!rst_n) begin
cpu_pend_valid <= 1'b0;
cpu_pend_addr <= 21'd0;
end else begin
if (cpu_dma_collision && !cpu_pend_valid) begin
cpu_pend_valid <= 1'b1;
cpu_pend_addr <= rd_ram_offset;
end else if (serve_cpu_def) begin
cpu_pend_valid <= 1'b0;
end
end
end
`ifndef SYNTHESIS
// Overflow detector: a second CPU+DMA collision while the slot is
// already pending means we'd drop the new CPU read silently. The
// current set of CPU clients can't trigger this (single outstanding
// read each), but future producers should fail loudly here.
always_ff @(posedge clk) begin
if (rst_n && cpu_dma_collision && cpu_pend_valid) begin
$error("[iop_memory_map_stub] Ch261 deferred-CPU-slot overflow: cpu_dma_collision while cpu_pend_valid (live addr=0x%05h pending addr=0x%05h)",
rd_ram_offset, cpu_pend_addr);
end
end
`endif
// SIF register-shell routing. Low byte of the physical address is
// presented downstream (mailbox uses 8-bit offsets).
assign sif_rd_en = iop_rd_en && rd_is_sif;
assign sif_rd_addr = rd_phys_addr[7:0];
assign sif_wr_en = iop_wr_en && cpu_wr_is_sif;
assign sif_wr_addr = wr_phys_addr[7:0];
assign sif_wr_data = iop_wr_data;
// IOP DMAC ch9 routing. Low 4 bits of the physical address select
// among MADR / BCR / CHCR (and any other in-block offsets).
assign iop_dmac_rd_en = iop_rd_en && rd_is_dmac;
assign iop_dmac_rd_addr = rd_phys_addr[3:0];
assign iop_dmac_wr_en = iop_wr_en && cpu_wr_is_dmac;
assign iop_dmac_wr_addr = wr_phys_addr[3:0];
assign iop_dmac_wr_data = iop_wr_data;
// IOP INTC routing. Low byte of the physical address selects
// INTC_STAT (0x00) or INTC_MASK (0x10).
assign iop_intc_rd_en = iop_rd_en && rd_is_intc;
assign iop_intc_rd_addr = rd_phys_addr[7:0];
assign iop_intc_wr_en = iop_wr_en && cpu_wr_is_intc;
assign iop_intc_wr_addr = wr_phys_addr[7:0];
assign iop_intc_wr_data = iop_wr_data;
// Ch234 — pad-I/O region wiring. The map owns a single internal
// `sio2_input_stub` instance; the bridge's INPUT_P1/P2 latches
// flow into it directly. `pad_rd_*` / `pad_wr_*` are the
// map↔stub handshake (4-bit word offset within the 256-byte
// region, captured from phys_addr[5:2]).
wire pad_rd_en;
wire [3:0] pad_rd_addr;
wire [31:0] pad_rd_data;
wire pad_rd_valid;
wire pad_wr_en;
wire [3:0] pad_wr_addr;
wire [31:0] pad_wr_data;
assign pad_rd_en = iop_rd_en && rd_is_pad;
assign pad_rd_addr = rd_phys_addr[5:2];
assign pad_wr_en = iop_wr_en && cpu_wr_is_pad;
assign pad_wr_addr = wr_phys_addr[5:2];
assign pad_wr_data = iop_wr_data;
sio2_input_stub u_sio2_input (
.clk (clk),
.rst_n (rst_n),
.input_p1 (input_p1),
.input_p2 (input_p2),
.rd_en (pad_rd_en),
.rd_addr (pad_rd_addr),
.rd_data (pad_rd_data),
.rd_valid (pad_rd_valid),
.wr_en (pad_wr_en),
.wr_addr (pad_wr_addr),
.wr_data (pad_wr_data)
);
// BIOS ROM routing. 22-bit byte offset within the 4 MiB window.
// No write path — BIOS is read-only.
assign bios_rd_en = iop_rd_en && rd_is_bios;
assign bios_rd_addr = rd_phys_addr[21:0];
// Write-path arbitration for the RAM side: CPU wins on same-cycle
// collision. Neither TB nor current design exercises collision;
// priority is defensive. SIF writes are a separate port and don't
// contend with RAM writes.
logic cpu_wr_hit;
logic bridge_wr_hit;
assign cpu_wr_hit = iop_wr_en && cpu_wr_is_ram;
assign bridge_wr_hit = bridge_wr_en && bridge_wr_is_ram;
assign ram_wr_en = cpu_wr_hit || bridge_wr_hit;
assign ram_wr_addr = cpu_wr_hit ? cpu_wr_ram_offset : bridge_wr_ram_offset;
assign ram_wr_data = cpu_wr_hit ? iop_wr_data : bridge_wr_data;
assign ram_wr_be = cpu_wr_hit ? iop_wr_be : bridge_wr_be;
assign ram_master_id = cpu_wr_hit ? master_id : bridge_master_id;
// ------------------------------------------------------------------
// Read response pipeline
// cycle N : iop_rd_en high, request routed downstream (or unmapped)
// cycle N+1: iop_rd_valid high, data from RAM or fault
// ------------------------------------------------------------------
logic rd_pending;
logic rd_was_ram;
logic rd_was_sif;
logic rd_was_dmac;
logic rd_was_intc;
logic rd_was_bios;
logic rd_was_pad; // Ch234
// Ch261 — rd_pending only pulses when the CPU read is ACTUALLY
// serviced this cycle. Three cases:
// 1. Non-RAM CPU read: always serviced (separate decode paths,
// no arbitration). Pulse rd_pending normally.
// 2. RAM CPU read, no collision: serviced this cycle (serve_cpu_now
// fires above). Pulse rd_pending.
// 3. RAM CPU read in collision: deferred (cpu_pend_valid latches).
// Do NOT pulse rd_pending — iop_rd_valid stays low until the
// deferred read finally fires (serve_cpu_def).
// 4. Deferred RAM read finally serviced (serve_cpu_def): pulse
// rd_pending with rd_was_ram=1; the data arrives next cycle.
always_ff @(posedge clk) begin
if (!rst_n) begin
rd_pending <= 1'b0;
rd_was_ram <= 1'b0;
rd_was_sif <= 1'b0;
rd_was_dmac <= 1'b0;
rd_was_intc <= 1'b0;
rd_was_bios <= 1'b0;
rd_was_pad <= 1'b0;
end else if (serve_cpu_def) begin
// Deferred RAM read serviced this cycle — data next cycle.
rd_pending <= 1'b1;
rd_was_ram <= 1'b1;
rd_was_sif <= 1'b0;
rd_was_dmac <= 1'b0;
rd_was_intc <= 1'b0;
rd_was_bios <= 1'b0;
rd_was_pad <= 1'b0;
end else if (iop_rd_en && !(rd_is_ram && cpu_dma_collision)) begin
// Normal read path: live RAM read with no collision, OR
// any non-RAM CPU read (decoded by rd_is_*, routed via
// independent paths so no arbitration concern).
rd_pending <= 1'b1;
rd_was_ram <= rd_is_ram;
rd_was_sif <= rd_is_sif;
rd_was_dmac <= rd_is_dmac;
rd_was_intc <= rd_is_intc;
rd_was_bios <= rd_is_bios;
rd_was_pad <= rd_is_pad;
end else begin
// Collision-deferred OR idle cycle. CPU waits for deferred
// read to fire; iop_rd_valid stays low.
rd_pending <= 1'b0;
end
end
assign iop_rd_valid = rd_pending;
assign iop_rd_data = rd_was_ram ? ram_rd_data
: rd_was_sif ? sif_rd_data
: rd_was_dmac ? iop_dmac_rd_data
: rd_was_intc ? iop_intc_rd_data
: rd_was_bios ? bios_rd_data
: rd_was_pad ? pad_rd_data
: 32'hDEADBEEF;
// ------------------------------------------------------------------
// DMA read response pipeline (separate from CPU pipeline). Ch261 —
// CPU+DMA collision is now handled cleanly by the deferred-CPU-slot
// above: DMA wins the port immediately, CPU's read is latched and
// serviced on the next non-DMA cycle. DMA always gets its own word
// on its expected timing; no silent corruption.
// ------------------------------------------------------------------
logic dma_rd_pending;
logic dma_rd_was_ram;
always_ff @(posedge clk) begin
if (!rst_n) begin
dma_rd_pending <= 1'b0;
dma_rd_was_ram <= 1'b0;
end else begin
dma_rd_pending <= dma_rd_en;
if (dma_rd_en) dma_rd_was_ram <= dma_rd_is_ram;
end
end
assign dma_rd_valid = dma_rd_pending;
assign dma_rd_data = dma_rd_was_ram ? ram_rd_data : 32'hDEADBEEF;
// ------------------------------------------------------------------
// Trace emission — one event per cycle. Priority:
// CPU read > CPU write > DMA read > bridge write
// Masters are expected to be sequenced in TBs; priority is defensive
// for the rare collision case.
// ------------------------------------------------------------------
always_ff @(posedge clk) begin
if (!rst_n) begin
ev_valid <= 1'b0;
ev_subsys <= SUBSYS_IOP;
ev_event <= EV_READ;
ev_arg0 <= 64'd0;
ev_arg1 <= 64'd0;
ev_arg2 <= 64'd0;
ev_arg3 <= 64'd0;
ev_flags <= 32'd0;
end else if (iop_rd_en) begin
ev_valid <= 1'b1;
ev_subsys <= SUBSYS_IOP;
if (rd_is_ram) begin
ev_event <= EV_READ;
ev_arg1 <= 64'd0;
ev_arg3 <= REGION_IOP_RAM;
end else if (rd_is_sif) begin
ev_event <= EV_READ;
ev_arg1 <= 64'd0;
ev_arg3 <= REGION_SIF_REGS;
end else if (rd_is_dmac) begin
ev_event <= EV_READ;
ev_arg1 <= 64'd0;
ev_arg3 <= REGION_IOP_DMAC;
end else if (rd_is_intc) begin
ev_event <= EV_READ;
ev_arg1 <= 64'd0;
ev_arg3 <= REGION_IOP_INTC;
end else if (rd_is_bios) begin
ev_event <= EV_READ;
ev_arg1 <= 64'd0;
ev_arg3 <= REGION_IOP_BIOS;
end else if (rd_is_pad) begin
ev_event <= EV_READ;
ev_arg1 <= 64'd0;
ev_arg3 <= REGION_PAD_IO;
end else begin
ev_event <= EV_UNMAPPED;
ev_arg1 <= 64'hDEADBEEF;
ev_arg3 <= REGION_UNMAPPED;
end
ev_arg0 <= {32'd0, iop_rd_addr};
ev_arg2 <= {56'd0, master_id};
ev_flags <= 32'd0;
end else if (iop_wr_en) begin
ev_valid <= 1'b1;
ev_subsys <= SUBSYS_IOP;
if (cpu_wr_is_ram) begin
ev_event <= EV_WRITE;
ev_arg3 <= REGION_IOP_RAM;
end else if (cpu_wr_is_sif) begin
ev_event <= EV_WRITE;
ev_arg3 <= REGION_SIF_REGS;
end else if (cpu_wr_is_dmac) begin
ev_event <= EV_WRITE;
ev_arg3 <= REGION_IOP_DMAC;
end else if (cpu_wr_is_intc) begin
ev_event <= EV_WRITE;
ev_arg3 <= REGION_IOP_INTC;
end else if (cpu_wr_is_pad) begin
ev_event <= EV_WRITE;
ev_arg3 <= REGION_PAD_IO;
end else begin
ev_event <= EV_UNMAPPED;
ev_arg3 <= REGION_UNMAPPED;
end
ev_arg0 <= {32'd0, iop_wr_addr};
ev_arg1 <= {32'd0, iop_wr_data};
ev_arg2 <= {56'd0, master_id};
ev_flags <= 32'h0000_0001;
end else if (dma_rd_en) begin
ev_valid <= 1'b1;
ev_subsys <= SUBSYS_IOP;
ev_event <= dma_rd_is_ram ? EV_READ : EV_UNMAPPED;
ev_arg0 <= {32'd0, dma_rd_addr};
ev_arg1 <= dma_rd_is_ram ? 64'd0 : 64'hDEADBEEF;
ev_arg2 <= {56'd0, dma_master_id};
ev_arg3 <= dma_rd_is_ram ? REGION_IOP_RAM : REGION_UNMAPPED;
ev_flags <= 32'd0;
end else if (bridge_wr_en) begin
ev_valid <= 1'b1;
ev_subsys <= SUBSYS_IOP;
ev_event <= bridge_wr_is_ram ? EV_WRITE : EV_UNMAPPED;
ev_arg0 <= {32'd0, bridge_wr_addr};
ev_arg1 <= {32'd0, bridge_wr_data};
ev_arg2 <= {56'd0, bridge_master_id};
ev_arg3 <= bridge_wr_is_ram ? REGION_IOP_RAM : REGION_UNMAPPED;
ev_flags <= 32'h0000_0001;
end else begin
ev_valid <= 1'b0;
end
end
endmodule : iop_memory_map_stub