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:
@@ -0,0 +1,212 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_raster_demo (Ch146)
|
||||
//
|
||||
// Focused TB for the first hardware-targeted top wrapper. Drives the
|
||||
// top with the static .mem fixtures generated by sim/data/
|
||||
// top_psmct32_raster_demo/bake.py and verifies the same 16×8 PCRTC
|
||||
// frame Ch123 produces. The point of this TB is NOT to re-verify
|
||||
// every Ch123 invariant — that's already covered by
|
||||
// tb_gs_demo_psmct32_swizzle_e2e — but to prove:
|
||||
// (1) The top wrapper compiles standalone with no hierarchical
|
||||
// refs into TB scopes.
|
||||
// (2) `$readmemh` of the static fixtures produces identical
|
||||
// behavior to the procedural Ch123 preload.
|
||||
// (3) The status bundle (core_halt + dma_done_seen + frame_seen)
|
||||
// fires in the expected order.
|
||||
// (4) The active PCRTC region matches Ch123's per-quadrant
|
||||
// grayscale exactly — so a board-level bring-up that wires
|
||||
// r/g/b/hsync/vsync/de to a video PHY would display the same
|
||||
// image the sim produces.
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_raster_demo;
|
||||
|
||||
localparam int H_ACTIVE = 16;
|
||||
localparam int V_ACTIVE = 8;
|
||||
|
||||
logic clk;
|
||||
logic rst_n;
|
||||
initial clk = 1'b0;
|
||||
always #5 clk = ~clk;
|
||||
|
||||
logic core_go;
|
||||
logic [7:0] r, g, b;
|
||||
logic hsync, vsync, de;
|
||||
logic core_halt;
|
||||
logic dma_done_seen;
|
||||
logic frame_seen;
|
||||
|
||||
// BIOS / payload image paths are passed to the top wrapper via
|
||||
// pre-defined macros (the Ch146 build rule sets
|
||||
// TOP_PSMCT32_RASTER_DEMO_BIOS_IMAGE_FILE +
|
||||
// TOP_PSMCT32_RASTER_DEMO_PAYLOAD_IMAGE_FILE via -D, then the
|
||||
// wrapper expands them at the bios_rom_stub / ee_ram_stub
|
||||
// instances).
|
||||
top_psmct32_raster_demo #(
|
||||
.H_ACTIVE (H_ACTIVE),
|
||||
.V_ACTIVE (V_ACTIVE)
|
||||
) dut (
|
||||
.clk(clk), .rst_n(rst_n),
|
||||
.core_go(core_go),
|
||||
.r(r), .g(g), .b(b),
|
||||
.hsync(hsync), .vsync(vsync), .de(de),
|
||||
.core_halt(core_halt),
|
||||
.dma_done_seen(dma_done_seen),
|
||||
.frame_seen(frame_seen)
|
||||
);
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Per-quadrant expected RGB (matches Ch123 / bake.py).
|
||||
// ----------------------------------------------------------------
|
||||
function automatic int quadrant_idx(input int x, input int y);
|
||||
int qx, qy;
|
||||
qx = (x < 8) ? 0 : 1;
|
||||
qy = (y < 4) ? 0 : 1;
|
||||
return qy * 2 + qx;
|
||||
endfunction
|
||||
function automatic logic [7:0] expected_r(input int q);
|
||||
case (q)
|
||||
0: return 8'h55;
|
||||
1: return 8'h66;
|
||||
2: return 8'h77;
|
||||
default: return 8'h88;
|
||||
endcase
|
||||
endfunction
|
||||
function automatic logic [7:0] expected_g(input int q);
|
||||
case (q)
|
||||
0: return 8'hAA;
|
||||
1: return 8'hBB;
|
||||
2: return 8'h33;
|
||||
default: return 8'h44;
|
||||
endcase
|
||||
endfunction
|
||||
function automatic logic [7:0] expected_b(input int q);
|
||||
case (q)
|
||||
0: return 8'hCC;
|
||||
1: return 8'hDD;
|
||||
2: return 8'h99;
|
||||
default: return 8'h22;
|
||||
endcase
|
||||
endfunction
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Frame capture.
|
||||
// ----------------------------------------------------------------
|
||||
logic [7:0] cap_r [0:V_ACTIVE-1][0:H_ACTIVE-1];
|
||||
logic [7:0] cap_g [0:V_ACTIVE-1][0:H_ACTIVE-1];
|
||||
logic [7:0] cap_b [0:V_ACTIVE-1][0:H_ACTIVE-1];
|
||||
logic cap_de[0:V_ACTIVE-1][0:H_ACTIVE-1];
|
||||
bit capture_armed;
|
||||
|
||||
initial begin
|
||||
for (int y = 0; y < V_ACTIVE; y++)
|
||||
for (int x = 0; x < H_ACTIVE; x++) begin
|
||||
cap_r[y][x] = 8'd0;
|
||||
cap_g[y][x] = 8'd0;
|
||||
cap_b[y][x] = 8'd0;
|
||||
cap_de[y][x] = 1'b0;
|
||||
end
|
||||
capture_armed = 1'b0;
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst_n && capture_armed && de
|
||||
&& (dut.u_pcrtc.vcnt < V_ACTIVE)
|
||||
&& (dut.u_pcrtc.hcnt < H_ACTIVE)) begin
|
||||
cap_r [dut.u_pcrtc.vcnt][dut.u_pcrtc.hcnt] <= r;
|
||||
cap_g [dut.u_pcrtc.vcnt][dut.u_pcrtc.hcnt] <= g;
|
||||
cap_b [dut.u_pcrtc.vcnt][dut.u_pcrtc.hcnt] <= b;
|
||||
cap_de[dut.u_pcrtc.vcnt][dut.u_pcrtc.hcnt] <= 1'b1;
|
||||
end
|
||||
end
|
||||
|
||||
int errors;
|
||||
initial errors = 0;
|
||||
|
||||
initial begin
|
||||
rst_n = 1'b0;
|
||||
core_go = 1'b0;
|
||||
repeat (4) @(posedge clk);
|
||||
rst_n = 1'b1;
|
||||
repeat (8) @(posedge clk);
|
||||
|
||||
// Pulse core_go for one cycle. On hardware a board reset-
|
||||
// release sequencer can do the same.
|
||||
@(negedge clk);
|
||||
core_go = 1'b1;
|
||||
@(negedge clk);
|
||||
core_go = 1'b0;
|
||||
|
||||
// Wait for the EE to halt (SYSCALL halt_o), then for DMAC
|
||||
// DONE event to be sticky-latched.
|
||||
wait (core_halt == 1'b1);
|
||||
repeat (4) @(posedge clk);
|
||||
wait (dma_done_seen == 1'b1);
|
||||
repeat (10) @(posedge clk);
|
||||
if (dut.xfer_busy == 1'b1)
|
||||
wait (dut.xfer_busy == 1'b0);
|
||||
if (dut.u_gs.raster_active == 1'b1)
|
||||
wait (dut.u_gs.raster_active == 1'b0);
|
||||
repeat (10) @(posedge clk);
|
||||
|
||||
@(posedge dut.u_pcrtc.end_of_frame);
|
||||
@(posedge clk);
|
||||
capture_armed = 1'b1;
|
||||
@(posedge dut.u_pcrtc.end_of_frame);
|
||||
@(posedge clk);
|
||||
capture_armed = 1'b0;
|
||||
|
||||
// Phase 1 — scanout assertion. Per-pixel per-quadrant RGB.
|
||||
for (int y = 0; y < V_ACTIVE; y++) begin
|
||||
for (int x = 0; x < H_ACTIVE; x++) begin
|
||||
int q;
|
||||
logic [7:0] er, eg, eb;
|
||||
q = quadrant_idx(x, y);
|
||||
er = expected_r(q);
|
||||
eg = expected_g(q);
|
||||
eb = expected_b(q);
|
||||
if (!cap_de[y][x]) begin
|
||||
$error("(%0d,%0d) DE never asserted", x, y);
|
||||
errors = errors + 1;
|
||||
end
|
||||
if (cap_r[y][x] !== er || cap_g[y][x] !== eg || cap_b[y][x] !== eb) begin
|
||||
$error("[scanout] (%0d,%0d) got (%02x,%02x,%02x) expected (%02x,%02x,%02x) sprite=%0d",
|
||||
x, y, cap_r[y][x], cap_g[y][x], cap_b[y][x], er, eg, eb, q);
|
||||
errors = errors + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Phase 2 — status bundle. core_halt, dma_done_seen, frame_seen
|
||||
// all latched.
|
||||
if (!core_halt) begin
|
||||
$error("core_halt low at end of test");
|
||||
errors = errors + 1;
|
||||
end
|
||||
if (!dma_done_seen) begin
|
||||
$error("dma_done_seen never latched");
|
||||
errors = errors + 1;
|
||||
end
|
||||
if (!frame_seen) begin
|
||||
$error("frame_seen never latched");
|
||||
errors = errors + 1;
|
||||
end
|
||||
|
||||
$display("[tb_top_psmct32_raster_demo] core_halt=%0b dma_done_seen=%0b frame_seen=%0b raster_emits=%0d errors=%0d",
|
||||
core_halt, dma_done_seen, frame_seen,
|
||||
dut.u_gs.raster_pixel_emit_count, errors);
|
||||
if (errors == 0)
|
||||
$display("[tb_top_psmct32_raster_demo] PASS");
|
||||
else
|
||||
$display("[tb_top_psmct32_raster_demo] FAIL");
|
||||
$finish;
|
||||
end
|
||||
|
||||
// Watchdog.
|
||||
initial begin
|
||||
#20000000;
|
||||
$error("[tb_top_psmct32_raster_demo] TIMEOUT");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule : tb_top_psmct32_raster_demo
|
||||
Reference in New Issue
Block a user