// retroDE_ps2 — tb_top_psmct32_feeder_gouraud_demo (Ch335)
//
// PER-VERTEX GOURAUD color. The combined-TAZ MODULATE path now multiplies the texel by the
// INTERPOLATED vertex color (aligned to the texel via comb_dx/comb_dy at CB_T), so distinct
// per-vertex RGBAQ produce a smooth gradient. Flat scenes (equal vertex colors) collapse to the
// constant (Ch333/334 anchors unchanged). Three runtime-switched scenes:
//   GOURAUD_TRI  : tile0 triangle, v0=red v1=green v2=blue  -> RGB gradient
//   GOURAUD_RECT : tile5 quad (2 tris), corners red/green/blue/white -> gradient quad
//   GOURAUD_MIX  : flat red triangle(0) + RGB gradient triangle(10)
//
// PROOF (a gradient, not flat): sampling each triangle near a vertex shows that vertex's channel
// dominating, AND the per-vertex samples differ. The flat tile in MIX is a single dominant color.

`timescale 1ns/1ps

module tb_top_psmct32_feeder_gouraud_demo;

    localparam int H_ACTIVE = 64, V_ACTIVE = 64;
    localparam real MARG = 0.12;
    localparam logic [1:0] C_RUN = 2'd1, C_READY = 2'd3;

    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, feeder_go_tb;

    top_psmct32_raster_demo_bram #(
        .H_ACTIVE(H_ACTIVE), .V_ACTIVE(V_ACTIVE),
        .VRAM_BYTES(32*1024), .PSMCT32_SWIZZLE(1'b0),
        .COMBINED_TAZ(1'b1), .TILE_LOCAL(1'b1), .TILE_COLS(4), .TILE_ROWS(4),
        .TILE_MULTIPRIM(1'b1), .TILE_PRIM_COUNT(4), .TILE_FIFO_DEPTH(8),
        .BIN_BUFFER_ENABLE(1'b1), .HEARTBEAT_SPLICE_ENABLE(1'b0),
        .FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(256)
    ) 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),
        .feeder_stg_we_i(1'b0), .feeder_stg_waddr_i(12'd0), .feeder_stg_wdata_i(64'd0),
        .feeder_go_i(feeder_go_tb),
        .feeder_ready_o(), .feeder_records_o(), .feeder_waits_o()
    );

    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;
    task automatic clear_cap;
        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
    endtask
    initial begin clear_cap(); 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)
        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
    task automatic capture_frame;
        clear_cap();
        @(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;
    endtask
    task automatic stage_and_run(input string memfile);
        $readmemh(memfile, dut.g_feeder.feeder_stg);
        @(negedge clk); feeder_go_tb=1'b1; @(negedge clk); feeder_go_tb=1'b0;
        wait (dut.g_feeder.cst == C_RUN); wait (dut.g_feeder.cst == C_READY);
        capture_frame();
    endtask

    int errors;
    // assert pixel (x,y) is drawn and channel `ch` (0=r,1=g,2=b) strictly dominates the other two.
    task automatic dom(input string lbl, input int x, input int y, input int ch);
        logic [7:0] pr, pg, pb;
        begin
            pr=cap_r[y][x]; pg=cap_g[y][x]; pb=cap_b[y][x];
            if (!cap_de[y][x]) begin $error("[%s] (%0d,%0d) not drawn", lbl,x,y); errors++; end
            else begin
                unique case (ch)
                    0: if (!(pr>pg && pr>pb)) begin $error("[%s] (%0d,%0d)=(%02x%02x%02x) not RED-dominant", lbl,x,y,pr,pg,pb); errors++; end
                    1: if (!(pg>pr && pg>pb)) begin $error("[%s] (%0d,%0d)=(%02x%02x%02x) not GREEN-dominant", lbl,x,y,pr,pg,pb); errors++; end
                    default: if (!(pb>pr && pb>pg)) begin $error("[%s] (%0d,%0d)=(%02x%02x%02x) not BLUE-dominant", lbl,x,y,pr,pg,pb); errors++; end
                endcase
            end
        end
    endtask

    initial begin
        errors=0; feeder_go_tb=1'b0;
        $readmemh(`FEEDER_GOURAUD_TRI_FILE, dut.g_feeder.feeder_stg);   // power-up = gouraud tri

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

        // GOURAUD_TRI: tile0 tri, v0=(1,1)red v1=(14,1)green v2=(1,14)blue -> gradient
        wait (dut.g_feeder.cst == C_READY); capture_frame();
        dom("G_TRI v0", 2, 2, 0); dom("G_TRI v1", 10, 2, 1); dom("G_TRI v2", 2, 10, 2);
        if (int'(dut.feeder_records)!=1) begin $error("G_TRI records=%0d (want 1)", int'(dut.feeder_records)); errors++; end
        $display("[G_TRI] v0(2,2)=%02x%02x%02x v1(10,2)=%02x%02x%02x v2(2,10)=%02x%02x%02x",
                 cap_r[2][2],cap_g[2][2],cap_b[2][2], cap_r[2][10],cap_g[2][10],cap_b[2][10], cap_r[10][2],cap_g[10][2],cap_b[10][2]);

        // GOURAUD_RECT: tile5 quad, corners TL(17,17)red TR(30,17)green BL(17,30)blue BR(30,30)white
        stage_and_run(`FEEDER_GOURAUD_RECT_FILE);
        dom("G_RECT TL", 18, 18, 0); dom("G_RECT TR", 29, 18, 1); dom("G_RECT BL", 18, 29, 2);
        // BR ~ white: all channels high
        if (!(cap_r[29][29]>8'hA0 && cap_g[29][29]>8'hA0 && cap_b[29][29]>8'hA0)) begin
            $error("G_RECT BR(29,29)=%02x%02x%02x not whiteish", cap_r[29][29],cap_g[29][29],cap_b[29][29]); errors++; end
        if (int'(dut.feeder_records)!=2) begin $error("G_RECT records=%0d (want 2)", int'(dut.feeder_records)); errors++; end
        $display("[G_RECT] TL=%02x%02x%02x TR=%02x%02x%02x BL=%02x%02x%02x BR=%02x%02x%02x",
                 cap_r[18][18],cap_g[18][18],cap_b[18][18], cap_r[18][29],cap_g[18][29],cap_b[18][29],
                 cap_r[29][18],cap_g[29][18],cap_b[29][18], cap_r[29][29],cap_g[29][29],cap_b[29][29]);

        // GOURAUD_MIX: flat red tri(0) + gradient tri(10, ox=oy=32)
        stage_and_run(`FEEDER_GOURAUD_MIX_FILE);
        // tile0 flat red: all three sampled points red-dominant AND equal (no gradient)
        dom("G_MIX flat", 3, 3, 0); dom("G_MIX flat", 10, 2, 0); dom("G_MIX flat", 2, 10, 0);
        // tile10 gradient: vertex dominance (v0=(33,33)red v1=(46,33)green v2=(33,46)blue)
        dom("G_MIX grad v0", 34, 34, 0); dom("G_MIX grad v1", 42, 34, 1); dom("G_MIX grad v2", 34, 42, 2);
        if (int'(dut.feeder_records)!=2) begin $error("G_MIX records=%0d (want 2)", int'(dut.feeder_records)); errors++; end
        $display("[G_MIX] flat0(3,3)=%02x%02x%02x  grad10 v0(34,34)=%02x%02x%02x v1(42,34)=%02x%02x%02x v2(34,42)=%02x%02x%02x",
                 cap_r[3][3],cap_g[3][3],cap_b[3][3], cap_r[34][34],cap_g[34][34],cap_b[34][34],
                 cap_r[34][42],cap_g[34][42],cap_b[34][42], cap_r[42][34],cap_g[42][34],cap_b[42][34]);

        if (raster_overflow) begin $error("raster_overflow set"); errors++; end
        if (!core_halt)      begin $error("core_halt low"); errors++; end
        $display("[tb_top_psmct32_feeder_gouraud_demo] errors=%0d", errors);
        if (errors==0) $display("[tb_top_psmct32_feeder_gouraud_demo] PASS");
        else           $display("[tb_top_psmct32_feeder_gouraud_demo] FAIL");
        $finish;
    end
    initial begin #1200000000; $error("[tb_top_psmct32_feeder_gouraud_demo] TIMEOUT"); $finish; end
endmodule : tb_top_psmct32_feeder_gouraud_demo
