Files
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

154 lines
5.5 KiB
Systemverilog

// retroDE_ps2 — iop_ram_stub
//
// First narrow IOP-side primitive. 32-bit IOP-RAM stub, architecturally
// honest to the IOP's R3000-class 32-bit bus. NOT an IOP CPU — this is
// pure memory. No fetch, no execution, no BIOS bring-up. Future IOP-side
// work (fetch stub, IOP memory map, DMAC routing) can build on top of it.
//
// Contract refs:
// docs/contracts/iop.md (IOP-local RAM/I/O decode)
// docs/contracts/memory.md (2 MiB IOP RAM in the PS2 memory map)
//
// Scope:
// - read/write 32-bit data
// - byte-enable granularity on writes
// - one-cycle read latency (matches existing stub ecosystem)
// - caller-provided master_id for trace attribution
// - trace events tagged as SUBSYS_IOP so IOP-side memory traffic is
// distinct from EE MEM events even when both are active
//
// Explicit non-goals (Wave 3 IOP first step):
// - IOP CPU execution
// - full 2 MiB sizing (default is 16 KiB — plenty for stub tests)
// - integration into any IOP memory map yet
// - connection to SIF receive path (intentional: kept independent so
// future bridging is explicit, not accidental)
//
// Trace payload schema:
// IOP READ arg0=addr arg1=data arg2=master_id arg3=region_id
// IOP WRITE arg0=addr arg1=data arg2=master_id arg3=region_id
// master_id : caller-provided (e.g. 0 = TB direct, future: 2 = IOP CPU,
// 3 = SIF bridge, etc.)
// region_id : 2 = IOP_RAM (constant for this module)
// flags[0] : 1 = write, 0 = read
`timescale 1ns/1ps
module iop_ram_stub
import trace_pkg::*;
#(
parameter int SIZE_BYTES = 16 * 1024, // 16 KiB default
parameter string IMAGE_FILE = ""
) (
input logic clk,
input logic rst_n,
// Read port
input logic rd_en,
input logic [$clog2(SIZE_BYTES)-1:0] rd_addr,
output logic [31:0] rd_data,
output logic rd_valid,
// Write port
input logic wr_en,
input logic [$clog2(SIZE_BYTES)-1:0] wr_addr,
input logic [31:0] wr_data,
input logic [3:0] wr_be,
// Caller-provided master id for trace attribution
input logic [7:0] master_id,
// 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 int ADDR_WIDTH = $clog2(SIZE_BYTES);
localparam int WORD_COUNT = SIZE_BYTES / 4;
localparam int WORD_INDEX_WIDTH = ADDR_WIDTH - 2;
localparam logic [63:0] REGION_IOP_RAM = 64'd2;
logic [31:0] mem [0:WORD_COUNT-1];
initial begin
if (IMAGE_FILE != "") begin
$display("[iop_ram_stub] loading image: %0s", IMAGE_FILE);
$readmemh(IMAGE_FILE, mem);
end else begin
for (int i = 0; i < WORD_COUNT; i++) mem[i] = 32'd0;
$display("[iop_ram_stub] zero-initialised (%0d words / %0d bytes)",
WORD_COUNT, SIZE_BYTES);
end
end
logic [WORD_INDEX_WIDTH-1:0] rd_word_idx;
logic [WORD_INDEX_WIDTH-1:0] wr_word_idx;
assign rd_word_idx = rd_addr[ADDR_WIDTH-1:2];
assign wr_word_idx = wr_addr[ADDR_WIDTH-1:2];
// ------------------------------------------------------------------
// Read + write (one-cycle latency)
// ------------------------------------------------------------------
always_ff @(posedge clk) begin
if (!rst_n) begin
rd_data <= 32'd0;
rd_valid <= 1'b0;
end else begin
rd_valid <= rd_en;
if (rd_en) rd_data <= mem[rd_word_idx];
if (wr_en) begin
for (int b = 0; b < 4; b++) begin
if (wr_be[b]) mem[wr_word_idx][b*8 +: 8] <= wr_data[b*8 +: 8];
end
end
end
end
// ------------------------------------------------------------------
// Trace emission — read wins on same-cycle collision (single-port
// RAM wouldn't see that anyway in Wave 3).
// ------------------------------------------------------------------
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 (rd_en) begin
ev_valid <= 1'b1;
ev_subsys <= SUBSYS_IOP;
ev_event <= EV_READ;
ev_arg0 <= {{(64-ADDR_WIDTH){1'b0}}, rd_addr};
ev_arg1 <= {32'd0, mem[rd_word_idx]};
ev_arg2 <= {56'd0, master_id};
ev_arg3 <= REGION_IOP_RAM;
ev_flags <= 32'd0;
end else if (wr_en) begin
ev_valid <= 1'b1;
ev_subsys <= SUBSYS_IOP;
ev_event <= EV_WRITE;
ev_arg0 <= {{(64-ADDR_WIDTH){1'b0}}, wr_addr};
ev_arg1 <= {32'd0, wr_data};
ev_arg2 <= {56'd0, master_id};
ev_arg3 <= REGION_IOP_RAM;
ev_flags <= 32'h0000_0001;
end else begin
ev_valid <= 1'b0;
end
end
endmodule : iop_ram_stub