Files
retroDE_ps2/sim/tb/iop/tb_iop_ram_stub.sv
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

195 lines
6.4 KiB
Systemverilog

// retroDE_ps2 — tb_iop_ram_stub
//
// Unit test for iop_ram_stub. Mirrors the style of other primitive tests
// (bios_rom_stub / ee_ram_stub) — drive reads and writes from the TB,
// verify round-trip data, check trace emission and region/master tagging.
//
// Scenarios:
// 1. Post-reset read returns zero (zero-init)
// 2. Byte-wide writes with partial byte enables
// 3. Word-wide writes
// 4. Read-back matches write pattern
// 5. Trace events tagged as SUBSYS_IOP with region=IOP_RAM and
// caller-provided master_id
`timescale 1ns/1ps
module tb_iop_ram_stub;
localparam int SIZE_BYTES = 4 * 1024;
localparam int ADDR_W = $clog2(SIZE_BYTES);
logic clk;
logic rst_n;
initial clk = 1'b0;
always #5 clk = ~clk;
logic rd_en;
logic [ADDR_W-1:0] rd_addr;
logic [31:0] rd_data;
logic rd_valid;
logic wr_en;
logic [ADDR_W-1:0] wr_addr;
logic [31:0] wr_data;
logic [3:0] wr_be;
logic [7:0] master_id;
logic ev_valid;
trace_pkg::subsys_e ev_subsys;
trace_pkg::event_e ev_event;
logic [63:0] ev_arg0, ev_arg1, ev_arg2, ev_arg3;
logic [31:0] ev_flags;
iop_ram_stub #(.SIZE_BYTES(SIZE_BYTES)) u_iop_ram (
.clk(clk), .rst_n(rst_n),
.rd_en(rd_en), .rd_addr(rd_addr),
.rd_data(rd_data), .rd_valid(rd_valid),
.wr_en(wr_en), .wr_addr(wr_addr),
.wr_data(wr_data), .wr_be(wr_be),
.master_id(master_id),
.ev_valid(ev_valid), .ev_subsys(ev_subsys),
.ev_event(ev_event),
.ev_arg0(ev_arg0), .ev_arg1(ev_arg1),
.ev_arg2(ev_arg2), .ev_arg3(ev_arg3),
.ev_flags(ev_flags)
);
trace_sink_stub #(.FILENAME("iop_ram.trace"), .SINK_LABEL("iop_ram"))
u_trace (.clk(clk), .rst_n(rst_n),
.ev_valid(ev_valid), .ev_subsys(ev_subsys),
.ev_event(ev_event), .ev_arg0(ev_arg0),
.ev_arg1(ev_arg1), .ev_arg2(ev_arg2),
.ev_arg3(ev_arg3), .ev_flags(ev_flags));
// ------------------------------------------------------------------
// Counters
// ------------------------------------------------------------------
int iop_reads;
int iop_writes;
int iop_region_hits;
int errors;
initial begin
iop_reads = 0;
iop_writes = 0;
iop_region_hits = 0;
errors = 0;
end
always_ff @(posedge clk) begin
if (rst_n && ev_valid && ev_subsys == trace_pkg::SUBSYS_IOP) begin
if (ev_event == trace_pkg::EV_READ) iop_reads <= iop_reads + 1;
if (ev_event == trace_pkg::EV_WRITE) iop_writes <= iop_writes + 1;
if (ev_arg3[7:0] == 8'd2) iop_region_hits <= iop_region_hits + 1;
end
end
// ------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------
task automatic do_write(input logic [ADDR_W-1:0] addr,
input logic [31:0] data,
input logic [3:0] be);
@(negedge clk);
wr_en = 1'b1;
wr_addr = addr;
wr_data = data;
wr_be = be;
@(negedge clk);
wr_en = 1'b0;
wr_addr = '0;
wr_data = 32'd0;
wr_be = 4'd0;
endtask
task automatic do_read_expect(input logic [ADDR_W-1:0] addr,
input logic [31:0] expected,
input string label);
@(negedge clk);
rd_en = 1'b1;
rd_addr = addr;
@(negedge clk);
rd_en = 1'b0;
rd_addr = '0;
if (rd_data !== expected || rd_valid !== 1'b1) begin
$error("[tb_iop_ram_stub] read %s: got 0x%08h valid=%0b expected 0x%08h",
label, rd_data, rd_valid, expected);
errors = errors + 1;
end
endtask
// ------------------------------------------------------------------
// Stimulus
// ------------------------------------------------------------------
logic [31:0] before_read;
initial begin
rst_n = 1'b0;
rd_en = 1'b0;
rd_addr = '0;
wr_en = 1'b0;
wr_addr = '0;
wr_data = 32'd0;
wr_be = 4'd0;
master_id = 8'd0;
repeat (4) @(posedge clk);
rst_n = 1'b1;
repeat (2) @(posedge clk);
// 1. Zero-init check
do_read_expect({ADDR_W{1'b0}}, 32'h0000_0000, "addr 0x0 after reset");
// 2. Word-wide write + read-back
master_id = 8'd0; // TB direct
do_write({ADDR_W{1'b0}}, 32'hCAFE_BABE, 4'b1111);
do_read_expect({ADDR_W{1'b0}}, 32'hCAFE_BABE, "word write round-trip");
// 3. Partial byte-enable write: preserve upper half, overwrite lower
// (start from known word value, write only be=0x3 with new data)
do_write({ADDR_W{1'b0}}, 32'h1234_0000, 4'b0011);
do_read_expect({ADDR_W{1'b0}}, 32'hCAFE_0000, "partial byte-enable low");
// 4. Non-zero master_id — IOP CPU hypothetical
master_id = 8'd2;
do_write(ADDR_W'(32'h0010), 32'hDEAD_BEEF, 4'b1111);
do_read_expect(ADDR_W'(32'h0010), 32'hDEAD_BEEF, "IOP-tagged write");
// 5. Address-distinct read — confirm storage independence
do_read_expect(ADDR_W'(32'h0020), 32'h0000_0000, "untouched slot");
repeat (4) @(posedge clk);
// ------------------------------------------------------------------
$display("[tb_iop_ram_stub] iop_reads=%0d iop_writes=%0d iop_region_hits=%0d errors=%0d",
iop_reads, iop_writes, iop_region_hits, errors);
if (iop_reads < 4) $error("expected >= 4 IOP read events, got %0d", iop_reads);
if (iop_writes < 3) $error("expected >= 3 IOP write events, got %0d", iop_writes);
if (iop_region_hits != iop_reads + iop_writes)
$error("region tag inconsistent: reads+writes=%0d region_hits=%0d",
iop_reads + iop_writes, iop_region_hits);
if (errors == 0 && iop_reads >= 4 && iop_writes >= 3 &&
iop_region_hits == iop_reads + iop_writes)
$display("[tb_iop_ram_stub] PASS");
else
$display("[tb_iop_ram_stub] FAIL");
$finish;
end
initial begin
#200000;
$error("[tb_iop_ram_stub] timeout");
$finish;
end
endmodule : tb_iop_ram_stub