ec82764bef
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>
217 lines
7.7 KiB
Systemverilog
217 lines
7.7 KiB
Systemverilog
// retroDE_ps2 — tb_top_psmct32_zbuffer_demo (Brick 2b)
|
|
//
|
|
// TOP-LEVEL Z-buffer (depth test) demo TB for the BRAM BOARD VARIANT.
|
|
// Proves the synthesizable FLAT Z-tested PSMCT32 SPRITE path renders
|
|
// end-to-end through `top_psmct32_raster_demo_bram` exactly as a board
|
|
// load would SHOW on HDMI.
|
|
//
|
|
// EE bootlet (bios_zbuffer.mem) + GIF payload (payload_zbuffer.mem):
|
|
// U1 PACKED -> NEAR red SPRITE, Z=0x200, x[0..11] y[0..7],
|
|
// TEST_1.ZTE=1 ZTST=GEQUAL, ZBUF_1 ZBP=1 PSMZ32 ZMSK=0.
|
|
// U2 PACKED -> FAR blue SPRITE, Z=0x100, x[4..15] y[0..7] (drawn
|
|
// second; GIF context PRIM/FRAME/TEST/ZBUF persist).
|
|
//
|
|
// The Z buffer (ZBP=1 -> byte base 0x800) starts cleared (VRAM power-on
|
|
// zero). NEAR draws first (Z 0x200 >= 0 everywhere it covers, passes),
|
|
// stamping Z=0x200 at x[0..11]. FAR draws second; with GEQUAL its
|
|
// Z=0x100 is BEHIND the near Z(0x200) in the overlap x[4..11], so it
|
|
// FAILS and does NOT overwrite — near wins regardless of draw order.
|
|
//
|
|
// near-only x[0..3] -> RED (0xFF,0x00,0x00)
|
|
// overlap x[4..11] -> RED (near wins; NOT blue last-drawn)
|
|
// far-only x[12..15] -> BLUE (0x00,0x00,0xFF; stored Z=0 there)
|
|
//
|
|
// The overlap guard asserts the overlap is RED, NOT BLUE — proving the
|
|
// depth test actually gated the FAR write rather than last-write-wins.
|
|
|
|
`timescale 1ns/1ps
|
|
|
|
module tb_top_psmct32_zbuffer_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;
|
|
logic raster_overflow;
|
|
logic frame_toggle;
|
|
logic dma_done_toggle;
|
|
|
|
top_psmct32_raster_demo_bram #(
|
|
.H_ACTIVE (H_ACTIVE),
|
|
.V_ACTIVE (V_ACTIVE),
|
|
.PSMCT32_SWIZZLE(1'b0)
|
|
) 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),
|
|
.raster_overflow(raster_overflow),
|
|
.frame_toggle(frame_toggle),
|
|
.dma_done_toggle(dma_done_toggle),
|
|
.joy_a_pressed_i(1'b0),
|
|
.joy_b_pressed_i(1'b0)
|
|
);
|
|
|
|
// ----- Expected colors -----
|
|
localparam logic [7:0] NEAR_R = 8'hFF, NEAR_G = 8'h00, NEAR_B = 8'h00; // red
|
|
localparam logic [7:0] FAR_R = 8'h00, FAR_G = 8'h00, FAR_B = 8'hFF; // blue
|
|
|
|
// ----- Frame capture (delayed counters: BRAM read is registered) -----
|
|
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
|
|
|
|
logic [31:0] hcnt_d;
|
|
logic [31:0] vcnt_d;
|
|
always_ff @(posedge clk) begin
|
|
if (!rst_n) begin
|
|
hcnt_d <= 32'd0;
|
|
vcnt_d <= 32'd0;
|
|
end else begin
|
|
hcnt_d <= 32'(dut.u_pcrtc.hcnt);
|
|
vcnt_d <= 32'(dut.u_pcrtc.vcnt);
|
|
end
|
|
end
|
|
|
|
always_ff @(posedge clk) begin
|
|
if (rst_n && capture_armed && de
|
|
&& (vcnt_d < V_ACTIVE) && (hcnt_d < H_ACTIVE)) begin
|
|
cap_r [vcnt_d][hcnt_d] <= r;
|
|
cap_g [vcnt_d][hcnt_d] <= g;
|
|
cap_b [vcnt_d][hcnt_d] <= b;
|
|
cap_de[vcnt_d][hcnt_d] <= 1'b1;
|
|
end
|
|
end
|
|
|
|
int errors;
|
|
int near_ok;
|
|
int overlap_ok;
|
|
int far_ok;
|
|
initial begin
|
|
errors = 0; near_ok = 0; overlap_ok = 0; far_ok = 0;
|
|
end
|
|
|
|
|
|
// Returns 1 (via `ok` output) when the pixel matched; iverilog-12
|
|
// doesn't support task reference ports, so the caller increments
|
|
// the per-region counter from `ok`.
|
|
task automatic chk(input int x, input int y,
|
|
input logic [7:0] er, input logic [7:0] eg, input logic [7:0] eb,
|
|
input string label, output bit ok);
|
|
ok = 1'b0;
|
|
if (!cap_de[y][x]) begin
|
|
$error("%s (%0d,%0d) DE never asserted", label, x, y);
|
|
errors = errors + 1;
|
|
end else if (cap_r[y][x] !== er || cap_g[y][x] !== eg || cap_b[y][x] !== eb) begin
|
|
$error("[%s] (%0d,%0d) got (%02x,%02x,%02x) expected (%02x,%02x,%02x)",
|
|
label, x, y, cap_r[y][x], cap_g[y][x], cap_b[y][x], er, eg, eb);
|
|
errors = errors + 1;
|
|
end else begin
|
|
ok = 1'b1;
|
|
end
|
|
endtask
|
|
|
|
initial begin
|
|
rst_n = 1'b0;
|
|
core_go = 1'b0;
|
|
repeat (4) @(posedge clk);
|
|
rst_n = 1'b1;
|
|
repeat (8) @(posedge clk);
|
|
|
|
@(negedge clk);
|
|
core_go = 1'b1;
|
|
@(negedge clk);
|
|
core_go = 1'b0;
|
|
|
|
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;
|
|
|
|
begin
|
|
bit ok;
|
|
for (int y = 0; y < 8; y++) begin
|
|
// near-only x[0..3] -> RED
|
|
for (int x = 0; x < 4; x++) begin
|
|
chk(x, y, NEAR_R, NEAR_G, NEAR_B, "near", ok);
|
|
if (ok) near_ok = near_ok + 1;
|
|
end
|
|
// overlap x[4..11] -> RED (near wins, NOT far/last-drawn)
|
|
for (int x = 4; x < 12; x++) begin
|
|
chk(x, y, NEAR_R, NEAR_G, NEAR_B, "overlap", ok);
|
|
if (ok) overlap_ok = overlap_ok + 1;
|
|
// Guard: overlap must NOT be the FAR (last-drawn) color.
|
|
if (cap_r[y][x] === FAR_R && cap_g[y][x] === FAR_G && cap_b[y][x] === FAR_B) begin
|
|
$error("[overlap] (%0d,%0d) is FAR (last-drawn) — depth NOT gated!", x, y);
|
|
errors = errors + 1;
|
|
end
|
|
end
|
|
// far-only x[12..15] -> BLUE
|
|
for (int x = 12; x < 16; x++) begin
|
|
chk(x, y, FAR_R, FAR_G, FAR_B, "far", ok);
|
|
if (ok) far_ok = far_ok + 1;
|
|
end
|
|
end
|
|
end
|
|
|
|
if (!core_halt) begin $error("core_halt low at end"); 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
|
|
if (raster_overflow) begin $error("raster_overflow set"); errors = errors + 1; end
|
|
|
|
$display("[tb_top_psmct32_zbuffer_demo] near_ok=%0d/32 overlap_ok=%0d/64 far_ok=%0d/32 raster_emits=%0d errors=%0d",
|
|
near_ok, overlap_ok, far_ok,
|
|
dut.u_gs.raster_pixel_emit_count, errors);
|
|
if (errors == 0)
|
|
$display("[tb_top_psmct32_zbuffer_demo] PASS");
|
|
else
|
|
$display("[tb_top_psmct32_zbuffer_demo] FAIL");
|
|
$finish;
|
|
end
|
|
|
|
initial begin
|
|
#20000000;
|
|
$error("[tb_top_psmct32_zbuffer_demo] TIMEOUT");
|
|
$finish;
|
|
end
|
|
|
|
endmodule : tb_top_psmct32_zbuffer_demo
|