Files
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

431 lines
16 KiB
Systemverilog

// retroDE_ps2 — tb_sif_dma_overflow
//
// Full-stop backpressure proof. Instantiates sif_dma_stub with DEPTH=2 and
// issues a QWC=4 DMAC transfer. After beats 0 and 1 land, the receive
// buffer is full; `in_ready` must drop and the DMAC must stall waiting
// for space that never comes (this stub has no consume path yet).
//
// Pass criteria (per Codex capacity-hardening spec):
// - exactly 2 beats accepted (rx_count == 2)
// - full_o asserts
// - no additional beats accepted while full
// - no DMA_DONE
// - two stored qwords intact and ordered
// - DMAC is still trying to emit the next beat at the expected source
// address (MADR + 32) — verified by internal state peek
`timescale 1ns/1ps
module tb_sif_dma_overflow;
logic clk;
logic rst_n;
initial clk = 1'b0;
always #5 clk = ~clk;
localparam int QWC_VAL = 4;
localparam int RAM_SIZE_BYTES = 4 * 1024;
localparam int RAM_ADDR_W = $clog2(RAM_SIZE_BYTES);
localparam int SIF_DEPTH = 2; // deliberately small
localparam int SIF_IDX_W = $clog2(SIF_DEPTH);
localparam logic [31:0] PAYLOAD_MADR = 32'h0000_0400;
// ------------------------------------------------------------------
// ee_ram_stub
// ------------------------------------------------------------------
logic ram_rd_en;
logic [RAM_ADDR_W-1:0] ram_rd_addr;
logic [127:0] ram_rd_data;
logic ram_rd_valid;
logic ram_wr_en;
logic [RAM_ADDR_W-1:0] ram_wr_addr;
logic [127:0] ram_wr_data;
logic [15:0] ram_wr_be;
logic [7:0] ram_master_id;
logic ram_ev_valid;
trace_pkg::subsys_e ram_ev_subsys;
trace_pkg::event_e ram_ev_event;
logic [63:0] ram_ev_arg0, ram_ev_arg1, ram_ev_arg2, ram_ev_arg3;
logic [31:0] ram_ev_flags;
ee_ram_stub #(.SIZE_BYTES(RAM_SIZE_BYTES)) u_ram (
.clk(clk), .rst_n(rst_n),
.rd_en(ram_rd_en), .rd_addr(ram_rd_addr),
.rd_data(ram_rd_data), .rd_valid(ram_rd_valid),
.wr_en(ram_wr_en), .wr_addr(ram_wr_addr),
.wr_data(ram_wr_data), .wr_be(ram_wr_be),
.master_id(ram_master_id),
.ev_valid(ram_ev_valid), .ev_subsys(ram_ev_subsys),
.ev_event(ram_ev_event),
.ev_arg0(ram_ev_arg0), .ev_arg1(ram_ev_arg1),
.ev_arg2(ram_ev_arg2), .ev_arg3(ram_ev_arg3),
.ev_flags(ram_ev_flags)
);
assign ram_master_id = ram_rd_en ? 8'd1 : 8'd0;
// ------------------------------------------------------------------
// DMAC (channel 5)
// ------------------------------------------------------------------
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)
);
// ------------------------------------------------------------------
// ee_memory_map_stub
// ------------------------------------------------------------------
logic map_ram_rd_en;
logic [24:0] map_ram_rd_addr;
logic map_ev_valid;
trace_pkg::subsys_e map_ev_subsys;
trace_pkg::event_e map_ev_event;
logic [63:0] map_ev_arg0, map_ev_arg1, map_ev_arg2, map_ev_arg3;
logic [31:0] map_ev_flags;
ee_memory_map_stub u_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(map_ram_rd_en), .ram_rd_addr(map_ram_rd_addr),
.ram_rd_data(ram_rd_data), .ram_rd_valid(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(map_ev_valid), .ev_subsys(map_ev_subsys),
.ev_event(map_ev_event),
.ev_arg0(map_ev_arg0), .ev_arg1(map_ev_arg1),
.ev_arg2(map_ev_arg2), .ev_arg3(map_ev_arg3),
.ev_flags(map_ev_flags)
);
assign ram_rd_en = map_ram_rd_en;
assign ram_rd_addr = map_ram_rd_addr[RAM_ADDR_W-1:0];
// ------------------------------------------------------------------
// sif_dma_stub (DEPTH=2)
// ------------------------------------------------------------------
logic sif_rd_en;
logic [SIF_IDX_W-1:0] sif_rd_idx;
logic [127:0] sif_rd_data;
logic sif_rd_valid;
logic sif_stall;
logic [31:0] sif_rx_count;
logic sif_last_seen;
logic sif_full;
logic sif_ev_valid;
trace_pkg::subsys_e sif_ev_subsys;
trace_pkg::event_e sif_ev_event;
logic [63:0] sif_ev_arg0, sif_ev_arg1, sif_ev_arg2, sif_ev_arg3;
logic [31:0] sif_ev_flags;
sif_dma_stub #(.DEPTH(SIF_DEPTH)) u_sif (
.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),
.rd_en(sif_rd_en), .rd_idx(sif_rd_idx),
.rd_data(sif_rd_data), .rd_valid(sif_rd_valid),
.stall_in(sif_stall),
.rx_count(sif_rx_count), .last_seen(sif_last_seen),
.full_o(sif_full),
.ev_valid(sif_ev_valid), .ev_subsys(sif_ev_subsys),
.ev_event(sif_ev_event),
.ev_arg0(sif_ev_arg0), .ev_arg1(sif_ev_arg1),
.ev_arg2(sif_ev_arg2), .ev_arg3(sif_ev_arg3),
.ev_flags(sif_ev_flags)
);
// ------------------------------------------------------------------
// Trace sinks (single combined file for brevity)
// ------------------------------------------------------------------
trace_sink_stub #(.FILENAME("sif_dma_overflow_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_dma_overflow_sif.trace"), .SINK_LABEL("sif"))
u_trace_sif (.clk(clk), .rst_n(rst_n),
.ev_valid(sif_ev_valid), .ev_subsys(sif_ev_subsys),
.ev_event(sif_ev_event), .ev_arg0(sif_ev_arg0),
.ev_arg1(sif_ev_arg1), .ev_arg2(sif_ev_arg2),
.ev_arg3(sif_ev_arg3), .ev_flags(sif_ev_flags));
// ------------------------------------------------------------------
// Counters
// ------------------------------------------------------------------
int dma_beat_count;
int dma_done_count;
logic dma_done_seen;
int errors;
initial begin
dma_beat_count = 0;
dma_done_count = 0;
dma_done_seen = 1'b0;
errors = 0;
end
always_ff @(posedge clk) begin
if (rst_n && dmac_ev_valid) begin
if (dmac_ev_event == trace_pkg::EV_DMA_BEAT)
dma_beat_count <= dma_beat_count + 1;
if (dmac_ev_event == trace_pkg::EV_DMA_DONE) begin
dma_done_count <= dma_done_count + 1;
dma_done_seen <= 1'b1;
end
end
end
// ------------------------------------------------------------------
// Payload table
// ------------------------------------------------------------------
logic [127:0] expected_payload [0:QWC_VAL-1];
initial begin
expected_payload[0] = 128'h1111_0000_1111_0000_1111_0000_1111_0000;
expected_payload[1] = 128'h2222_0000_2222_0000_2222_0000_2222_0000;
expected_payload[2] = 128'h3333_0000_3333_0000_3333_0000_3333_0000;
expected_payload[3] = 128'h4444_0000_4444_0000_4444_0000_4444_0000;
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_ram_qword(input logic [RAM_ADDR_W-1:0] addr,
input logic [127:0] data);
@(negedge clk);
ram_wr_en = 1'b1;
ram_wr_addr = addr;
ram_wr_data = data;
ram_wr_be = 16'hFFFF;
@(negedge clk);
ram_wr_en = 1'b0;
ram_wr_addr = '0;
ram_wr_data = 128'd0;
ram_wr_be = 16'd0;
endtask
task automatic sif_read_slot(input logic [SIF_IDX_W-1:0] idx,
output logic [127:0] data);
@(negedge clk);
sif_rd_en = 1'b1;
sif_rd_idx = idx;
@(negedge clk);
sif_rd_en = 1'b0;
sif_rd_idx = '0;
data = sif_rd_data;
endtask
// ------------------------------------------------------------------
// Stimulus
// ------------------------------------------------------------------
int settle_beats;
int settle_rx;
logic settle_full;
logic settle_done;
int beat_index_peek;
logic [127:0] beat_payload_peek;
logic [127:0] rb;
initial begin
rst_n = 1'b0;
dmac_reg_wr_en = 1'b0;
dmac_reg_offset = 8'd0;
dmac_reg_wr_data = 32'd0;
ram_wr_en = 1'b0;
ram_wr_addr = '0;
ram_wr_data = 128'd0;
ram_wr_be = 16'd0;
sif_rd_en = 1'b0;
sif_rd_idx = '0;
sif_stall = 1'b0;
repeat (4) @(posedge clk);
rst_n = 1'b1;
repeat (2) @(posedge clk);
begin : do_preload
logic [31:0] a;
for (int i = 0; i < QWC_VAL; i++) begin
a = PAYLOAD_MADR + (i * 32'd16);
preload_ram_qword(a[RAM_ADDR_W-1:0], expected_payload[i]);
end
end
// Program DMAC for a QWC=4 transfer. With DEPTH=2, the receiver
// should accept exactly 2 beats and then backpressure indefinitely.
write_dmac(8'h10, PAYLOAD_MADR);
write_dmac(8'h20, 32'd4);
write_dmac(8'h00, 32'h0000_0001);
// Let the DMAC exhaust buffer capacity and settle into stall.
repeat (80) @(posedge clk);
settle_beats = dma_beat_count;
settle_rx = sif_rx_count;
settle_full = sif_full;
settle_done = dma_done_seen;
// Whitebox peeks into the DMAC to verify it is still trying to
// emit beat 2 at MADR + 32, not skipping ahead.
beat_index_peek = u_dmac.beat_index;
beat_payload_peek = u_dmac.beat_payload;
if (settle_beats !== 2) begin
$error("[tb_sif_dma_overflow] expected exactly 2 beats accepted, got %0d",
settle_beats);
errors = errors + 1;
end
if (settle_rx !== 32'd2) begin
$error("[tb_sif_dma_overflow] rx_count=%0d, expected 2", settle_rx);
errors = errors + 1;
end
if (!settle_full) begin
$error("[tb_sif_dma_overflow] full_o never asserted");
errors = errors + 1;
end
if (settle_done) begin
$error("[tb_sif_dma_overflow] DMA_DONE fired while buffer full");
errors = errors + 1;
end
if (beat_index_peek !== 2) begin
$error("[tb_sif_dma_overflow] DMAC beat_index peek=%0d, expected 2 (stuck on beat 2)",
beat_index_peek);
errors = errors + 1;
end
if (beat_payload_peek !== expected_payload[2]) begin
$error("[tb_sif_dma_overflow] DMAC beat_payload peek mismatch");
$error(" got: 0x%032h", beat_payload_peek);
$error(" expected: 0x%032h (= payload[2], fetched from MADR+32)",
expected_payload[2]);
errors = errors + 1;
end
// Spin another window — nothing should advance.
repeat (40) @(posedge clk);
if (dma_beat_count !== 2 || sif_rx_count !== 32'd2 || dma_done_seen) begin
$error("[tb_sif_dma_overflow] state advanced during second stall window");
errors = errors + 1;
end
// Verify the two stored qwords are intact.
for (int i = 0; i < SIF_DEPTH; i++) begin
sif_read_slot(SIF_IDX_W'(i), rb);
if (rb !== expected_payload[i]) begin
$error("[tb_sif_dma_overflow] slot[%0d]: got 0x%032h expected 0x%032h",
i, rb, expected_payload[i]);
errors = errors + 1;
end
end
repeat (4) @(posedge clk);
// ------------------------------------------------------------------
$display("[tb_sif_dma_overflow] settle_beats=%0d settle_rx=%0d full=%0b done=%0b beat_idx=%0d errors=%0d",
settle_beats, settle_rx, settle_full, settle_done,
beat_index_peek, errors);
if (errors == 0 &&
settle_beats == 2 && settle_rx === 32'd2 &&
settle_full && !settle_done &&
beat_index_peek == 2 &&
beat_payload_peek === expected_payload[2])
$display("[tb_sif_dma_overflow] PASS");
else
$display("[tb_sif_dma_overflow] FAIL");
$finish;
end
initial begin
#400000;
$error("[tb_sif_dma_overflow] timeout");
$finish;
end
endmodule : tb_sif_dma_overflow