// retroDE_ps2 — tb_top_psmct32_tile_late_demo (Ch316)
//
// LEADING-EMPTY-TILE traversal fix. ONE primitive, ONLY in the LAST tile t15
// ([48..63]^2) of a 4x4 (64x64) grid; tiles t0..t14 are EMPTY and PRECEDE the first
// (and only) non-empty tile. Before Ch316 the leading empty tiles flushed BLACK
// (the flush row-stride used ras_fbw, which was its reset value 0 until a primitive
// loaded → all rows collapsed onto row 0). Ch316 loads the batch FRAME params
// (ras_fbw/fbp/psm) at grid-render start so EVERY tile's flush address is valid.
//
// PROOFS:
//   LEADING EMPTIES : every empty early tile (t0..t14) flushes the GREEN clear
//                     colour, NOT black (this is the regressed behaviour).
//   LATE RENDER     : t15 contains the blue triangle (the renderer reached the last
//                     tile — no premature done/idle) over its own green clear.
//   IMAGE           : whole 64x64 == reference (inside t15 triangle -> blue, else green).
//   NO BLACK        : ZERO rendered pixels are black (the bug's signature).
//   GRID            : 16 tiles cleared (256 each) + 16*256 flush; bin_n[15]==1.

`timescale 1ns/1ps

module tb_top_psmct32_tile_late_demo;

    localparam int H_ACTIVE = 64, V_ACTIVE = 64;
    localparam int TP_CLEAR = 1;
    localparam real MARG = 0.12;

    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(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(1), .BIN_BUFFER_ENABLE(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)
    );

    // the single prim — t15 triangle (matches bake.py build_tile_late_demo_payload)
    localparam int LX0=50, LY0=50, LX1=62, LY1=50, LX2=50, LY2=62;

    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 [0:15];
    int flush_emits;
    initial begin for(int t=0;t<16;t++) clear_cw[t]=0; flush_emits=0; 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 t; t=int'(dut.u_gs.tile_row_r)*4 + int'(dut.u_gs.tile_col_r);
            if (t>=0 && t<16) clear_cw[t] <= clear_cw[t] + 1;
        end
        if (dut.u_gs.raster_pixel_emit) flush_emits <= flush_emits + 1;
    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, n_match, n_check, n_blue, n_green, n_black, bn15;

    initial begin
        errors=0; n_match=0; n_check=0; n_blue=0; n_green=0; n_black=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);
        repeat(120000) @(posedge clk);                 // full-render settle
        bn15 = int'(dut.u_gs.bin_n[15]);
        @(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;

        // GRID — render reached every tile (no premature done): all cleared + flushed.
        for (int t=0;t<16;t++)
            if (clear_cw[t] != 256) begin $error("[late] tile %0d cleared %0d (want 256 — premature done?)", t, clear_cw[t]); errors++; end
        if (flush_emits != 16*256) begin $error("[late] flush emitted %0d (want %0d)", flush_emits, 16*256); errors++; end
        if (bn15 != 1)             begin $error("[late] bin_n[15]=%0d (want 1)", bn15); errors++; end

        // IMAGE — inside t15 triangle -> blue, else green; NO black anywhere.
        for (int py=0; py<V_ACTIVE; py++) begin
            for (int px=0; px<H_ACTIVE; px++) begin
                int c; logic [7:0] er,eg,eb;
                if (cap_de[py][px]) begin
                    // black-pixel detector (the regressed signature) — count regardless of expectation
                    if (cap_r[py][px]<8'h20 && cap_g[py][px]<8'h20 && cap_b[py][px]<8'h20) n_black++;
                    c = tri_cls(real'(px),real'(py), real'(LX0),real'(LY0),
                                real'(LX1),real'(LY1), real'(LX2),real'(LY2));
                    if (c==2) begin er=8'hxx;eg=8'hxx;eb=8'hxx; end                 // edge — skip
                    else if (c==1) begin er=8'h00; eg=8'h00; eb=8'hFF; end          // blue
                    else           begin er=8'h00; eg=8'h80; eb=8'h00; end          // green clear
                    if (er!==8'hxx) begin
                        n_check++;
                        if (cap_r[py][px]===er&&cap_g[py][px]===eg&&cap_b[py][px]===eb) begin
                            n_match++;
                            if (eb==8'hFF) n_blue++; else n_green++;
                        end else begin
                            if (errors<14) $error("[late] (%0d,%0d) got(%02x,%02x,%02x) exp(%02x,%02x,%02x)",px,py,cap_r[py][px],cap_g[py][px],cap_b[py][px],er,eg,eb);
                            errors++;
                        end
                    end
                end
            end
        end

        if (n_check < 1000)     begin $error("[late] too few checkable pixels (%0d)", n_check); errors++; end
        if (n_match != n_check) begin $error("[late] image match %0d/%0d", n_match, n_check); errors++; end
        if (n_black != 0)       begin $error("[late] %0d BLACK pixels — leading-empty-tile bug present", n_black); errors++; end
        if (n_blue < 20)        begin $error("[late] too few blue pixels (%0d) — t15 prim not rendered (premature done?)", n_blue); errors++; end
        if (n_green < 2000)     begin $error("[late] too few green pixels (%0d) — empty tiles not cleared", n_green); errors++; end
        if (!core_halt)         begin $error("core_halt low"); errors++; end

        $display("[tb_top_psmct32_tile_late_demo] clears_all256=%0b flush=%0d bin15=%0d match=%0d/%0d blue=%0d green=%0d black=%0d errors=%0d",
                 (clear_cw[0]==256), flush_emits, bn15, n_match, n_check, n_blue, n_green, n_black, errors);
        if (errors==0) $display("[tb_top_psmct32_tile_late_demo] PASS");
        else           $display("[tb_top_psmct32_tile_late_demo] FAIL");
        $finish;
    end

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