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>
This commit is contained in:
2026-06-29 20:10:50 -04:00
commit ec82764bef
2462 changed files with 2174303 additions and 0 deletions
@@ -0,0 +1,499 @@
// 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