// retroDE_ps2 — tb_top_psmct32_raster_demo_bram_tritex
//
// TOP-LEVEL textured-TRIANGLE demo TB for the BRAM board variant
// (top_psmct32_raster_demo_bram — the variant the de25_nano board top
// instantiates). Proves the textured-triangle rung renders end-to-end
// through the BRAM VRAM, including vram_bram_stub.read2's 1-cycle
// registered texel-fetch latency (gs_stub TEX_RD_REGISTERED=1).
//
//   EE bootlet (bios_tritex.mem) + GIF payload (payload_tritex.mem):
//     U1 BITBLT/TRX  -> uploads an 8x8 PSMCT32 texture (DBP=8)
//     U2 IMAGE       -> 16 qwords = the 64 texels
//     U3 PACKED      -> PRIM(TRI+TME) + TEX0 + 3 vertices (RGBAQ/UV/XYZ2):
//                       one non-axis-aligned textured triangle.
//
// UV == screen XY at each vertex, so the affine interpolated (u,v) ==
// (x,y) at every interior pixel; the sampled texel is exactly the
// uploaded tex_demo_texel(x,y). Verified through the PCRTC scanout RGB.
//
// Built with PSMCT32_SWIZZLE=0 so the LINEAR gs_texel_addr fetch and the
// BITBLT upload share one VRAM layout. ZTE=0, ABE=0 for this rung.

`timescale 1ns/1ps

module tb_top_psmct32_raster_demo_bram_tritex;

    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)
    );

    // ----- Reference: triangle interior + expected texel (UV==XY trick) -----
    // Triangle from bake.py build_textured_triangle_demo_payload: (1,1),(7,1),(2,6).
    localparam int VX0 = 1, VY0 = 1;
    localparam int VX1 = 7, VY1 = 1;
    localparam int VX2 = 2, VY2 = 6;
    int sx1, sy1, sx2, sy2;

    function automatic int edge_f(input int px, input int py,
                                  input int ax, input int ay,
                                  input int bx, input int by);
        return (px - ax) * (by - ay) - (py - ay) * (bx - ax);
    endfunction
    function automatic bit tol(input int ax, input int ay, input int bx, input int by);
        int dx, dy; dx = bx - ax; dy = by - ay;
        return (dy > 0) || ((dy == 0) && (dx > 0));
    endfunction
    task automatic setup_ref();
        int sa;
        sa = (VX1 - VX0) * (VY2 - VY0) - (VY1 - VY0) * (VX2 - VX0);
        if (sa < 0) begin sx1 = VX2; sy1 = VY2; sx2 = VX1; sy2 = VY1; end
        else        begin sx1 = VX1; sy1 = VY1; sx2 = VX2; sy2 = VY2; end
    endtask
    function automatic bit ref_inside(input int px, input int py);
        int e0, e1, e2, b0, b1, b2;
        e0 = edge_f(px, py, VX0, VY0, sx1, sy1);
        e1 = edge_f(px, py, sx1, sy1, sx2, sy2);
        e2 = edge_f(px, py, sx2, sy2, VX0, VY0);
        b0 = tol(VX0, VY0, sx1, sy1) ? 0 : 1;
        b1 = tol(sx1, sy1, sx2, sy2) ? 0 : 1;
        b2 = tol(sx2, sy2, VX0, VY0) ? 0 : 1;
        return ((e0 + b0) <= 0) && ((e1 + b1) <= 0) && ((e2 + b2) <= 0);
    endfunction

    // tex_demo_texel(x,y) quadrant pattern (mirrors bake.py):
    //   (x<4,y<4)=RED  (x>=4,y<4)=GREEN  (x<4,y>=4)=BLUE  (x>=4,y>=4)=YELLOW
    // PCRTC PSMCT32 -> r=texel[7:0], g=texel[15:8], b=texel[23:16].
    function automatic logic [7:0] tex_exp_r(input int x, input int y); return ((x<4)==(y<4))  ? 8'hFF : 8'h00; endfunction
    function automatic logic [7:0] tex_exp_g(input int x, input int y); return (x>=4)          ? 8'hFF : 8'h00; endfunction
    function automatic logic [7:0] tex_exp_b(input int x, input int y); return ((x<4)&&(y>=4)) ? 8'hFF : 8'h00; endfunction

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

    // BRAM sync read: r/g/b at cycle T correspond to the pixel addressed
    // at T-1, so index the captured pixel by the delayed counters.
    logic [31:0] hcnt_d;
    logic [31:0] 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;
    int tex_pixels_ok;
    int distinct_seen;

    initial begin
        errors = 0;
        tex_pixels_ok = 0;
        distinct_seen = 0;
        setup_ref();

        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;

        // Every interior pixel must show the sampled texel for its (x,y).
        for (int y = 0; y < V_ACTIVE; y++) begin
            for (int x = 0; x < H_ACTIVE; x++) begin
                if (ref_inside(x, y)) begin
                    logic [7:0] er, eg, eb;
                    er = tex_exp_r(x, y); eg = tex_exp_g(x, y); eb = tex_exp_b(x, y);
                    if (!cap_de[y][x]) begin
                        $error("tritex (%0d,%0d) DE never asserted", x, y);
                        errors = errors + 1;
                    end else if (cap_r[y][x] !== er || cap_g[y][x] !== eg || cap_b[y][x] !== eb) begin
                        $error("[tritex] (%0d,%0d) got (%02x,%02x,%02x) expected (%02x,%02x,%02x)",
                               x, y, cap_r[y][x], cap_g[y][x], cap_b[y][x], er, eg, eb);
                        errors = errors + 1;
                    end else begin
                        tex_pixels_ok = tex_pixels_ok + 1;
                    end
                end
            end
        end

        // The rendered triangle must show MORE THAN ONE distinct texel
        // colour (real sampled gradient, not a collapsed flat fill).
        // Count interior pixels whose colour differs from the first one.
        begin
            logic [23:0] first_seen;
            bit          have_first;
            logic [23:0] this_px;
            have_first   = 1'b0;
            distinct_seen = 1;   // the first pixel itself
            for (int y = 0; y < V_ACTIVE; y++)
                for (int x = 0; x < H_ACTIVE; x++)
                    if (ref_inside(x, y) && cap_de[y][x]) begin
                        this_px = {cap_b[y][x], cap_g[y][x], cap_r[y][x]};
                        if (!have_first) begin
                            first_seen = this_px; have_first = 1'b1;
                        end else if (this_px !== first_seen) begin
                            distinct_seen = 2;   // at least 2 distinct colours
                        end
                    end
        end
        if (distinct_seen < 2) begin
            $error("textured triangle is uniform (distinct=%0d) — texturing collapsed",
                   distinct_seen);
            errors = errors + 1;
        end
        if (tex_pixels_ok < 8) begin
            $error("too few interior textured pixels matched (%0d)", tex_pixels_ok);
            errors = errors + 1;
        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_raster_demo_bram_tritex] tex_ok=%0d distinct=%0d raster_emits=%0d errors=%0d",
                 tex_pixels_ok, distinct_seen, dut.u_gs.raster_pixel_emit_count, errors);
        if (errors == 0)
            $display("[tb_top_psmct32_raster_demo_bram_tritex] PASS");
        else
            $display("[tb_top_psmct32_raster_demo_bram_tritex] FAIL");
        $finish;
    end

    initial begin
        #20000000;
        $error("[tb_top_psmct32_raster_demo_bram_tritex] TIMEOUT");
        $finish;
    end

endmodule : tb_top_psmct32_raster_demo_bram_tritex
