Files
retroDE_ps2/sim/tb/integration/tb_sif_iop_bridge_smoke.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

500 lines
20 KiB
Systemverilog
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// retroDE_ps2 — tb_sif_iop_bridge_smoke
//
// First bridge milestone: EE-sourced SIF DMA landing in architecturally
// visible IOP RAM via the IOP memory map. Explicitly does NOT yet exercise
// iop_fetch_stub reading the landed payload — that's the follow-up
// milestone. Scope here is limited to proving the write side:
//
// ee_ram_stub (TB preload)
// ↓ (via ee_memory_map_stub, DMAC ch5 fetch)
// dmac_reg_stub (ch5)
// ↓ ep_*
// sif_dma_iop_ram_bridge_stub (128-bit → 4× 32-bit)
// ↓ bridge_wr_*
// iop_memory_map_stub (routes physical RAM-window writes)
// ↓ ram_wr_*
// iop_ram_stub (landing site)
//
// TB then reads iop_ram_stub back through the IOP map's CPU-side read
// port to verify:
// - 4 words per qword landed at consecutive addresses
// - little-endian unpacking (qword[31:0] at lowest address)
// - ordering across multiple qwords is preserved
// - no drops, no duplicates
// - map trace tags bridge writes with master_id=3 (bridge) and
// region=IOP_RAM
`timescale 1ns/1ps
module tb_sif_iop_bridge_smoke;
logic clk;
logic rst_n;
initial clk = 1'b0;
always #5 clk = ~clk;
localparam int QWC_VAL = 2;
localparam int EE_RAM_BYTES = 4 * 1024;
localparam int EE_RAM_ADDR_W = $clog2(EE_RAM_BYTES);
localparam int IOP_RAM_BYTES = 4 * 1024;
localparam int IOP_RAM_ADDR_W = $clog2(IOP_RAM_BYTES);
localparam logic [31:0] SOURCE_MADR = 32'h0000_0600; // EE-side src
localparam logic [31:0] DEST_BASE_ADDR = 32'h0000_0400; // IOP RAM dest
// ------------------------------------------------------------------
// EE-side RAM (source)
// ------------------------------------------------------------------
logic ee_ram_rd_en;
logic [EE_RAM_ADDR_W-1:0] ee_ram_rd_addr;
logic [127:0] ee_ram_rd_data;
logic ee_ram_rd_valid;
logic ee_ram_wr_en;
logic [EE_RAM_ADDR_W-1:0] ee_ram_wr_addr;
logic [127:0] ee_ram_wr_data;
logic [15:0] ee_ram_wr_be;
logic [7:0] ee_ram_master_id;
logic ee_ram_ev_valid;
trace_pkg::subsys_e ee_ram_ev_subsys;
trace_pkg::event_e ee_ram_ev_event;
logic [63:0] ee_ram_ev_arg0, ee_ram_ev_arg1, ee_ram_ev_arg2, ee_ram_ev_arg3;
logic [31:0] ee_ram_ev_flags;
ee_ram_stub #(.SIZE_BYTES(EE_RAM_BYTES)) u_ee_ram (
.clk(clk), .rst_n(rst_n),
.rd_en(ee_ram_rd_en), .rd_addr(ee_ram_rd_addr),
.rd_data(ee_ram_rd_data), .rd_valid(ee_ram_rd_valid),
.wr_en(ee_ram_wr_en), .wr_addr(ee_ram_wr_addr),
.wr_data(ee_ram_wr_data), .wr_be(ee_ram_wr_be),
.master_id(ee_ram_master_id),
.ev_valid(ee_ram_ev_valid), .ev_subsys(ee_ram_ev_subsys),
.ev_event(ee_ram_ev_event),
.ev_arg0(ee_ram_ev_arg0), .ev_arg1(ee_ram_ev_arg1),
.ev_arg2(ee_ram_ev_arg2), .ev_arg3(ee_ram_ev_arg3),
.ev_flags(ee_ram_ev_flags)
);
assign ee_ram_master_id = ee_ram_rd_en ? 8'd1 : 8'd0;
// ------------------------------------------------------------------
// EE DMAC (channel 5) + EE memory map
// ------------------------------------------------------------------
logic dmac_reg_wr_en;
logic [7:0] dmac_reg_offset;
logic [31:0] dmac_reg_wr_data;
logic dmac_mem_rd_en;
logic [31:0] dmac_mem_rd_addr;
logic [127:0] map_to_dmac_rd_data;
logic map_to_dmac_rd_valid;
logic dmac_ep_valid;
logic [127:0] dmac_ep_data;
logic dmac_ep_last;
logic dmac_ep_ready;
logic dmac_ev_valid;
trace_pkg::subsys_e dmac_ev_subsys;
trace_pkg::event_e dmac_ev_event;
logic [63:0] dmac_ev_arg0, dmac_ev_arg1, dmac_ev_arg2, dmac_ev_arg3;
logic [31:0] dmac_ev_flags;
dmac_reg_stub #(.CHANNEL(4'd5), .PATH_ID(4'd5)) u_dmac (
.clk(clk), .rst_n(rst_n),
.reg_wr_en(dmac_reg_wr_en), .reg_offset(dmac_reg_offset),
.reg_wr_data(dmac_reg_wr_data),
.reg_rd_en(1'b0), .reg_rd_data(), .reg_rd_valid(),
.mem_rd_en(dmac_mem_rd_en), .mem_rd_addr(dmac_mem_rd_addr),
.mem_rd_data(map_to_dmac_rd_data), .mem_rd_valid(map_to_dmac_rd_valid),
.ep_valid(dmac_ep_valid), .ep_data(dmac_ep_data),
.ep_last(dmac_ep_last), .ep_ready(dmac_ep_ready),
.irq_completion_o(),
.ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys),
.ev_event(dmac_ev_event),
.ev_arg0(dmac_ev_arg0), .ev_arg1(dmac_ev_arg1),
.ev_arg2(dmac_ev_arg2), .ev_arg3(dmac_ev_arg3),
.ev_flags(dmac_ev_flags)
);
logic ee_map_ram_rd_en;
logic [24:0] ee_map_ram_rd_addr;
ee_memory_map_stub u_ee_map (
.clk(clk), .rst_n(rst_n),
.ee_rd_en(1'b0), .ee_rd_addr(32'd0),
.ee_rd_data(), .ee_rd_valid(),
.ee_wr_en(1'b0), .ee_wr_addr(32'd0),
.ee_wr_data(32'd0), .ee_wr_be(4'd0),
.dmac_rd_en(dmac_mem_rd_en), .dmac_rd_addr(dmac_mem_rd_addr),
.dmac_rd_data(map_to_dmac_rd_data),
.dmac_rd_valid(map_to_dmac_rd_valid),
.bios_rd_en(), .bios_rd_addr(),
.bios_rd_data(32'd0), .bios_rd_valid(1'b0),
.ram_rd_en(ee_map_ram_rd_en), .ram_rd_addr(ee_map_ram_rd_addr),
.ram_rd_data(ee_ram_rd_data), .ram_rd_valid(ee_ram_rd_valid),
.bridge_wr_en(1'b0), .bridge_wr_addr(32'd0),
.bridge_wr_data(128'd0), .bridge_wr_be(16'd0),
.bridge_master_id(8'd0),
.ram_wr_en(), .ram_wr_addr(), .ram_wr_data(),
.ram_wr_be(), .ram_master_id(),
.ee_dmac_ch2_wr_en(), .ee_dmac_ch2_wr_addr(), .ee_dmac_ch2_wr_data(),
.ee_dmac_ch2_rd_en(), .ee_dmac_ch2_rd_addr(),
.ee_dmac_ch2_rd_data(32'd0), .ee_dmac_ch2_rd_valid(1'b0),
.ee_intc_wr_en(), .ee_intc_wr_addr(), .ee_intc_wr_data(),
.ee_intc_rd_en(), .ee_intc_rd_addr(),
.ee_intc_rd_data(32'd0), .ee_intc_rd_valid(1'b0),
.ee_misc_mmio_wr_en(), .ee_misc_mmio_wr_addr(), .ee_misc_mmio_wr_data(), .ee_misc_mmio_wr_be(),
.ee_misc_mmio_rd_en(), .ee_misc_mmio_rd_addr(),
.ee_misc_mmio_rd_data(32'd0), .ee_misc_mmio_rd_valid(1'b0),
.ee_biu_wr_en(), .ee_biu_wr_addr(), .ee_biu_wr_data(), .ee_biu_wr_be(),
.ee_biu_rd_en(), .ee_biu_rd_addr(),
.ee_biu_rd_data(32'd0), .ee_biu_rd_valid(1'b0),
.ev_valid(), .ev_subsys(), .ev_event(),
.ev_arg0(), .ev_arg1(), .ev_arg2(), .ev_arg3(), .ev_flags()
);
assign ee_ram_rd_en = ee_map_ram_rd_en;
assign ee_ram_rd_addr = ee_map_ram_rd_addr[EE_RAM_ADDR_W-1:0];
// ------------------------------------------------------------------
// SIF → IOP-RAM bridge
// ------------------------------------------------------------------
logic bridge_wr_en;
logic [31:0] bridge_wr_addr;
logic [31:0] bridge_wr_data;
logic [3:0] bridge_wr_be;
logic [7:0] bridge_master_id;
sif_dma_iop_ram_bridge_stub
#(.DEST_BASE_ADDR(DEST_BASE_ADDR), .MASTER_ID(8'd3))
u_bridge (
.clk(clk), .rst_n(rst_n),
.in_valid(dmac_ep_valid), .in_data(dmac_ep_data),
.in_last(dmac_ep_last), .in_ready(dmac_ep_ready),
.bridge_wr_en(bridge_wr_en), .bridge_wr_addr(bridge_wr_addr),
.bridge_wr_data(bridge_wr_data), .bridge_wr_be(bridge_wr_be),
.bridge_master_id(bridge_master_id)
);
// ------------------------------------------------------------------
// IOP map + IOP RAM
// ------------------------------------------------------------------
// TB-side CPU port: used to READ the landed payload for verification.
logic iop_rd_en;
logic [31:0] iop_rd_addr;
logic [31:0] iop_rd_data;
logic iop_rd_valid;
logic iop_map_ram_rd_en;
logic [20:0] iop_map_ram_rd_addr;
logic [31:0] iop_ram_rd_data;
logic iop_ram_rd_valid;
logic iop_map_ram_wr_en;
logic [20:0] iop_map_ram_wr_addr;
logic [31:0] iop_map_ram_wr_data;
logic [3:0] iop_map_ram_wr_be;
logic [7:0] iop_map_ram_master_id;
logic iop_map_ev_valid;
trace_pkg::subsys_e iop_map_ev_subsys;
trace_pkg::event_e iop_map_ev_event;
logic [63:0] iop_map_ev_arg0, iop_map_ev_arg1, iop_map_ev_arg2, iop_map_ev_arg3;
logic [31:0] iop_map_ev_flags;
iop_memory_map_stub u_iop_map (
.clk(clk), .rst_n(rst_n),
// CPU-side read (TB verification)
.iop_rd_en(iop_rd_en), .iop_rd_addr(iop_rd_addr),
.iop_rd_data(iop_rd_data), .iop_rd_valid(iop_rd_valid),
// No CPU writes this pass
.iop_wr_en(1'b0), .iop_wr_addr(32'd0),
.iop_wr_data(32'd0), .iop_wr_be(4'd0),
.master_id(8'd2), // IOP_CPU (read tag)
// Bridge write port — wired
.bridge_wr_en(bridge_wr_en), .bridge_wr_addr(bridge_wr_addr),
.bridge_wr_data(bridge_wr_data), .bridge_wr_be(bridge_wr_be),
.bridge_master_id(bridge_master_id),
// DMA read-master port — unused by this TB
.dma_rd_en(1'b0), .dma_rd_addr(32'd0),
.dma_master_id(8'd0),
.dma_rd_data(), .dma_rd_valid(),
// SIF register-shell port — unused by this TB
.sif_rd_en(), .sif_rd_addr(),
.sif_rd_data(32'd0), .sif_rd_valid(1'b0),
.sif_wr_en(), .sif_wr_addr(), .sif_wr_data(),
// IOP DMAC port — unused by this TB
.iop_dmac_rd_en(), .iop_dmac_rd_addr(),
.iop_dmac_rd_data(32'd0), .iop_dmac_rd_valid(1'b0),
.iop_dmac_wr_en(), .iop_dmac_wr_addr(), .iop_dmac_wr_data(),
// IOP INTC port — unused by this TB
.iop_intc_rd_en(), .iop_intc_rd_addr(),
.iop_intc_rd_data(32'd0), .iop_intc_rd_valid(1'b0),
.iop_intc_wr_en(), .iop_intc_wr_addr(), .iop_intc_wr_data(),
.input_p1(32'd0), .input_p2(32'd0),
// BIOS ROM port — unused by this TB
.bios_rd_en(), .bios_rd_addr(),
.bios_rd_data(32'd0), .bios_rd_valid(1'b0),
// Downstream to IOP RAM
.ram_rd_en(iop_map_ram_rd_en), .ram_rd_addr(iop_map_ram_rd_addr),
.ram_rd_data(iop_ram_rd_data), .ram_rd_valid(iop_ram_rd_valid),
.ram_wr_en(iop_map_ram_wr_en), .ram_wr_addr(iop_map_ram_wr_addr),
.ram_wr_data(iop_map_ram_wr_data), .ram_wr_be(iop_map_ram_wr_be),
.ram_master_id(iop_map_ram_master_id),
.ev_valid(iop_map_ev_valid), .ev_subsys(iop_map_ev_subsys),
.ev_event(iop_map_ev_event),
.ev_arg0(iop_map_ev_arg0), .ev_arg1(iop_map_ev_arg1),
.ev_arg2(iop_map_ev_arg2), .ev_arg3(iop_map_ev_arg3),
.ev_flags(iop_map_ev_flags)
);
logic iop_ram_ev_valid;
trace_pkg::subsys_e iop_ram_ev_subsys;
trace_pkg::event_e iop_ram_ev_event;
logic [63:0] iop_ram_ev_arg0, iop_ram_ev_arg1, iop_ram_ev_arg2, iop_ram_ev_arg3;
logic [31:0] iop_ram_ev_flags;
iop_ram_stub #(.SIZE_BYTES(IOP_RAM_BYTES)) u_iop_ram (
.clk(clk), .rst_n(rst_n),
.rd_en(iop_map_ram_rd_en), .rd_addr(iop_map_ram_rd_addr[IOP_RAM_ADDR_W-1:0]),
.rd_data(iop_ram_rd_data), .rd_valid(iop_ram_rd_valid),
.wr_en(iop_map_ram_wr_en), .wr_addr(iop_map_ram_wr_addr[IOP_RAM_ADDR_W-1:0]),
.wr_data(iop_map_ram_wr_data), .wr_be(iop_map_ram_wr_be),
.master_id(iop_map_ram_master_id),
.ev_valid(iop_ram_ev_valid), .ev_subsys(iop_ram_ev_subsys),
.ev_event(iop_ram_ev_event),
.ev_arg0(iop_ram_ev_arg0), .ev_arg1(iop_ram_ev_arg1),
.ev_arg2(iop_ram_ev_arg2), .ev_arg3(iop_ram_ev_arg3),
.ev_flags(iop_ram_ev_flags)
);
// ------------------------------------------------------------------
// Trace sinks
// ------------------------------------------------------------------
trace_sink_stub #(.FILENAME("sif_iop_bridge_dmac.trace"), .SINK_LABEL("dmac"))
u_trace_dmac (.clk(clk), .rst_n(rst_n),
.ev_valid(dmac_ev_valid), .ev_subsys(dmac_ev_subsys),
.ev_event(dmac_ev_event), .ev_arg0(dmac_ev_arg0),
.ev_arg1(dmac_ev_arg1), .ev_arg2(dmac_ev_arg2),
.ev_arg3(dmac_ev_arg3), .ev_flags(dmac_ev_flags));
trace_sink_stub #(.FILENAME("sif_iop_bridge_iopmap.trace"), .SINK_LABEL("iop_map"))
u_trace_iopmap (.clk(clk), .rst_n(rst_n),
.ev_valid(iop_map_ev_valid), .ev_subsys(iop_map_ev_subsys),
.ev_event(iop_map_ev_event), .ev_arg0(iop_map_ev_arg0),
.ev_arg1(iop_map_ev_arg1), .ev_arg2(iop_map_ev_arg2),
.ev_arg3(iop_map_ev_arg3), .ev_flags(iop_map_ev_flags));
trace_sink_stub #(.FILENAME("sif_iop_bridge_iopram.trace"), .SINK_LABEL("iop_ram"))
u_trace_iopram (.clk(clk), .rst_n(rst_n),
.ev_valid(iop_ram_ev_valid), .ev_subsys(iop_ram_ev_subsys),
.ev_event(iop_ram_ev_event), .ev_arg0(iop_ram_ev_arg0),
.ev_arg1(iop_ram_ev_arg1), .ev_arg2(iop_ram_ev_arg2),
.ev_arg3(iop_ram_ev_arg3), .ev_flags(iop_ram_ev_flags));
// ------------------------------------------------------------------
// Counters
// ------------------------------------------------------------------
int dma_done_count;
int bridge_write_count_map; // map-layer writes from bridge
int bridge_write_count_ram; // ram-layer writes from bridge
int errors;
logic dma_done_seen;
initial begin
dma_done_count = 0;
bridge_write_count_map = 0;
bridge_write_count_ram = 0;
errors = 0;
dma_done_seen = 1'b0;
end
always_ff @(posedge clk) begin
if (rst_n && dmac_ev_valid &&
dmac_ev_event == trace_pkg::EV_DMA_DONE) begin
dma_done_count <= dma_done_count + 1;
dma_done_seen <= 1'b1;
end
// Map-layer bridge writes: master=3 (bridge), region=IOP_RAM=2
if (rst_n && iop_map_ev_valid &&
iop_map_ev_event == trace_pkg::EV_WRITE &&
iop_map_ev_arg2[7:0] == 8'd3 &&
iop_map_ev_arg3[7:0] == 8'd2)
bridge_write_count_map <= bridge_write_count_map + 1;
// RAM-layer bridge writes (same attribution propagated)
if (rst_n && iop_ram_ev_valid &&
iop_ram_ev_event == trace_pkg::EV_WRITE &&
iop_ram_ev_arg2[7:0] == 8'd3)
bridge_write_count_ram <= bridge_write_count_ram + 1;
end
// ------------------------------------------------------------------
// Payload table
// ------------------------------------------------------------------
logic [127:0] expected_qword [0:QWC_VAL-1];
logic [31:0] expected_word [0:(QWC_VAL*4)-1];
initial begin
expected_qword[0] = 128'hDDDD_DDDD_CCCC_CCCC_BBBB_BBBB_AAAA_AAAA;
expected_qword[1] = 128'h4444_4444_3333_3333_2222_2222_1111_1111;
// Little-endian unpacking expectation
expected_word[0] = 32'hAAAA_AAAA;
expected_word[1] = 32'hBBBB_BBBB;
expected_word[2] = 32'hCCCC_CCCC;
expected_word[3] = 32'hDDDD_DDDD;
expected_word[4] = 32'h1111_1111;
expected_word[5] = 32'h2222_2222;
expected_word[6] = 32'h3333_3333;
expected_word[7] = 32'h4444_4444;
end
// ------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------
task automatic write_dmac(input logic [7:0] offset, input logic [31:0] data);
@(negedge clk);
dmac_reg_wr_en = 1'b1;
dmac_reg_offset = offset;
dmac_reg_wr_data = data;
@(negedge clk);
dmac_reg_wr_en = 1'b0;
dmac_reg_offset = 8'd0;
dmac_reg_wr_data = 32'd0;
endtask
task automatic preload_ee(input logic [EE_RAM_ADDR_W-1:0] addr,
input logic [127:0] data);
@(negedge clk);
ee_ram_wr_en = 1'b1;
ee_ram_wr_addr = addr;
ee_ram_wr_data = data;
ee_ram_wr_be = 16'hFFFF;
@(negedge clk);
ee_ram_wr_en = 1'b0;
ee_ram_wr_addr = '0;
ee_ram_wr_data = 128'd0;
ee_ram_wr_be = 16'd0;
endtask
task automatic iop_read_expect(input logic [31:0] addr,
input logic [31:0] expected,
input string label);
@(negedge clk);
iop_rd_en = 1'b1;
iop_rd_addr = addr;
@(negedge clk);
iop_rd_en = 1'b0;
iop_rd_addr = 32'd0;
if (iop_rd_data !== expected || iop_rd_valid !== 1'b1) begin
$error("[tb_sif_iop_bridge_smoke] IOP read %s at 0x%08h: got 0x%08h valid=%0b expected 0x%08h",
label, addr, iop_rd_data, iop_rd_valid, expected);
errors = errors + 1;
end
endtask
// ------------------------------------------------------------------
// Stimulus
// ------------------------------------------------------------------
initial begin
rst_n = 1'b0;
dmac_reg_wr_en = 1'b0;
dmac_reg_offset = 8'd0;
dmac_reg_wr_data = 32'd0;
ee_ram_wr_en = 1'b0;
ee_ram_wr_addr = '0;
ee_ram_wr_data = 128'd0;
ee_ram_wr_be = 16'd0;
iop_rd_en = 1'b0;
iop_rd_addr = 32'd0;
repeat (4) @(posedge clk);
rst_n = 1'b1;
repeat (2) @(posedge clk);
// Preload EE RAM with the source qwords.
begin : do_preload
logic [31:0] a;
for (int i = 0; i < QWC_VAL; i++) begin
a = SOURCE_MADR + (i * 32'd16);
preload_ee(a[EE_RAM_ADDR_W-1:0], expected_qword[i]);
end
end
// Program DMAC ch5.
write_dmac(8'h10, SOURCE_MADR);
write_dmac(8'h20, 32'd2);
write_dmac(8'h00, 32'h0000_0001);
// Wait for DMA completion.
begin : wait_done
int spin;
spin = 0;
while (!dma_done_seen && spin < 500) begin
@(posedge clk);
spin = spin + 1;
end
if (!dma_done_seen) begin
$error("[tb_sif_iop_bridge_smoke] DMA never completed");
errors = errors + 1;
end
end
// Give the bridge a few cycles to finish flushing its final qword
// out to the IOP map after DMA_DONE.
repeat (20) @(posedge clk);
// Verify: 8 32-bit words landed at DEST_BASE, DEST_BASE+4, ...
for (int i = 0; i < QWC_VAL * 4; i++) begin
iop_read_expect(DEST_BASE_ADDR + (i * 32'd4),
expected_word[i],
$sformatf("word[%0d]", i));
end
repeat (4) @(posedge clk);
// ------------------------------------------------------------------
$display("[tb_sif_iop_bridge_smoke] dma_done=%0d bridge_writes_map=%0d bridge_writes_ram=%0d errors=%0d",
dma_done_count, bridge_write_count_map,
bridge_write_count_ram, errors);
if (dma_done_count != 1)
$error("expected exactly 1 DMA_DONE, got %0d", dma_done_count);
if (bridge_write_count_map != QWC_VAL * 4)
$error("expected %0d map-layer bridge writes, got %0d",
QWC_VAL * 4, bridge_write_count_map);
if (bridge_write_count_ram != QWC_VAL * 4)
$error("expected %0d RAM-layer bridge writes, got %0d",
QWC_VAL * 4, bridge_write_count_ram);
if (errors == 0 &&
dma_done_count == 1 &&
bridge_write_count_map == QWC_VAL * 4 &&
bridge_write_count_ram == QWC_VAL * 4)
$display("[tb_sif_iop_bridge_smoke] PASS");
else
$display("[tb_sif_iop_bridge_smoke] FAIL");
$finish;
end
initial begin
#400000;
$error("[tb_sif_iop_bridge_smoke] timeout");
$finish;
end
endmodule : tb_sif_iop_bridge_smoke