// retroDE_ps2 — tb_top_psmct32_tile_bin_demo (Ch311)
//
// Per-tile BIN BUFFER. Three primitives with distinct tile coverage over the 2x2
// grid; a binning pass precomputes per-tile primitive-index lists, and each tile
// renders only its bin (vs the Ch305 re-test-every-prim-per-tile path).
//   P0 blue bg  (Z=0x5000): bbox all 4 tiles  -> in every bin.
//   P1 red      (Z=0x6000): bbox tiles (0,0)+(1,0) only (2 bins).
//   P2 white    (Z=0x5800): bbox tile (1,1) only (1 bin).
// Prim indices P0=0,P1=1,P2=2. Tile index t=row*2+col: t0=(0,0) t1=(1,0) t2=(0,1) t3=(1,1).
// Expected bins: t0={0,1} t1={0,1} t2={0} t3={0,2}.
//
// PROOFS:
//   BIN CONTENTS : bin_n/bin_prim match exactly — each prim appears in EVERY
//                  overlapping tile bin, in NO non-overlapping bin, draw order kept.
//   IMAGE        : the whole 32x32 scanout matches a single screen-space re-test
//                  reference (P0,P1,P2 replayed with depth + blend) — so routing
//                  through bins produces the SAME image; seams clean.
//   GRID         : 4 tiles cleared (256 each) + 1024 flush; prim list = 3.

`timescale 1ns/1ps

module tb_top_psmct32_tile_bin_demo;

    localparam int H_ACTIVE = 32, V_ACTIVE = 32;
    localparam int TP_CLEAR = 1;
    localparam real MARG = 0.08;
    localparam int Z0 = 'h5000, Z1 = 'h6000, Z2 = 'h5800, ZCLR = 'h4000;

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

    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
    function automatic logic [7:0] sovr(input int cs, input int cd, input int aa);
        int t; begin t=((cs-cd)*aa)>>>7; t=t+cd; if(t<0)t=0; if(t>255)t=255; sovr=t[7:0]; end
    endfunction

    int  clear_cw_tile [0:1][0:1];
    int  flush_emits;
    bit  saw_prim_count_3;
    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_3=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)==3)) saw_prim_count_3 <= 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, n_match, n_check, n_seam, n_red, n_lgreen;
    // bins are stable from end-of-TP_BIN through the whole grid render and after
    // (not cleared until the next grid start). Read them with CONSTANT indices
    // post-render (iverilog cross-module array binds need constant, not loop-var).
    int bn [0:3]; int bp [0:3][0:3];
    initial begin for(int t=0;t<4;t++) begin bn[t]=0; for(int s=0;s<4;s++) bp[t][s]=0; end end

    // expected bin (prim idx) helper: returns 1 if prim p should be in tile t's bin
    // (bbox-overlap), via the same bbox-vs-tile test the hw bins on.
    function automatic bit binx(input int p, input int t);
        int xl,xh,yl,yh, tcx0,tcx1,tcy0,tcy1;
        begin
            // prim bboxes (must match bake.py Ch311 verts)
            case (p)
              0: begin xl=1;  xh=30; yl=1;  yh=30; end   // P0
              1: begin xl=8;  xh=24; yl=2;  yh=12; end   // P1
              default: begin xl=20; xh=28; yl=20; yh=28; end // P2
            endcase
            tcx0=(t%2)*16; tcx1=tcx0+15; tcy0=(t/2)*16; tcy1=tcy0+15;
            binx = !((xl>tcx1)||(xh<tcx0)||(yl>tcy1)||(yh<tcy0));
        end
    endfunction

    initial begin
        errors=0; n_match=0; n_check=0; n_seam=0; n_red=0; n_lgreen=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);
        // grab bins (constant indices; stable after binning through end-of-grid)
        bn[0]=int'(dut.u_gs.bin_n[0]); bn[1]=int'(dut.u_gs.bin_n[1]); bn[2]=int'(dut.u_gs.bin_n[2]); bn[3]=int'(dut.u_gs.bin_n[3]);
        bp[0][0]=int'(dut.u_gs.bin_prim[0][0]); bp[0][1]=int'(dut.u_gs.bin_prim[0][1]);
        bp[1][0]=int'(dut.u_gs.bin_prim[1][0]); bp[1][1]=int'(dut.u_gs.bin_prim[1][1]);
        bp[2][0]=int'(dut.u_gs.bin_prim[2][0]); bp[2][1]=int'(dut.u_gs.bin_prim[2][1]);
        bp[3][0]=int'(dut.u_gs.bin_prim[3][0]); bp[3][1]=int'(dut.u_gs.bin_prim[3][1]);
        @(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("[bn] 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("[bn] flush emitted %0d (want %0d)", flush_emits, 4*256); errors++; end
        if (!saw_prim_count_3)     begin $error("[bn] prim_count_r never == 3"); errors++; end

        // PROOF BIN CONTENTS — every overlapping prim in the bin (draw order), none else.
        for (int t=0;t<4;t++) begin
            int exp_n, sidx;
            exp_n=0;
            for (int p=0;p<3;p++) if (binx(p,t)) begin
                if (bp[t][exp_n] !== p) begin
                    $error("[bn] tile %0d bin slot %0d = prim %0d, want %0d (order/contents)", t, exp_n, bp[t][exp_n], p); errors++;
                end
                exp_n++;
            end
            if (bn[t] !== exp_n) begin $error("[bn] tile %0d bin_n=%0d, want %0d", t, bn[t], exp_n); errors++; end
        end
        $display("[bn] bins: t0(n=%0d:%0d,%0d) t1(n=%0d:%0d,%0d) t2(n=%0d:%0d) t3(n=%0d:%0d,%0d)",
                 bn[0],bp[0][0],bp[0][1], bn[1],bp[1][0],bp[1][1], bn[2],bp[2][0], bn[3],bp[3][0],bp[3][1]);

        // PROOF IMAGE — re-test reference: P0 blue (src-over opaque), P1 red (opaque),
        // P2 white (src-over translucent A=0x40), depth GEQUAL write-on-pass.
        for (int py=0; py<V_ACTIVE; py++) begin
            for (int px=0; px<H_ACTIVE; px++) begin
                int c0,c1,c2,cr,cg,cb,zz; logic [7:0] er,eg,eb;
                c0 = tri_cls(real'(px),real'(py),  1, 1, 30, 1, 15,30);
                c1 = tri_cls(real'(px),real'(py),  8, 2, 24, 2, 16,12);
                c2 = tri_cls(real'(px),real'(py), 20,20, 28,20, 20,28);
                if (c0==2||c1==2||c2==2) begin er=8'hxx;eg=8'hxx;eb=8'hxx; end
                else begin
                    cr=0;cg=128;cb=0;zz=ZCLR;
                    if (c0==1 && Z0>=zz) begin cr=sovr(0,cr,'h80);   cg=sovr(0,cg,'h80);   cb=sovr(255,cb,'h80); zz=Z0; end
                    if (c1==1 && Z1>=zz) begin cr=sovr(255,cr,'h80); cg=sovr(0,cg,'h80);   cb=sovr(0,cb,'h80);   zz=Z1; end
                    if (c2==1 && Z2>=zz) begin cr=sovr(255,cr,'h40); cg=sovr(255,cg,'h40); cb=sovr(255,cb,'h40); zz=Z2; end
                    er=cr[7:0]; eg=cg[7:0]; eb=cb[7:0];
                end
                if (er!==8'hxx && cap_de[py][px]) begin
                    n_check++;
                    if (er==8'hFF&&eg==8'h00&&eb==8'h00) n_red++;
                    if (er==8'h7F&&eg==8'hBF&&eb==8'h7F) n_lgreen++;  // P2 white over GREEN clear
                    if (cap_r[py][px]===er&&cap_g[py][px]===eg&&cap_b[py][px]===eb) begin
                        n_match++; if (px==15||px==16||py==15||py==16) n_seam++;
                    end else begin
                        if (errors<14) $error("[bn] (%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

        if (n_check < 150)      begin $error("[bn] too few checkable pixels (%0d)", n_check); errors++; end
        if (n_match != n_check) begin $error("[bn] image match %0d/%0d (bin routing error)", n_match, n_check); errors++; end
        if (n_red   < 8)        begin $error("[bn] RED region thin (%0d) — P1 not binned/rendered", n_red); errors++; end
        if (n_lgreen < 4)       begin $error("[bn] LIGHT-GREEN thin (%0d) — P2 (1-tile prim) not binned/rendered", n_lgreen); 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_bin_demo] clears=[%0d %0d %0d %0d] flush=%0d prim3=%0b match=%0d/%0d seam=%0d | red=%0d lgreen=%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_3, n_match, n_check, n_seam, n_red, n_lgreen, errors);
        if (errors==0) $display("[tb_top_psmct32_tile_bin_demo] PASS");
        else           $display("[tb_top_psmct32_tile_bin_demo] FAIL");
        $finish;
    end

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