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>
164 lines
8.7 KiB
Systemverilog
164 lines
8.7 KiB
Systemverilog
// retroDE_ps2 — tb_top_psmct32_tile2x2_demo (Ch304)
|
|
//
|
|
// 2x2 MULTI-TILE renderer TB. ONE combined TME+ABE+ZTE triangle spanning the
|
|
// 32x32 region (a 2x2 grid of 16x16 tiles), crossing BOTH tile seams (x=16 and
|
|
// y=16). The renderer re-tests the triangle against each of the 4 tiles
|
|
// (CLEAR -> RENDER-clipped -> FLUSH per tile). Proves:
|
|
// PROOF GRID : all 4 tiles (col,row in {0,1}x{0,1}) clear independently
|
|
// (256 tile_color writes each) and flush (1024 FB emits total).
|
|
// PROOF SEAM : the WHOLE 32x32 scanout matches a SINGLE screen-space reference
|
|
// (one continuous barycentric function of screen x,y) — so the
|
|
// image is identical whether a pixel was rendered in tile (0,0)
|
|
// or its neighbour. Matching across x=15|16 and y=15|16 with NO
|
|
// discontinuity IS the seam proof. (Plus an explicit seam-pixel
|
|
// match count.)
|
|
// PROOF DEPTH/BLEND : top half blended (red->orange / blue->teal over green),
|
|
// bottom half occluded green — same per-pixel rule as Ch302/303.
|
|
|
|
`timescale 1ns/1ps
|
|
|
|
module tb_top_psmct32_tile2x2_demo;
|
|
|
|
localparam int H_ACTIVE = 32, V_ACTIVE = 32;
|
|
localparam int TP_CLEAR=1, TP_FLUSH=3;
|
|
localparam int ZBG = 'h4000;
|
|
|
|
logic clk, 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, dma_done_seen, frame_seen, raster_overflow;
|
|
logic frame_toggle, dma_done_toggle;
|
|
|
|
top_psmct32_raster_demo_bram #(
|
|
.H_ACTIVE(H_ACTIVE), .V_ACTIVE(V_ACTIVE),
|
|
.VRAM_BYTES(16*1024), .PSMCT32_SWIZZLE(1'b0),
|
|
.COMBINED_TAZ(1'b1), .TILE_LOCAL(1'b1), .TILE_COLS(2), .TILE_ROWS(2)
|
|
) 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)
|
|
);
|
|
|
|
// ---- single screen-space reference: triangle v0(3,3) v1(28,3) v2(16,29) ----
|
|
function automatic real bdet(input real ax,input real ay,input real bx,input real by,input real cx,input real cy);
|
|
bdet=(by-cy)*(ax-cx)+(cx-bx)*(ay-cy); endfunction
|
|
function automatic real bwa(input real px,input real py,input real ax,input real ay,input real bx,input real by,input real cx,input real cy);
|
|
bwa=((by-cy)*(px-cx)+(cx-bx)*(py-cy))/bdet(ax,ay,bx,by,cx,cy); endfunction
|
|
function automatic real bwb(input real px,input real py,input real ax,input real ay,input real bx,input real by,input real cx,input real cy);
|
|
bwb=((cy-ay)*(px-cx)+(ax-cx)*(py-cy))/bdet(ax,ay,bx,by,cx,cy); endfunction
|
|
|
|
// ---- grid tracers ----
|
|
int clear_cw_tile [0:1][0:1]; // tile_color writes per (row,col) during CLEAR
|
|
int flush_emits;
|
|
initial begin
|
|
for (int rr=0;rr<2;rr++) for (int cc=0;cc<2;cc++) clear_cw_tile[rr][cc]=0;
|
|
flush_emits=0;
|
|
end
|
|
always_ff @(posedge clk) if (rst_n) begin
|
|
if (dut.u_gs.tile_phase==TP_CLEAR && dut.u_gs.tile_color_we) begin
|
|
int cc, rr; cc=int'(dut.u_gs.tile_col_r); rr=int'(dut.u_gs.tile_row_r);
|
|
if (cc<2 && rr<2) clear_cw_tile[rr][cc] <= clear_cw_tile[rr][cc] + 1;
|
|
end
|
|
if (dut.u_gs.raster_pixel_emit) flush_emits <= flush_emits + 1; // tile mode: only flush emits
|
|
end
|
|
|
|
// ---- scanout capture (32x32) ----
|
|
logic [7:0] cap_r[0:V_ACTIVE-1][0:H_ACTIVE-1], cap_g[0:V_ACTIVE-1][0:H_ACTIVE-1], 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]=0;cap_g[y][x]=0;cap_b[y][x]=0;cap_de[y][x]=0; end
|
|
capture_armed=1'b0;
|
|
end
|
|
logic [31:0] hcnt_d, vcnt_d;
|
|
always_ff @(posedge clk) begin
|
|
if (!rst_n) begin hcnt_d<=0; vcnt_d<=0; 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, n_match, n_check, n_seam;
|
|
|
|
initial begin
|
|
errors=0; n_match=0; n_check=0; n_seam=0;
|
|
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);
|
|
@(posedge dut.u_gs.raster_active); // grid render begins
|
|
@(negedge dut.u_gs.raster_active); // all 4 tiles done (clear+render+flush)
|
|
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;
|
|
|
|
// PROOF GRID — each of the 4 tiles cleared independently + total flushes.
|
|
for (int rr=0;rr<2;rr++) for (int cc=0;cc<2;cc++)
|
|
if (clear_cw_tile[rr][cc] != 256) begin
|
|
$error("[t2x2] tile(col=%0d,row=%0d) cleared %0d color entries (want 256)", cc, rr, clear_cw_tile[rr][cc]); errors++;
|
|
end
|
|
if (flush_emits != 4*256) begin $error("[t2x2] flush emitted %0d (want %0d)", flush_emits, 4*256); errors++; end
|
|
|
|
// PROOF SEAM + DEPTH/BLEND — whole 32x32 vs ONE screen-space reference.
|
|
for (int py=0; py<V_ACTIVE; py++) begin
|
|
for (int px=0; px<H_ACTIVE; px++) begin
|
|
real wa, wb, wc, fragz, uu;
|
|
bit clearly_in, clearly_out;
|
|
logic [7:0] er, eg, eb;
|
|
wa = bwa(px,py, 3,3, 28,3, 16,29);
|
|
wb = bwb(px,py, 3,3, 28,3, 16,29);
|
|
wc = 1.0 - wa - wb;
|
|
clearly_in = (wa>0.06)&&(wb>0.06)&&(wc>0.06);
|
|
clearly_out = (wa<-0.06)||(wb<-0.06)||(wc<-0.06);
|
|
fragz = (wa+wb)*real'('h6000) + wc*real'('h2000);
|
|
// expected color
|
|
if (clearly_out) begin er=8'h00; eg=8'h80; eb=8'h00; end // green clear
|
|
else if (clearly_in && (fragz <= real'(ZBG)-1024.0)) begin er=8'h00; eg=8'h80; eb=8'h00; end // occluded green
|
|
else if (clearly_in && (fragz >= real'(ZBG)+1024.0)) begin
|
|
uu = wb*7.0 + wc*3.0;
|
|
if (uu < 3.5) begin er=8'd127; eg=8'd64; eb=8'd0; end
|
|
else if (uu > 4.5) begin er=8'd0; eg=8'd64; eb=8'd127; end
|
|
else begin er=8'hxx; eg=8'hxx; eb=8'hxx; end // cell-boundary: skip
|
|
end else begin er=8'hxx; eg=8'hxx; eb=8'hxx; end // edge/Z-boundary: skip
|
|
|
|
if (er!==8'hxx && cap_de[py][px]) begin
|
|
n_check++;
|
|
if (cap_r[py][px]===er && cap_g[py][px]===eg && cap_b[py][px]===eb) begin
|
|
n_match++;
|
|
if (px==15||px==16||py==15||py==16) n_seam++; // seam-region pixels that matched
|
|
end else begin
|
|
if (errors<12) $error("[t2x2] (%0d,%0d) got(%02x,%02x,%02x) exp(%02x,%02x,%02x)",px,py,cap_r[py][px],cap_g[py][px],cap_b[py][px],er,eg,eb);
|
|
errors++;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if (n_check < 200) begin $error("[t2x2] too few checkable pixels (%0d)", n_check); errors++; end
|
|
if (n_match != n_check) begin $error("[t2x2] image match %0d/%0d (seam/tiling error)", n_match, n_check); errors++; end
|
|
if (n_seam < 20) begin $error("[t2x2] too few SEAM-region matches (%0d) — seam not exercised", n_seam); errors++; end
|
|
if (!core_halt) begin $error("core_halt low"); errors++; end
|
|
if (raster_overflow) begin $error("raster_overflow"); errors++; end
|
|
|
|
$display("[tb_top_psmct32_tile2x2_demo] clears(per-tile)=[%0d %0d %0d %0d] flush=%0d match=%0d/%0d seam_matches=%0d errors=%0d",
|
|
clear_cw_tile[0][0],clear_cw_tile[0][1],clear_cw_tile[1][0],clear_cw_tile[1][1], flush_emits, n_match, n_check, n_seam, errors);
|
|
if (errors==0) $display("[tb_top_psmct32_tile2x2_demo] PASS");
|
|
else $display("[tb_top_psmct32_tile2x2_demo] FAIL");
|
|
$finish;
|
|
end
|
|
|
|
initial begin #250000000; $error("[tb_top_psmct32_tile2x2_demo] TIMEOUT"); $finish; end
|
|
endmodule : tb_top_psmct32_tile2x2_demo
|