// retroDE_ps2 — tb_top_psmct32_tile_psmct16fb_demo (Ch313)
//
// FULL PSMCT16 FRAMEBUFFER MODE. The same Ch312 4x4 (64x64) bin-buffer scene, but
// the WHOLE framebuffer is PSMCT16: FRAME_1.PSM=PSMCT16, so the relaxed
// close_combined gate must accept a PSMCT16 dest (it does, because
// TILE_COLOR_PSMCT16=1). The on-chip tile RAM is 16-bit, dest-color reads for
// blending unpack RGB5A1, the flush writes 16-bit halfword lanes (be=4'b0011,
// psm=0x02), and DISPFB1.PSM=PSMCT16 scans it out. The textures stay PSMCT32 DECAL.
// A 64x64 PSMCT16 FB is 8 KiB (HALF the 16 KiB PSMCT32 FB) -> 16 KiB VRAM (vs Ch312's
// 32 KiB). Same three prims:
//   P0 blue (Z5000): top-left,  bbox t0,1,4,5            (4 tiles)
//   P1 red  (Z6000): mid, crosses seams, bbox t5,6,7,9,10,11 (6 tiles)
//   P2 white(Z5800): bottom-right corner, t15 ONLY       (1 tile)
//   EMPTY t2,3,8,12,13,14 -> stay clear (quantized green).
//
// PROOFS:
//   BIN COUNTS  : bin_n[t] for ALL 16 tiles == # prims whose bbox overlaps tile t
//                 (binning is format-independent; identical to Ch312).
//   BIN ORDER   : t5 (the one multi-prim tile) lists {P0,P1} in draw order.
//   PSMCT16 FLUSH: every flush emit carries psm=0x02 + be=4'b0011 (16-bit lanes);
//                 NO PSMCT32 (psm=0x00) flush ever occurs -> the whole FB is 16-bit.
//   IMAGE       : whole 64x64 matches the screen-space re-test reference replayed
//                 with PER-STEP 5-bit quantization q5(c)=(c&0xF8)|(c>>5) — EXACT, not
//                 a tolerance — proving pack-on-write, unpack-for-blend, and PSMCT16
//                 scanout round-trip, with seam continuity across x=16/32/48 & y=16/32.
//   QUANTIZED   : MANY matched pixels differ from the un-quantized PSMCT32 value
//                 (e.g. clear green 0x80->0x84), and the capture matches the QUANTIZED
//                 one — direct evidence the framebuffer really is RGB5A1, not PSMCT32.

`timescale 1ns/1ps

module tb_top_psmct32_tile_psmct16fb_demo;

    localparam int H_ACTIVE = 64, V_ACTIVE = 64;
    localparam real MARG = 0.10;
    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(4), .TILE_ROWS(4),
        .TILE_MULTIPRIM(1'b1), .TILE_PRIM_COUNT(3), .BIN_BUFFER_ENABLE(1'b1),
        .TILE_COLOR_PSMCT16(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
    // PSMCT16 5-bit quantization: keep top 5 bits, replicate top 3 into the low 3
    // (== the pack-to-RGB5A1 then unpack-on-scanout round trip). Idempotent.
    function automatic logic [7:0] q5(input logic [7:0] c);
        q5 = (c & 8'hF8) | (c >> 5);
    endfunction
    // prim p's bbox overlaps tile t? (bboxes match bake.py Ch313 verts == Ch312)
    function automatic bit binx(input int p, input int t);
        int xl,xh,yl,yh,tx0,tx1,ty0,ty1;
        begin
            case (p)
              0: begin xl=2;  xh=30; yl=2;  yh=30; end  // P0
              1: begin xl=20; xh=50; yl=18; yh=40; end  // P1
              default: begin xl=52; xh=60; yl=52; yh=60; end // P2
            endcase
            tx0=(t%4)*16; tx1=tx0+15; ty0=(t/4)*16; ty1=ty0+15;
            binx = !((xl>tx1)||(xh<tx0)||(yl>ty1)||(yh<ty0));
        end
    endfunction

    int clear_cw [0:15];
    int flush_emits; bit saw_prim_count_3;
    int flush_psmct16, flush_psmct32, flush_be_bad;   // PSMCT16-flush proof counters
    initial begin
        for(int t=0;t<16;t++) clear_cw[t]=0;
        flush_emits=0; saw_prim_count_3=0; flush_psmct16=0; flush_psmct32=0; flush_be_bad=0;
    end
    always_ff @(posedge clk) if (rst_n) begin
        if (dut.u_gs.tile_phase==2'd1 /*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) begin
            flush_emits <= flush_emits + 1;
            // The flush emit register carries the FB write format. PSMCT16 mode must
            // emit psm=0x02 + be=4'b0011 on EVERY flush; psm=0x00 must never appear.
            if (dut.u_gs.raster_pixel_psm_q == 6'h02) flush_psmct16 <= flush_psmct16 + 1;
            if (dut.u_gs.raster_pixel_psm_q == 6'h00) flush_psmct32 <= flush_psmct32 + 1;
            if (dut.u_gs.raster_pixel_be_q  != 4'b0011) flush_be_bad <= flush_be_bad + 1;
        end
        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_empty_chk, n_quant;
    int bnv [0:15];
    int b5_0, b5_1;

    initial begin
        errors=0; n_match=0; n_check=0; n_seam=0; n_empty_chk=0; n_quant=0;
        for(int t=0;t<16;t++) bnv[t]=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);
        bnv[0]=int'(dut.u_gs.bin_n[0]);   bnv[1]=int'(dut.u_gs.bin_n[1]);
        bnv[2]=int'(dut.u_gs.bin_n[2]);   bnv[3]=int'(dut.u_gs.bin_n[3]);
        bnv[4]=int'(dut.u_gs.bin_n[4]);   bnv[5]=int'(dut.u_gs.bin_n[5]);
        bnv[6]=int'(dut.u_gs.bin_n[6]);   bnv[7]=int'(dut.u_gs.bin_n[7]);
        bnv[8]=int'(dut.u_gs.bin_n[8]);   bnv[9]=int'(dut.u_gs.bin_n[9]);
        bnv[10]=int'(dut.u_gs.bin_n[10]); bnv[11]=int'(dut.u_gs.bin_n[11]);
        bnv[12]=int'(dut.u_gs.bin_n[12]); bnv[13]=int'(dut.u_gs.bin_n[13]);
        bnv[14]=int'(dut.u_gs.bin_n[14]); bnv[15]=int'(dut.u_gs.bin_n[15]);
        b5_0=int'(dut.u_gs.bin_prim[5][0]); b5_1=int'(dut.u_gs.bin_prim[5][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;

        // PROOF GRID
        for (int t=0;t<16;t++)
            if (clear_cw[t] != 256) begin $error("[f16] tile %0d cleared %0d (want 256)", t, clear_cw[t]); errors++; end
        if (flush_emits != 16*256) begin $error("[f16] flush emitted %0d (want %0d)", flush_emits, 16*256); errors++; end
        if (!saw_prim_count_3)     begin $error("[f16] prim_count_r never == 3"); errors++; end

        // PROOF PSMCT16 FLUSH — every flush emit is a 16-bit halfword write, NONE PSMCT32.
        if (flush_psmct16 != flush_emits) begin $error("[f16] PSMCT16 flushes %0d != emits %0d", flush_psmct16, flush_emits); errors++; end
        if (flush_psmct32 != 0)           begin $error("[f16] %0d PSMCT32 flushes leaked (FB not fully 16-bit)", flush_psmct32); errors++; end
        if (flush_be_bad != 0)            begin $error("[f16] %0d flushes with be!=4'b0011", flush_be_bad); errors++; end

        // PROOF BIN COUNTS
        for (int t=0;t<16;t++) begin
            int exp_n; exp_n=0;
            for (int p=0;p<3;p++) if (binx(p,t)) exp_n++;
            if (bnv[t] !== exp_n) begin $error("[f16] tile %0d bin_n=%0d, want %0d", t, bnv[t], exp_n); errors++; end
        end
        if (b5_0 !== 0 || b5_1 !== 1) begin $error("[f16] tile5 bin = {%0d,%0d}, want {0,1}", b5_0, b5_1); errors++; end
        $display("[f16] bin_n[0..15]=%0d%0d%0d%0d %0d%0d%0d%0d %0d%0d%0d%0d %0d%0d%0d%0d  t5={%0d,%0d}",
                 bnv[0],bnv[1],bnv[2],bnv[3],bnv[4],bnv[5],bnv[6],bnv[7],bnv[8],bnv[9],bnv[10],bnv[11],bnv[12],bnv[13],bnv[14],bnv[15],b5_0,b5_1);

        // PROOF IMAGE — re-test reference with PER-STEP RGB5A1 quantization. Each blend
        // step writes the quantized dest back to the 16-bit tile RAM, so the next
        // blend reads the quantized value; q5 after every step models that exactly.
        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; int rr,rg,rb; logic [7:0] er,eg,eb; bit scene_here, in_empty;
                c0 = tri_cls(real'(px),real'(py),  2, 2, 30, 2, 2,30);
                c1 = tri_cls(real'(px),real'(py), 20,18, 50,18, 35,40);
                c2 = tri_cls(real'(px),real'(py), 52,52, 60,52, 52,60);
                if (c0==2||c1==2||c2==2) begin er=8'hxx;eg=8'hxx;eb=8'hxx; end
                else begin
                    scene_here = (c0==1)||(c1==1)||(c2==1);
                    in_empty = !scene_here;
                    // unquantized PSMCT32 reference (rr/rg/rb) tracked in parallel to
                    // witness where 16-bit quantization actually changes the result.
                    cr=q5(8'h00); cg=q5(8'h80); cb=q5(8'h00); zz=ZCLR;  // quantized clear green
                    rr=8'h00;     rg=8'h80;     rb=8'h00;
                    if (c0==1 && Z0>=zz) begin
                        cr=q5(sovr(0,cr,'h80));   cg=q5(sovr(0,cg,'h80));   cb=q5(sovr(255,cb,'h80)); zz=Z0;
                        rr=sovr(0,rr,'h80);       rg=sovr(0,rg,'h80);       rb=sovr(255,rb,'h80);
                    end
                    if (c1==1 && Z1>=zz) begin
                        cr=q5(sovr(255,cr,'h80)); cg=q5(sovr(0,cg,'h80));   cb=q5(sovr(0,cb,'h80));   zz=Z1;
                        rr=sovr(255,rr,'h80);     rg=sovr(0,rg,'h80);       rb=sovr(0,rb,'h80);
                    end
                    if (c2==1 && Z2>=zz) begin
                        cr=q5(sovr(255,cr,'h40)); cg=q5(sovr(255,cg,'h40)); cb=q5(sovr(255,cb,'h40)); zz=Z2;
                        rr=sovr(255,rr,'h40);     rg=sovr(255,rg,'h40);     rb=sovr(255,rb,'h40);
                    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 (in_empty && er==8'h00&&eg==8'h84&&eb==8'h00) n_empty_chk++;
                    if (cap_r[py][px]===er&&cap_g[py][px]===eg&&cap_b[py][px]===eb) begin
                        n_match++;
                        // quantization witness: this matched pixel would have been
                        // DIFFERENT in a PSMCT32 FB (proves the FB is genuinely 16-bit).
                        if (er!==rr[7:0] || eg!==rg[7:0] || eb!==rb[7:0]) n_quant++;
                        if (px==15||px==16||px==31||px==32||px==47||px==48||py==15||py==16||py==31||py==32) n_seam++;
                    end else begin
                        if (errors<14) $error("[f16] (%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 < 400)      begin $error("[f16] too few checkable pixels (%0d)", n_check); errors++; end
        if (n_match != n_check) begin $error("[f16] image match %0d/%0d (PSMCT16-FB round-trip error)", n_match, n_check); errors++; end
        if (n_empty_chk < 100)  begin $error("[f16] too few empty-tile quantized-green pixels (%0d)", n_empty_chk); errors++; end
        if (n_quant < 100)      begin $error("[f16] too few quantized-witness pixels (%0d) — FB may not be 16-bit", n_quant); errors++; end
        if (n_seam < 40)        begin $error("[f16] too few SEAM matches (%0d)", n_seam); 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_psmct16fb_demo] flush=%0d psm16=%0d psm32=%0d be_bad=%0d prim3=%0b match=%0d/%0d seam=%0d empty=%0d quant=%0d errors=%0d",
                 flush_emits, flush_psmct16, flush_psmct32, flush_be_bad, saw_prim_count_3, n_match, n_check, n_seam, n_empty_chk, n_quant, errors);
        if (errors==0) $display("[tb_top_psmct32_tile_psmct16fb_demo] PASS");
        else           $display("[tb_top_psmct32_tile_psmct16fb_demo] FAIL");
        $finish;
    end

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