ec82764bef
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>
653 lines
29 KiB
Systemverilog
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
|