Files
retroDE_ps2/sim/tb/top/tb_top_psmct32_raster_demo.sv
T
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

213 lines
7.2 KiB
Systemverilog
Raw 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_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