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,206 @@
|
||||
// retroDE_ps2 — tb_top_psmct32_combined_demo (Ch302)
|
||||
//
|
||||
// COMBINED textured + alpha + depth probe TB. Proves the multi-beat per-pixel
|
||||
// memory-op SCHEDULE (per the architect: assert the SEQUENCE of reads/writes,
|
||||
// not just final pixels), plus the rendered occlusion/blend result.
|
||||
//
|
||||
// Fixture (bake.py combined): opaque GREEN Z-writing background (Cbg, Zbg=0x4000)
|
||||
// then a TME+ABE+ZTE triangle whose interpolated Z runs 0x6000 (top, PASS) ->
|
||||
// 0x2000 (bottom, FAIL), texture = translucent (A=0x40) red(left)/blue(right).
|
||||
//
|
||||
// MEMORY-OP TRACER: while comb_active, per held pixel (ras_cur_x/ras_cur_y) count
|
||||
// z_rd_en, tex_rd_en, fb_rd_en, color-writes (emit to FB addr <0x400),
|
||||
// z-writes (emit to Z addr >=0x1000).
|
||||
// Asserts:
|
||||
// PROOF A (depth FAIL): >=1 inside-but-hidden pixel with z_rd=1, tex_rd=0,
|
||||
// fb_rd=0, color_wr=0, z_wr=0 (no texel read, no dest read, no writes).
|
||||
// PROOF B (depth PASS): >=1 visible pixel with z_rd=1, tex_rd=1, fb_rd=1,
|
||||
// color_wr=1, z_wr=1.
|
||||
// PROOF C (outside triangle): z_rd=0 (FSM beat0 sees !inside, no reads/writes).
|
||||
// PROOF D (render): PASS pixel == blend(texel, green) — texel RGB AND green
|
||||
// dest both present in the result; FAIL/outside pixel == green (occluded).
|
||||
|
||||
`timescale 1ns/1ps
|
||||
|
||||
module tb_top_psmct32_combined_demo;
|
||||
|
||||
localparam int H_ACTIVE = 16, V_ACTIVE = 16;
|
||||
localparam int ZBG = 'h4000;
|
||||
localparam int ZBUF_BASE = 2*2048; // ZBP=2 -> 0x1000 (FB stride=256B/row -> FB spans 0..0xFFF)
|
||||
localparam int TEX_BASE = 32*256; // 0x2000 (above FB+Z)
|
||||
|
||||
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)
|
||||
) 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: barycentric on the triangle v0(2,1) v1(13,1) v2(7,14) ----
|
||||
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
|
||||
|
||||
// ---- memory-op tracer (per held pixel) ----
|
||||
int zr [0:V_ACTIVE-1][0:H_ACTIVE-1];
|
||||
int txr [0:V_ACTIVE-1][0:H_ACTIVE-1];
|
||||
int fbr [0:V_ACTIVE-1][0:H_ACTIVE-1];
|
||||
int cwr [0:V_ACTIVE-1][0:H_ACTIVE-1]; // color writes
|
||||
int zwr [0:V_ACTIVE-1][0:H_ACTIVE-1]; // z writes
|
||||
initial for (int y=0;y<V_ACTIVE;y++) for (int x=0;x<H_ACTIVE;x++) begin
|
||||
zr[y][x]=0; txr[y][x]=0; fbr[y][x]=0; cwr[y][x]=0; zwr[y][x]=0; end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst_n && dut.u_gs.comb_active) begin
|
||||
int cx, cy;
|
||||
// READS: issued while the walker is held at the pixel -> index by ras_cur_x/y.
|
||||
cx = int'(dut.u_gs.ras_cur_x); cy = int'(dut.u_gs.ras_cur_y);
|
||||
if (cx < H_ACTIVE && cy < V_ACTIVE) begin
|
||||
if (dut.u_gs.z_rd_en) zr [cy][cx] <= zr [cy][cx] + 1;
|
||||
if (dut.u_gs.tex_rd_en) txr[cy][cx] <= txr[cy][cx] + 1;
|
||||
if (dut.u_gs.fb_rd_en) fbr[cy][cx] <= fbr[cy][cx] + 1;
|
||||
end
|
||||
// WRITES: the Z write (last beat) coincides with the walker advance,
|
||||
// so attribute writes by DECODING the FB/Z address (robust to timing).
|
||||
if (dut.u_gs.raster_pixel_emit) begin
|
||||
int wa, wx, wy;
|
||||
wa = int'(dut.u_gs.raster_pixel_fb_addr_q);
|
||||
if (wa < ZBUF_BASE) begin
|
||||
wx = (wa>>2) % 64; wy = (wa>>2) / 64;
|
||||
if (wx<H_ACTIVE && wy<V_ACTIVE) cwr[wy][wx] <= cwr[wy][wx] + 1; // FB color
|
||||
end else if (wa < TEX_BASE) begin
|
||||
wx = ((wa-ZBUF_BASE)>>2) % 64; wy = ((wa-ZBUF_BASE)>>2) / 64;
|
||||
if (wx<H_ACTIVE && wy<V_ACTIVE) zwr[wy][wx] <= zwr[wy][wx] + 1; // Z
|
||||
end
|
||||
end
|
||||
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);
|
||||
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;
|
||||
|
||||
for (int py=0; py<V_ACTIVE; py++) begin
|
||||
for (int px=0; px<H_ACTIVE; px++) begin
|
||||
real wa, wb, wc, fragz, uu, vv;
|
||||
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)); // clearly above Zbg
|
||||
pix_fail = clearly_in && (fragz <= (real'(ZBG) - 1024.0)); // clearly below Zbg
|
||||
|
||||
// PROOF C — clearly OUTSIDE the triangle: FSM does NO reads/writes.
|
||||
if (clearly_out) begin
|
||||
n_out++;
|
||||
if (zr[py][px]!=0 || txr[py][px]!=0 || fbr[py][px]!=0 || cwr[py][px]!=0 || zwr[py][px]!=0) begin
|
||||
if (errors<12) $error("[comb] OUTSIDE (%0d,%0d) had ops z=%0d t=%0d f=%0d cw=%0d zw=%0d",
|
||||
px,py, zr[py][px],txr[py][px],fbr[py][px],cwr[py][px],zwr[py][px]); errors++;
|
||||
end
|
||||
// background green
|
||||
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<12) $error("[comb] OUTSIDE (%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 A — depth FAIL (hidden): exactly the Z read, nothing else.
|
||||
if (pix_fail) begin
|
||||
n_fail++;
|
||||
if (!(zr[py][px]==1 && txr[py][px]==0 && fbr[py][px]==0 && cwr[py][px]==0 && zwr[py][px]==0)) begin
|
||||
if (errors<12) $error("[comb] FAIL (%0d,%0d) wrong ops z=%0d t=%0d f=%0d cw=%0d zw=%0d (want 1,0,0,0,0)",
|
||||
px,py, zr[py][px],txr[py][px],fbr[py][px],cwr[py][px],zwr[py][px]); errors++;
|
||||
end
|
||||
// occluded -> background green
|
||||
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<12) $error("[comb] FAIL (%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 B + D — depth PASS (visible): full read+write schedule + blend.
|
||||
if (pix_pass) begin
|
||||
n_pass++;
|
||||
if (!(zr[py][px]==1 && txr[py][px]==1 && fbr[py][px]==1 && cwr[py][px]==1 && zwr[py][px]==1)) begin
|
||||
if (errors<12) $error("[comb] PASS (%0d,%0d) wrong ops z=%0d t=%0d f=%0d cw=%0d zw=%0d (want 1,1,1,1,1)",
|
||||
px,py, zr[py][px],txr[py][px],fbr[py][px],cwr[py][px],zwr[py][px]); errors++;
|
||||
end
|
||||
// expected blend: texel (red u<4 / blue u>=4, A=0x40) over green
|
||||
uu = wb*7.0 + wc*3.0; vv = wc*7.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<12) $error("[comb] PASS (%0d,%0d) u=%.1f got(%02x,%02x,%02x) exp(%02x,%02x,%02x)",
|
||||
px,py,uu, 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("[comb] too few PASS pixels (%0d) — geometry issue", n_pass); errors++; end
|
||||
if (n_fail < 6) begin $error("[comb] too few FAIL pixels (%0d) — geometry issue", n_fail); errors++; end
|
||||
if (n_out < 20) begin $error("[comb] too few OUTSIDE pixels (%0d)", n_out); 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_combined_demo] PASS-pix=%0d FAIL-pix=%0d OUT-pix=%0d errors=%0d", n_pass, n_fail, n_out, errors);
|
||||
if (errors==0) $display("[tb_top_psmct32_combined_demo] PASS");
|
||||
else $display("[tb_top_psmct32_combined_demo] FAIL");
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin #120000000; $error("[tb_top_psmct32_combined_demo] TIMEOUT"); $finish; end
|
||||
|
||||
endmodule : tb_top_psmct32_combined_demo
|
||||
Reference in New Issue
Block a user