// retroDE_ps2 — tb_top_psmct32_tile_multiprim_demo (Ch305)
//
// MULTI-PRIMITIVE tiled-scene TB. A fixed LIST of 3 combined TME+ABE+ZTE
// primitives is re-rendered PER TILE, IN ORDER, over a 2x2 grid of 16x16
// on-chip tiles (32x32 region). All three triangles cross BOTH seams (x=16,
// y=16). Draw order: P0 opaque BLUE bg (flat Z=0x5000) -> P1 opaque RED
// (flat Z=0x6000, in front) -> P2 translucent WHITE (flat Z=0x5800, blends).
//
// PROOFS:
//   GRID  : all 4 tiles clear independently (256 color writes each) and the
//           grid flushes 4*256=1024 FB writes (one flush per tile, AFTER all
//           3 primitives composited into that tile).
//   LIST  : prim_count_r latched to 3 (the renderer processes a 3-primitive
//           list per tile, not a single primitive).
//   ORDER+DEPTH+ALPHA : the whole 32x32 scanout matches a SINGLE screen-space
//           software reference that replays P0,P1,P2 in order with an integer
//           Z-buffer (GEQUAL, write-on-pass) + source-over blend. Distinct
//           regions exercised (counted): pure BLUE (opaque bg), RED (P1 over
//           P0 = depth order), LIGHT-BLUE (P2 blended over blue = alpha + proves
//           P0 rendered), and OCCLUSION (P0&P1&P2 -> P2 FAILS vs red's Z so RED
//           shows through, NOT pink = depth occludes the blend). Seam-region
//           matches counted separately (continuity across x=15|16, y=15|16).

`timescale 1ns/1ps

module tb_top_psmct32_tile_multiprim_demo;

    localparam int H_ACTIVE = 32, V_ACTIVE = 32;
    localparam int TP_CLEAR = 1;
    localparam real MARG = 0.08;
    // primitive flat-Z + texel colors (must match bake.py Ch305 fixture)
    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)
    ) 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)
    );

    // ---- triangle classify: 1=clearly-IN, 0=clearly-OUT, 2=EDGE(skip) ----
    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

    // ---- one-channel source-over blend, mirrors gs_alpha_blend EXACTLY:
    //      Cv = clamp( ((Cs-Cd)*As >>> 7) + Cd ).  As=0x80 -> Cv=Cs (opaque). ----
    function automatic logic [7:0] blendch(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;
            blendch = t[7:0];
        end
    endfunction

    // ---- grid tracers ----
    int  clear_cw_tile [0:1][0:1];   // tile_color writes per (row,col) during CLEAR
    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;  // tile mode: only flush emits
        if (dut.u_gs.raster_active && (int'(dut.u_gs.prim_count_r)==3)) saw_prim_count_3 <= 1'b1;
    end

    // ---- scanout capture (32x32) ----
    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;
    int n_blue, n_red, n_lblue, n_occ, n_green;

    initial begin
        errors=0; n_match=0; n_check=0; n_seam=0;
        n_blue=0; n_red=0; n_lblue=0; n_occ=0; n_green=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);   // grid render begins
        @(negedge dut.u_gs.raster_active);   // all tiles done (clear+3prims+flush)
        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;

        // PROOF GRID — each tile cleared independently + total flushes.
        for (int rr=0;rr<2;rr++) for (int cc=0;cc<2;cc++)
            if (clear_cw_tile[rr][cc] != 256) begin
                $error("[tmp] 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("[tmp] flush emitted %0d (want %0d)", flush_emits, 4*256); errors++; end
        // PROOF LIST — the batch was a 3-primitive list.
        if (!saw_prim_count_3) begin $error("[tmp] prim_count_r never observed == 3 (list not formed)"); errors++; end

        // PROOF ORDER+DEPTH+ALPHA — whole 32x32 vs the 3-primitive SW replay.
        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;
                real fpx, fpy;
                fpx = real'(px); fpy = real'(py);
                c0 = tri_cls(fpx,fpy,  1, 1, 30, 1, 15,30);  // P0 blue
                c1 = tri_cls(fpx,fpy,  8, 3, 22, 3, 15,20);  // P1 red
                c2 = tri_cls(fpx,fpy,  7, 9, 23, 9, 15,28);  // P2 white
                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;                        // clear green
                    if (c0==1 && Z0>=zz) begin                          // P0 opaque blue
                        cr=blendch(0,cr,'h80); cg=blendch(0,cg,'h80); cb=blendch(255,cb,'h80); zz=Z0;
                    end
                    if (c1==1 && Z1>=zz) begin                          // P1 opaque red (in front)
                        cr=blendch(255,cr,'h80); cg=blendch(0,cg,'h80); cb=blendch(0,cb,'h80); zz=Z1;
                    end
                    if (c2==1 && Z2>=zz) begin                          // P2 translucent white (may FAIL vs red)
                        cr=blendch(255,cr,'h40); cg=blendch(255,cg,'h40); cb=blendch(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++;
                    // region tally (for proof-coverage reporting)
                    if      (er==8'h00 && eg==8'h00 && eb==8'hFF) n_blue++;
                    else if (er==8'hFF && eg==8'h00 && eb==8'h00) n_red++;
                    else if (er==8'h7F && eg==8'h7F && eb==8'hFF) n_lblue++;
                    else if (er==8'h00 && eg==8'h80 && eb==8'h00) n_green++;
                    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("[tmp] (%0d,%0d) got(%02x,%02x,%02x) exp(%02x,%02x,%02x) [c0%0d c1%0d c2%0d]",
                                              px,py,cap_r[py][px],cap_g[py][px],cap_b[py][px],er,eg,eb,c0,c1,c2);
                        errors++;
                    end
                end
            end
        end
        // occlusion count: pixels clearly-in ALL three -> must be RED (P2 occluded)
        for (int py=0; py<V_ACTIVE; py++) for (int px=0; px<H_ACTIVE; px++) begin
            real fpx2, fpy2;
            fpx2 = real'(px); fpy2 = real'(py);
            if (tri_cls(fpx2,fpy2, 1,1,30,1,15,30)==1 && tri_cls(fpx2,fpy2, 8,3,22,3,15,20)==1
             && tri_cls(fpx2,fpy2, 7,9,23,9,15,28)==1) n_occ++;
        end

        if (n_check < 200)            begin $error("[tmp] too few checkable pixels (%0d)", n_check); errors++; end
        if (n_match != n_check)       begin $error("[tmp] image match %0d/%0d (composite/depth/alpha error)", n_match, n_check); errors++; end
        if (n_seam  < 20)             begin $error("[tmp] too few SEAM matches (%0d)", n_seam); errors++; end
        if (n_blue  < 10)             begin $error("[tmp] BLUE region thin (%0d) — opaque bg not proven", n_blue); errors++; end
        if (n_red   < 10)             begin $error("[tmp] RED region thin (%0d) — depth order not proven", n_red); errors++; end
        if (n_lblue < 10)             begin $error("[tmp] LIGHT-BLUE region thin (%0d) — alpha-over-bg not proven", n_lblue); errors++; end
        if (n_occ   < 8)              begin $error("[tmp] OCCLUSION region thin (%0d) — depth occlusion not proven", n_occ); 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_multiprim_demo] clears=[%0d %0d %0d %0d] flush=%0d prim3=%0b match=%0d/%0d seam=%0d | blue=%0d red=%0d lblue=%0d occ=%0d green=%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_blue, n_red, n_lblue, n_occ, n_green, errors);
        if (errors==0) $display("[tb_top_psmct32_tile_multiprim_demo] PASS");
        else           $display("[tb_top_psmct32_tile_multiprim_demo] FAIL");
        $finish;
    end

    initial begin #300000000; $error("[tb_top_psmct32_tile_multiprim_demo] TIMEOUT"); $finish; end

endmodule : tb_top_psmct32_tile_multiprim_demo
