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>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
// 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
|
||||
Reference in New Issue
Block a user