// 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
