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>
204 lines
11 KiB
Systemverilog
204 lines
11 KiB
Systemverilog
// retroDE_ps2 — tb_top_psmct32_tile_demo (Ch303)
|
|
//
|
|
// TILE-LOCAL combined renderer TB — first tiled-VRAM rung. The Ch302 combined
|
|
// TME+ABE+ZTE triangle is rendered into an ON-CHIP 16x16 color+Z tile
|
|
// (CLEAR -> RENDER -> FLUSH); texture still from VRAM. Proves the tile memory
|
|
// schedule (per the architect), not just final pixels:
|
|
//
|
|
// PROOF CLEAR : TP_CLEAR writes all 256 color + 256 Z tile entries.
|
|
// PROOF HIDDEN : a depth-FAIL pixel in TP_RENDER does NOT read texture and
|
|
// does NOT write tile color or tile Z.
|
|
// PROOF VISIBLE: a depth-PASS pixel reads texture and WRITES tile color + tile Z.
|
|
// PROOF FLUSH : TP_FLUSH emits 256 framebuffer writes (the tile -> VRAM copy).
|
|
// PROOF RENDER : final scanout == Ch302 result (cleared green; triangle top
|
|
// blended red->orange/blue->teal over green; bottom occluded green),
|
|
// which requires correct tile RAM registered-read latency end-to-end.
|
|
//
|
|
// tile_phase: 0=OFF 1=CLEAR 2=RENDER 3=FLUSH.
|
|
|
|
`timescale 1ns/1ps
|
|
|
|
module tb_top_psmct32_tile_demo;
|
|
|
|
localparam int H_ACTIVE = 16, V_ACTIVE = 16;
|
|
localparam int TP_CLEAR=1, TP_RENDER=2, 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(8*1024), .PSMCT32_SWIZZLE(1'b0),
|
|
.COMBINED_TAZ(1'b1), .TILE_LOCAL(1'b1)
|
|
) 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)
|
|
);
|
|
|
|
// ---- reference (same triangle/texture as Ch302) ----
|
|
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
|
|
|
|
// ---- per-pixel render-phase tracers ----
|
|
int txr [0:V_ACTIVE-1][0:H_ACTIVE-1]; // texture reads (VRAM)
|
|
int tcw [0:V_ACTIVE-1][0:H_ACTIVE-1]; // tile_color writes
|
|
int tzw [0:V_ACTIVE-1][0:H_ACTIVE-1]; // tile_z writes
|
|
int clear_cw, clear_zw, flush_emits;
|
|
initial begin
|
|
for (int y=0;y<V_ACTIVE;y++) for (int x=0;x<H_ACTIVE;x++) begin txr[y][x]=0; tcw[y][x]=0; tzw[y][x]=0; end
|
|
clear_cw=0; clear_zw=0; flush_emits=0;
|
|
end
|
|
|
|
always_ff @(posedge clk) begin
|
|
if (rst_n) begin
|
|
// CLEAR phase: count tile color/Z initializations.
|
|
if (dut.u_gs.tile_phase == TP_CLEAR) begin
|
|
if (dut.u_gs.tile_color_we) clear_cw <= clear_cw + 1;
|
|
if (dut.u_gs.tile_z_we) clear_zw <= clear_zw + 1;
|
|
end
|
|
// RENDER phase: per-pixel tile ops. Writes attributed by decoding the
|
|
// tile-RAM write address (robust to the walker advancing on the write
|
|
// cycle); texture reads by the held walker position.
|
|
if (dut.u_gs.tile_phase == TP_RENDER) begin
|
|
int tx, ty, cx, cy, zx, zy;
|
|
cx = int'(dut.u_gs.ras_cur_x[3:0]); cy = int'(dut.u_gs.ras_cur_y[3:0]);
|
|
if (dut.u_gs.tex_rd_en) txr[cy][cx] <= txr[cy][cx] + 1;
|
|
if (dut.u_gs.tile_color_we) begin
|
|
tx = int'(dut.u_gs.tile_color_waddr[3:0]); ty = int'(dut.u_gs.tile_color_waddr[7:4]);
|
|
tcw[ty][tx] <= tcw[ty][tx] + 1;
|
|
end
|
|
if (dut.u_gs.tile_z_we) begin
|
|
zx = int'(dut.u_gs.tile_z_waddr[3:0]); zy = int'(dut.u_gs.tile_z_waddr[7:4]);
|
|
tzw[zy][zx] <= tzw[zy][zx] + 1;
|
|
end
|
|
end
|
|
// FLUSH emits: in tile mode the ONLY raster_pixel_emit pulses are the
|
|
// tile->FB flush writes (render writes go to tile RAM; clear emits
|
|
// nothing). Count them unconditionally — the very last emit (idx 255)
|
|
// fires on the cycle tile_phase transitions FLUSH->OFF, so a
|
|
// phase==FLUSH gate would undercount by one.
|
|
if (dut.u_gs.raster_pixel_emit) flush_emits <= flush_emits + 1;
|
|
end
|
|
end
|
|
|
|
// ---- scanout capture ----
|
|
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_pass, n_fail, n_out;
|
|
|
|
initial begin
|
|
errors=0; n_pass=0; n_fail=0; n_out=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);
|
|
// Bracket the tile render: wait for it to BEGIN then COMPLETE
|
|
// (CLEAR->RENDER->FLUSH). If FLUSH/R_DRAIN never fires, raster_active
|
|
// never falls and this hangs to the timeout, exposing the bug.
|
|
@(posedge dut.u_gs.raster_active);
|
|
@(negedge dut.u_gs.raster_active);
|
|
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 CLEAR + FLUSH (whole-tile counts)
|
|
if (clear_cw != 256) begin $error("[tile] CLEAR wrote %0d color entries (want 256)", clear_cw); errors++; end
|
|
if (clear_zw != 256) begin $error("[tile] CLEAR wrote %0d Z entries (want 256)", clear_zw); errors++; end
|
|
if (flush_emits != 256) begin $error("[tile] FLUSH emitted %0d FB pixels (want 256)", flush_emits); errors++; end
|
|
|
|
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, pix_pass, pix_fail;
|
|
logic [7:0] er, eg, eb;
|
|
wa = bwa(px,py, 2,1, 13,1, 7,14);
|
|
wb = bwb(px,py, 2,1, 13,1, 7,14);
|
|
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);
|
|
pix_pass = clearly_in && (fragz >= (real'(ZBG)+1024.0));
|
|
pix_fail = clearly_in && (fragz <= (real'(ZBG)-1024.0));
|
|
|
|
if (clearly_out) begin
|
|
n_out++;
|
|
// outside the triangle: cleared green shows through.
|
|
if (cap_de[py][px] && !(cap_r[py][px]==8'h00 && cap_g[py][px]==8'h80 && cap_b[py][px]==8'h00)) begin
|
|
if (errors<10) $error("[tile] OUT (%0d,%0d) not green (%02x,%02x,%02x)",px,py,cap_r[py][px],cap_g[py][px],cap_b[py][px]); errors++;
|
|
end
|
|
end
|
|
// PROOF HIDDEN — depth fail: no texture read, no tile color/Z write.
|
|
if (pix_fail) begin
|
|
n_fail++;
|
|
if (!(txr[py][px]==0 && tcw[py][px]==0 && tzw[py][px]==0)) begin
|
|
if (errors<10) $error("[tile] HIDDEN (%0d,%0d) did ops tex=%0d cw=%0d zw=%0d (want 0,0,0)",px,py,txr[py][px],tcw[py][px],tzw[py][px]); errors++;
|
|
end
|
|
if (cap_de[py][px] && !(cap_r[py][px]==8'h00 && cap_g[py][px]==8'h80 && cap_b[py][px]==8'h00)) begin
|
|
if (errors<10) $error("[tile] HIDDEN (%0d,%0d) not green",px,py); errors++;
|
|
end
|
|
end
|
|
// PROOF VISIBLE — depth pass: tex read + tile color/Z write; blended color.
|
|
if (pix_pass) begin
|
|
n_pass++;
|
|
if (!(txr[py][px]>=1 && tcw[py][px]>=1 && tzw[py][px]>=1)) begin
|
|
if (errors<10) $error("[tile] VISIBLE (%0d,%0d) ops tex=%0d cw=%0d zw=%0d (want >=1 each)",px,py,txr[py][px],tcw[py][px],tzw[py][px]); errors++;
|
|
end
|
|
uu = wb*7.0 + wc*3.0;
|
|
if (uu < 4.0) begin er=8'd127; eg=8'd64; eb=8'd0; end // red over green
|
|
else begin er=8'd0; eg=8'd64; eb=8'd127; end // blue over green
|
|
if (cap_de[py][px] && !(cap_r[py][px]===er && cap_g[py][px]===eg && cap_b[py][px]===eb)) begin
|
|
if (errors<10) $error("[tile] VISIBLE (%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_pass<6) begin $error("[tile] too few PASS pixels (%0d)", n_pass); errors++; end
|
|
if (n_fail<6) begin $error("[tile] too few FAIL pixels (%0d)", n_fail); 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_tile_demo] clear_cw=%0d clear_zw=%0d flush=%0d PASS-pix=%0d FAIL-pix=%0d OUT-pix=%0d errors=%0d",
|
|
clear_cw, clear_zw, flush_emits, n_pass, n_fail, n_out, errors);
|
|
if (errors==0) $display("[tb_top_psmct32_tile_demo] PASS");
|
|
else $display("[tb_top_psmct32_tile_demo] FAIL");
|
|
$finish;
|
|
end
|
|
|
|
initial begin #150000000; $error("[tb_top_psmct32_tile_demo] TIMEOUT"); $finish; end
|
|
endmodule : tb_top_psmct32_tile_demo
|