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>
154 lines
5.5 KiB
Systemverilog
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
|