// 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