// retroDE_ps2 — tb_top_psmct32_tile_palbilinear_demo (Ch314)
//
// BILINEAR filtering for a PALETTIZED (PSMT8 indexed) texture in the combined
// tiled renderer. Two triangles sample the SAME magnified 4x4 PSMT8 INDEX checker
// (idx 0/1) with a CLUT (0=blue, 1=white), at different TEX1.MMAG:
//   LEFT  tri (x2..14, MMAG=0, NEAREST):  blocky checker — every pixel is one of
//                                         the 2 CLUT colors, NO midtones.
//   RIGHT tri (x18..30, MMAG=1, LINEAR):  smoothed — midtone (light-blue) pixels
//                                         at texel boundaries.
//
// This is the ON-BOARD CLUT-BEFORE-INTERP proof: each bilinear tap fetches an
// INDEX, CLUTs it to a color, then the 4 COLORS are interpolated. If the hardware
// instead interpolated INDICES and CLUT'd once, the RIGHT triangle would be just
// as blocky as the LEFT (an interpolated index rounds to one palette entry → a
// pure color). So midtones on the RIGHT but NONE on the LEFT proves colors — not
// indices — are interpolated. The exact 4-tap CLUT math is proven at the unit
// level by tb_gs_texture_bilinear (CASE7..CASE12).
//
// Checker colors blue (0,0,255) / white (255,255,255): b==255 in both, so a
// bilinear blend gives r=g in (0,255), b high (a midtone); green clear has b==0.
//
// PROOFS:
//   NEAREST blocky : LEFT tri has ZERO midtone pixels (pure CLUT colors only).
//   BILINEAR smooth: RIGHT tri has >=N midtone pixels (CLUT-then-blend at borders).
//   RENDERED       : both tris paint a healthy number of checker pixels (the
//                    ~9-cyc indexed stall + CLUT did not drop the bilinear prim).
//   GRID           : 4 tiles cleared (256 each) + 1024 flush; prim list = 2.

`timescale 1ns/1ps

module tb_top_psmct32_tile_palbilinear_demo;

    localparam int H_ACTIVE = 32, V_ACTIVE = 32;
    localparam int TP_CLEAR = 1;
    localparam real MARG = 0.10;

    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), .TILE_LOCAL(1'b1), .TILE_COLS(2), .TILE_ROWS(2),
        .TILE_MULTIPRIM(1'b1), .TILE_PRIM_COUNT(2),
        .BILINEAR_ENABLE(1'b1), .PALETTE_BILINEAR(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)
    );

    function automatic int tri_cls(input real px, input real py,
                                   input real ax, input real ay,
                                   input real bx, input real by,
                                   input real cx, input real cy);
        real d, wa, wb, wc;
        begin
            d  = (by-cy)*(ax-cx) + (cx-bx)*(ay-cy);
            wa = ((by-cy)*(px-cx) + (cx-bx)*(py-cy)) / d;
            wb = ((cy-ay)*(px-cx) + (ax-cx)*(py-cy)) / d;
            wc = 1.0 - wa - wb;
            if (wa>MARG && wb>MARG && wc>MARG)            tri_cls = 1;
            else if (wa<-MARG || wb<-MARG || wc<-MARG)    tri_cls = 0;
            else                                          tri_cls = 2;
        end
    endfunction

    int  clear_cw_tile [0:1][0:1];
    int  flush_emits;
    bit  saw_prim_count_2;
    initial begin
        for (int rr=0;rr<2;rr++) for (int cc=0;cc<2;cc++) clear_cw_tile[rr][cc]=0;
        flush_emits=0; saw_prim_count_2=1'b0;
    end
    always_ff @(posedge clk) if (rst_n) begin
        if (dut.u_gs.tile_phase==TP_CLEAR && dut.u_gs.tile_color_we) begin
            int cc, rr; cc=int'(dut.u_gs.tile_col_r); rr=int'(dut.u_gs.tile_row_r);
            if (cc<2 && rr<2) clear_cw_tile[rr][cc] <= clear_cw_tile[rr][cc] + 1;
        end
        if (dut.u_gs.raster_pixel_emit) flush_emits <= flush_emits + 1;
        if (dut.u_gs.raster_active && (int'(dut.u_gs.prim_count_r)==2)) saw_prim_count_2 <= 1'b1;
    end

    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;
    int n_near_tex, n_near_mid, n_bil_tex, n_bil_mid;

    // a checker pixel = b high (>=200). midtone = checker AND r strictly between
    // the two pure CLUT values (not pure blue r=0, not pure white r=255).
    function automatic bit is_checker(input logic [7:0] rr, input logic [7:0] gg, input logic [7:0] bb);
        is_checker = (bb >= 8'd200);
    endfunction
    function automatic bit is_midtone(input logic [7:0] rr, input logic [7:0] gg, input logic [7:0] bb);
        is_midtone = (bb >= 8'd200) && (rr > 8'd16) && (rr < 8'd239);
    endfunction

    initial begin
        errors=0; n_near_tex=0; n_near_mid=0; n_bil_tex=0; n_bil_mid=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);
        @(posedge dut.u_gs.raster_active);
        @(negedge dut.u_gs.raster_active);
        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 rr=0;rr<2;rr++) for (int cc=0;cc<2;cc++)
            if (clear_cw_tile[rr][cc] != 256) begin
                $error("[pb] tile(col=%0d,row=%0d) cleared %0d (want 256)", cc, rr, clear_cw_tile[rr][cc]); errors++;
            end
        if (flush_emits != 4*256) begin $error("[pb] flush emitted %0d (want %0d)", flush_emits, 4*256); errors++; end
        if (!saw_prim_count_2)     begin $error("[pb] prim_count_r never == 2"); errors++; end

        // classify each pixel: LEFT (nearest) tri vs RIGHT (bilinear) tri.
        for (int py=0; py<V_ACTIVE; py++) begin
            for (int px=0; px<H_ACTIVE; px++) begin
                int cl, cr2;
                logic [7:0] rr, gg, bb;
                if (cap_de[py][px]) begin
                    rr=cap_r[py][px]; gg=cap_g[py][px]; bb=cap_b[py][px];
                    cl  = tri_cls(real'(px),real'(py),  2, 4, 14, 4,  2,26);   // LEFT nearest
                    cr2 = tri_cls(real'(px),real'(py), 18, 4, 30, 4, 18,26);   // RIGHT bilinear
                    if (cl==1) begin                                          // clearly inside LEFT
                        if (is_checker(rr,gg,bb)) n_near_tex++;
                        if (is_midtone(rr,gg,bb)) n_near_mid++;
                    end
                    if (cr2==1) begin                                         // clearly inside RIGHT
                        if (is_checker(rr,gg,bb)) n_bil_tex++;
                        if (is_midtone(rr,gg,bb)) n_bil_mid++;
                    end
                end
            end
        end

        // PROOFS
        if (n_near_tex < 30)  begin $error("[pb] LEFT(nearest) too few checker pixels (%0d) — not rendered (CLUT not loaded?)", n_near_tex); errors++; end
        if (n_bil_tex  < 30)  begin $error("[pb] RIGHT(bilinear) too few checker pixels (%0d) — indexed ~9cyc stall dropped the prim?", n_bil_tex); errors++; end
        if (n_near_mid != 0)  begin $error("[pb] NEAREST produced %0d midtone pixels — indexed nearest must be blocky (0)", n_near_mid); errors++; end
        if (n_bil_mid  < 10)  begin $error("[pb] BILINEAR produced only %0d midtones — CLUT-before-interp not smoothing (indices interpolated?)", n_bil_mid); 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_tile_palbilinear_demo] clears=[%0d %0d %0d %0d] flush=%0d prim2=%0b | nearTex=%0d nearMid=%0d bilTex=%0d bilMid=%0d errors=%0d",
                 clear_cw_tile[0][0],clear_cw_tile[0][1],clear_cw_tile[1][0],clear_cw_tile[1][1],
                 flush_emits, saw_prim_count_2, n_near_tex, n_near_mid, n_bil_tex, n_bil_mid, errors);
        if (errors==0) $display("[tb_top_psmct32_tile_palbilinear_demo] PASS");
        else           $display("[tb_top_psmct32_tile_palbilinear_demo] FAIL");
        $finish;
    end

    initial begin #400000000; $error("[tb_top_psmct32_tile_palbilinear_demo] TIMEOUT"); $finish; end
endmodule : tb_top_psmct32_tile_palbilinear_demo
