// retroDE_ps2 — tb_top_psmct32_triangle_demo (Brick 3) // // TOP-LEVEL non-axis-aligned GOURAUD TRIANGLE demo TB for the BRAM BOARD // VARIANT. Proves the first real triangle renders end-to-end through // `top_psmct32_raster_demo_bram` exactly as a board load would SHOW on // HDMI — with interpolated COLOR (Gouraud) and interpolated DEPTH (Z). // // EE bootlet (bios_triangle.mem) + GIF payload (payload_triangle.mem): // U1 PACKED -> TRI A: Gouraud, v0=(1,1) RED, v1=(14,1) GREEN, // v2=(7,7) BLUE, FLAT-NEAR Z=0x300, GEQUAL Z-test, // ZBUF ZBP=2 PSMZ32 (cleared -> passes, stamps Z=0x300). // U2 PACKED -> TRI B: grey (0x80) at FLAT-FAR Z=0x100, v0=(2,5) // v1=(13,5) v2=(7,2). Drawn second; GEQUAL. // // Expected on screen: // * TRI A is a real non-axis-aligned (downward) Gouraud triangle: its // interior shows a visible R/G/B gradient (corners tend toward the // vertex colors). // * In the band where TRI B overlaps TRI A, TRI A WINS (its Z=0x300 > // TRI B's interpolated Z=0x100 under GEQUAL), so those pixels are // TRI A's Gouraud color, NOT grey — proving the per-pixel // interpolated depth gated the second triangle's write. // * TRI B only paints grey where TRI A did NOT cover (stored Z=0 there). // // Checks: // (1) coverage: a representative set of TRI A interior pixels are // drawn (non-zero color, DE asserted), and a clearly-outside // pixel is background (black). // (2) gradient: the color VARIES across TRI A (not flat) and the // near-vertex regions lean toward the corresponding vertex color. // (3) depth gating: a pixel inside the TRI A/TRI B overlap is NOT grey // (TRI A won) — proving interpolated-Z depth test gated TRI B. `timescale 1ns/1ps module tb_top_psmct32_triangle_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) ); // ----- 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, 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; initial errors = 0; function automatic bit is_grey(input int x, input int y); return (cap_r[y][x] == 8'h80) && (cap_g[y][x] == 8'h80) && (cap_b[y][x] == 8'h80); endfunction function automatic bit nonzero(input int x, input int y); return (cap_r[y][x] != 0) || (cap_g[y][x] != 0) || (cap_b[y][x] != 0); endfunction 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; // ---- (1) Coverage: representative TRI A interior pixels drawn ---- // TRI A spans v0=(1,1) v1=(14,1) v2=(7,7); centre column near the // top is solidly interior. Check a few deep-interior pixels. begin int interior_drawn; interior_drawn = 0; for (int yy = 2; yy <= 5; yy++) for (int xx = 5; xx <= 9; xx++) if (cap_de[yy][xx] && nonzero(xx, yy)) interior_drawn = interior_drawn + 1; if (interior_drawn < 12) begin $error("TRI A interior coverage too sparse: %0d/20 deep-interior pixels drawn", interior_drawn); errors = errors + 1; end $display("[tri_demo] TRI A deep-interior drawn = %0d/20", interior_drawn); end // Clearly-OUTSIDE pixel (top corners outside the downward tri). if (nonzero(0, 7)) begin // (0,7): far bottom-left, outside TRI A and outside TRI B. $error("outside pixel (0,7) is colored (%02x,%02x,%02x), expected background", cap_r[7][0], cap_g[7][0], cap_b[7][0]); errors = errors + 1; end // ---- (2) Gradient present: color VARIES across TRI A ---- // Top-left interior (near RED v0) should have more R than B; // top-right interior (near GREEN v1) more G than R; lower-centre // (near BLUE v2) more B than R. We sample interior pixels. begin // near v0 (1,1) RED: pixel (3,2) if (cap_de[2][3] && !(cap_r[2][3] > cap_b[2][3])) begin $error("gradient: near-RED (3,2) R=%0d not > B=%0d", cap_r[2][3], cap_b[2][3]); errors = errors + 1; end // near v1 (14,1) GREEN: pixel (12,2) if (cap_de[2][12] && !(cap_g[2][12] > cap_r[2][12])) begin $error("gradient: near-GREEN (12,2) G=%0d not > R=%0d", cap_g[2][12], cap_r[2][12]); errors = errors + 1; end // near v2 (7,7) BLUE: pixel (7,6) if (cap_de[6][7] && !(cap_b[6][7] > cap_r[6][7])) begin $error("gradient: near-BLUE (7,6) B=%0d not > R=%0d", cap_b[6][7], cap_r[6][7]); errors = errors + 1; end $display("[tri_demo] gradient samples: nearR(3,2)=(%0d,%0d,%0d) nearG(12,2)=(%0d,%0d,%0d) nearB(7,6)=(%0d,%0d,%0d)", cap_r[2][3],cap_g[2][3],cap_b[2][3], cap_r[2][12],cap_g[2][12],cap_b[2][12], cap_r[6][7],cap_g[6][7],cap_b[6][7]); end // ---- (3) Depth gating: TRI A/TRI B overlap shows TRI A (not grey) ---- // The TRI B band is around y=2..5 mid-x; pixel (7,4) is inside both // TRI A and TRI B. TRI A (Z=0x300) was stamped first; TRI B // (Z=0x100) must FAIL GEQUAL there -> pixel is TRI A's Gouraud // color, NOT grey 0x80. begin bit any_overlap_checked; any_overlap_checked = 1'b0; for (int yy = 3; yy <= 4; yy++) begin for (int xx = 6; xx <= 8; xx++) begin if (cap_de[yy][xx] && nonzero(xx, yy)) begin any_overlap_checked = 1'b1; if (is_grey(xx, yy)) begin $error("depth gating FAIL: overlap (%0d,%0d) is GREY (TRI B won) — interp-Z not gated!", xx, yy); errors = errors + 1; end end end end if (!any_overlap_checked) begin $error("depth gating: no overlap pixel observed to test"); errors = errors + 1; end // And TRI B SHOULD have painted grey somewhere it was unoccluded // (e.g. its lower band where TRI A didn't reach). Confirm grey // appears at least once so we know TRI B actually drew. begin int grey_count; grey_count = 0; for (int yy = 0; yy < V_ACTIVE; yy++) for (int xx = 0; xx < H_ACTIVE; xx++) if (cap_de[yy][xx] && is_grey(xx, yy)) grey_count = grey_count + 1; if (grey_count == 0) begin $error("TRI B never painted grey anywhere — second triangle did not render"); errors = errors + 1; end $display("[tri_demo] grey (TRI B unoccluded) pixel count = %0d", grey_count); 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_triangle_demo] raster_emits=%0d errors=%0d", dut.u_gs.raster_pixel_emit_count, errors); if (errors == 0) $display("[tb_top_psmct32_triangle_demo] PASS"); else $display("[tb_top_psmct32_triangle_demo] FAIL"); $finish; end initial begin #20000000; $error("[tb_top_psmct32_triangle_demo] TIMEOUT"); $finish; end endmodule : tb_top_psmct32_triangle_demo