// retroDE_ps2 — ee_ram_stub // // Tiny addressable EE-RAM block for Wave 2.5. Provides the first real memory // source for DMAC-backed transfers. Not the final 32 MiB main-RAM model — // see docs/wave25_memory_backed_dma_plan.md for explicit scope. // // Contract refs: // docs/wave25_memory_backed_dma_plan.md (ee_ram_stub scope) // docs/contracts/memory.md (memory subsystem ownership) // // Interface: // - 128-bit wide data path, qword-aligned addressing (low 4 bits ignored). // - One-cycle read latency: rd_en on cycle N → rd_data / rd_valid on N+1. // - Write port has per-byte enables (wr_be[15:0]). // - Optional `$readmemh` preload via IMAGE_FILE parameter. // // Trace: // Emits MEM READ / MEM WRITE events one cycle after the request, matching // the existing MEM schema. master_id is a caller-provided input (8 bits); // the integration TB tags reads as 1 (DMAC) while TB-initiated writes are // tagged 0. Any downstream master can drive its own id without RAM-side // changes. // // Trace payload: // MEM READ arg0=addr arg1=data_lo arg2=master_id arg3=region_id // MEM WRITE arg0=addr arg1=data_lo arg2=master_id arg3=region_id // master_id : caller-provided (e.g. 0 = TB direct, 1 = DMAC) // region_id : 1 = EE_RAM (constant for this module) // flags bit 0: 1 = write, 0 = read `timescale 1ns/1ps module ee_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 (qword-aligned) input logic rd_en, input logic [$clog2(SIZE_BYTES)-1:0] rd_addr, output logic [127:0] rd_data, output logic rd_valid, // Write port (qword-aligned; wr_be provides per-byte granularity) input logic wr_en, input logic [$clog2(SIZE_BYTES)-1:0] wr_addr, input logic [127:0] wr_data, input logic [15:0] wr_be, // Optional caller-provided master id for trace attribution. Default tie // to 8'd0 (TB direct) if the caller doesn't drive; DMAC drives 8'd1. 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 QWORD_COUNT = SIZE_BYTES / 16; localparam int QW_INDEX_WIDTH = $clog2(QWORD_COUNT); localparam logic [63:0] REGION_EE_RAM = 64'd1; logic [127:0] mem [0:QWORD_COUNT-1]; initial begin if (IMAGE_FILE != "") begin $display("[ee_ram_stub] loading image: %0s", IMAGE_FILE); $readmemh(IMAGE_FILE, mem); end else begin for (int i = 0; i < QWORD_COUNT; i++) mem[i] = 128'd0; $display("[ee_ram_stub] zero-initialised (%0d qwords / %0d bytes)", QWORD_COUNT, SIZE_BYTES); end end logic [QW_INDEX_WIDTH-1:0] rd_qw_idx; logic [QW_INDEX_WIDTH-1:0] wr_qw_idx; assign rd_qw_idx = rd_addr[ADDR_WIDTH-1:4]; assign wr_qw_idx = wr_addr[ADDR_WIDTH-1:4]; // ------------------------------------------------------------------ // Read + write (one-cycle latency). Reads and writes to the same // address in the same cycle are not expected in Wave 2.5; if they // occur, the read sees pre-write data (standard register-file // semantics). // ------------------------------------------------------------------ always_ff @(posedge clk) begin if (!rst_n) begin rd_data <= 128'd0; rd_valid <= 1'b0; end else begin rd_valid <= rd_en; if (rd_en) rd_data <= mem[rd_qw_idx]; if (wr_en) begin for (int b = 0; b < 16; b++) begin if (wr_be[b]) mem[wr_qw_idx][b*8 +: 8] <= wr_data[b*8 +: 8]; end end end end // ------------------------------------------------------------------ // Trace emission: one event per cycle, read wins over write on the // unlikely same-cycle collision (single-port RAM would not see that // anyway). Registered so ev_valid lines up with rd_valid / wr_ack // boundaries. // ------------------------------------------------------------------ logic [127:0] rd_data_sampled; always_ff @(posedge clk) begin if (!rst_n) begin ev_valid <= 1'b0; ev_subsys <= SUBSYS_MEM; ev_event <= EV_READ; ev_arg0 <= 64'd0; ev_arg1 <= 64'd0; ev_arg2 <= 64'd0; ev_arg3 <= 64'd0; ev_flags <= 32'd0; rd_data_sampled <= 128'd0; end else begin // The actual fetched data is available one cycle after rd_en. // Sample it in parallel with rd_data so the trace fires on the // same edge as rd_valid. rd_data_sampled <= mem[rd_qw_idx]; if (rd_en) begin ev_valid <= 1'b1; ev_subsys <= SUBSYS_MEM; ev_event <= EV_READ; ev_arg0 <= {{(64-ADDR_WIDTH){1'b0}}, rd_addr}; ev_arg1 <= mem[rd_qw_idx][63:0]; ev_arg2 <= {56'd0, master_id}; ev_arg3 <= REGION_EE_RAM; ev_flags <= 32'd0; end else if (wr_en) begin ev_valid <= 1'b1; ev_subsys <= SUBSYS_MEM; ev_event <= EV_WRITE; ev_arg0 <= {{(64-ADDR_WIDTH){1'b0}}, wr_addr}; ev_arg1 <= wr_data[63:0]; ev_arg2 <= {56'd0, master_id}; ev_arg3 <= REGION_EE_RAM; ev_flags <= 32'h0000_0001; // bit 0 = write end else begin ev_valid <= 1'b0; end end end endmodule : ee_ram_stub