// retroDE_ps2 — tb_top_psmct32_feeder_equiv_demo (Ch315)
//
// PRIMITIVE/BIN CAPACITY scaling. The 4x4 (64x64) grid with TILE_FIFO_DEPTH=8 and
// SEVEN prims (vs Ch312's 3). Six of them (P0..P5) fall ENTIRELY inside centre tile
// t5 (col1,row1 = x[16..31],y[16..31]); the seventh (P6) is a lone corner prim in
// t15. So:
//   bin_n[t5]  = 6  (P0..P5, in draw order)   <- DEEPER than the old FIFO depth 4
//   bin_n[t15] = 1  (P6)
//   all other 14 tiles = 0 (empty)
// One shared OPAQUE-blue texture, so a covered pixel is blue and order doesn't change
// the colour — draw order is proven instead by the BIN ORDER read-back.
//
// PROOFS:
//   CAPACITY    : bin_occ_max_r == 6 (a bin held 6 prims, > the old depth-4 limit),
//                 and raster_overflow / count / bin_overflow are ALL 0 (depth-8 FIFO
//                 swallowed all 7 with room to spare).
//   BIN COUNTS  : bin_n[t]==# prims whose bbox overlaps tile t (t5=6,t15=1,rest 0).
//   BIN ORDER   : bin_prim[5][0..5] == {0,1,2,3,4,5} (draw order preserved at depth 6).
//   IMAGE       : whole 64x64 == reference (covered-by-any-prim -> blue, else green).
//   GRID        : 16 tiles cleared (256 each) + 16*256 flush; prim list == 7.

`timescale 1ns/1ps

module tb_top_psmct32_feeder_equiv_demo;

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

    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(4), .TILE_FIFO_DEPTH(8),
        .BIN_BUFFER_ENABLE(1'b1), .HEARTBEAT_SPLICE_ENABLE(1'b0),
        .FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(256)
    ) 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),
        // Ch330 Brick 4 feeder ports — unused here (staging via $readmemh, single list, no retrigger).
        .feeder_stg_we_i(1'b0), .feeder_stg_waddr_i(12'd0), .feeder_stg_wdata_i(64'd0),
        .feeder_go_i(1'b0),
        .feeder_ready_o(), .feeder_records_o(), .feeder_waits_o()
    );

    // the 7 prim vertex tables (must match bake.py CAP_PRIMS)
    int vx [0:NPRIM-1][0:2];
    int vy [0:NPRIM-1][0:2];
    initial begin
        // iverilog-12: no array-slice '{} assignment — set elements individually.
        // P0..P5 are SIX IDENTICAL t0 triangles (match bake.py CAP_T0); P6 = corner t15.
        for (int p=0;p<NPRIM;p++) begin
            vx[p][0]=1; vx[p][1]=14; vx[p][2]=1;     // 4 combined tris, tile t0
            vy[p][0]=1; vy[p][1]=1;  vy[p][2]=14;
        end
    end

    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

    // expected: prim p's bbox overlaps tile t? (bboxes derived from the verts)
    function automatic bit binx(input int p, input int t);
        int xl,xh,yl,yh,tx0,tx1,ty0,ty1;
        begin
            xl=vx[p][0]; xh=vx[p][0]; yl=vy[p][0]; yh=vy[p][0];
            for (int k=1;k<3;k++) begin
                if (vx[p][k]<xl) xl=vx[p][k]; if (vx[p][k]>xh) xh=vx[p][k];
                if (vy[p][k]<yl) yl=vy[p][k]; if (vy[p][k]>yh) yh=vy[p][k];
            end
            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_4;
    initial begin for(int t=0;t<16;t++) clear_cw[t]=0; flush_emits=0; saw_prim_count_4=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;
        if (dut.u_gs.raster_active && (int'(dut.u_gs.prim_count_r)==4)) saw_prim_count_4 <= 1'b1;
    end

    // Ch330 — load the feeder staging (the 4-tri list) into the bram-top's staging RAM.
    initial $readmemh(`FEEDER_STG_IMAGE_FILE, dut.g_feeder.feeder_stg);

    // Ch330 phase monitors (Codex acceptance): the bootlet's TRX/IMAGE upload must complete
    // in phase 0 BEFORE the feeder takes the bus, and the feeder must not emit before handoff.
    bit   xfer_went_high, xfer_low_before_feeder, feeder_emit_before_handoff, handoff_seen;
    logic feeder_owns_d;
    always_ff @(posedge clk) if (rst_n) begin
        feeder_owns_d <= dut.feeder_owns_bus;
        if (dut.xfer_busy) xfer_went_high <= 1'b1;
        if (!dut.feeder_owns_bus && dut.feeder_gif_reg_wr_en) feeder_emit_before_handoff <= 1'b1;
        if (dut.feeder_owns_bus && !feeder_owns_d) begin            // handoff 0->1
            handoff_seen <= 1'b1;
            if (xfer_went_high && !dut.xfer_busy) xfer_low_before_feeder <= 1'b1;
        end
    end
    initial begin xfer_went_high=0; xfer_low_before_feeder=0; feeder_emit_before_handoff=0; handoff_seen=0; 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;
    int bnv [0:15];
    int b5 [0:5];           // t5's six bin slots
    int occ_max, ovf_count; logic ovf, bin_ovf;

    initial begin
        errors=0; n_match=0; n_check=0; n_blue=0; n_green=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);
        // Big settle: the 7-prim render (6 prims deep in tile t5, across 16 tiles)
        // takes far longer than the 3-prim scenes, and raster_active can dip between
        // phases — wait a generous fixed window so the WHOLE frame is flushed before
        // sampling bins + arming the scanout capture (avoids a mid-render race).
        repeat(120000) @(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[0][0]); b5[1]=int'(dut.u_gs.bin_prim[0][1]);
        b5[2]=int'(dut.u_gs.bin_prim[0][2]); b5[3]=int'(dut.u_gs.bin_prim[0][3]);
        b5[4]=int'(dut.u_gs.bin_prim[0][4]); b5[5]=int'(dut.u_gs.bin_prim[0][5]);
        occ_max   = int'(dut.u_gs.bin_occ_max_r);
        ovf_count = int'(dut.u_gs.raster_overflow_count_r);
        ovf       = dut.u_gs.raster_overflow;
        bin_ovf   = dut.u_gs.bin_overflow_r;
        @(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("[cap] tile %0d cleared %0d (want 256)", t, clear_cw[t]); errors++; end
        if (flush_emits != 16*256) begin $error("[cap] flush emitted %0d (want %0d)", flush_emits, 16*256); errors++; end
        if (!saw_prim_count_4)     begin $error("[feeder] prim_count_r never == 4 (feeder pushed 4 prims)"); errors++; end

        // PROOF — the FEEDER drove 4 combined-TAZ prims into the grid.
        if (occ_max != 4)   begin $error("[feeder] bin_occ_max=%0d (want 4)", occ_max); errors++; end
        if (ovf)            begin $error("[feeder] raster_overflow set"); errors++; end
        if (ovf_count != 0) begin $error("[feeder] overflow_count=%0d (want 0)", ovf_count); errors++; end
        if (bin_ovf)        begin $error("[feeder] bin_overflow set"); errors++; end

        // PROOF PHASE HANDOFF (Codex acceptance):
        if (!handoff_seen)              begin $error("[feeder] feeder never took the bus (no phase handoff)"); errors++; end
        if (!xfer_went_high)            begin $error("[feeder] TRX/IMAGE upload never ran in phase 0 (xfer_busy never high)"); errors++; end
        if (!xfer_low_before_feeder)    begin $error("[feeder] texture upload not committed before feeder start (xfer_busy not low at handoff)"); errors++; end
        if (feeder_emit_before_handoff) begin $error("[feeder] feeder emitted gif_reg_* BEFORE owning the bus"); errors++; end

        // PROOF BIN COUNTS
        for (int t=0;t<16;t++) begin
            int exp_n; exp_n=0;
            for (int p=0;p<NPRIM;p++) if (binx(p,t)) exp_n++;
            if (bnv[t] !== exp_n) begin $error("[cap] tile %0d bin_n=%0d, want %0d", t, bnv[t], exp_n); errors++; end
        end
        // PROOF BIN ORDER — t0 holds the feeder's 4 prims in emit order.
        for (int s=0;s<4;s++)
            if (b5[s] !== s) begin $error("[feeder] tile0 slot %0d = prim %0d (want %0d)", s, b5[s], s); errors++; end
        $display("[cap] bin_n[0..15]=%0d%0d%0d%0d %0d%0d%0d%0d %0d%0d%0d%0d %0d%0d%0d%0d  t5={%0d%0d%0d%0d%0d%0d} occ_max=%0d ovf=%0b cnt=%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],b5[2],b5[3],b5[4],b5[5],occ_max,ovf,ovf_count);

        // PROOF IMAGE — covered by ANY prim -> opaque blue; else green clear.
        for (int py=0; py<V_ACTIVE; py++) begin
            for (int px=0; px<H_ACTIVE; px++) begin
                bit is_in, is_bnd; logic [7:0] er,eg,eb;
                is_in=0; is_bnd=0;
                for (int p=0;p<NPRIM;p++) begin
                    int c; c = tri_cls(real'(px),real'(py),
                                       real'(vx[p][0]),real'(vy[p][0]),
                                       real'(vx[p][1]),real'(vy[p][1]),
                                       real'(vx[p][2]),real'(vy[p][2]));
                    if (c==1) is_in=1;
                    if (c==2) is_bnd=1;
                end
                if (is_bnd && !is_in) begin er=8'hxx;eg=8'hxx;eb=8'hxx; end  // skip ambiguous edge
                else if (is_in) 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 && cap_de[py][px]) 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("[cap] (%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("[cap] too few checkable pixels (%0d)", n_check); errors++; end
        if (n_match != n_check) begin $error("[cap] image match %0d/%0d", n_match, n_check); errors++; end
        if (n_blue < 30)        begin $error("[cap] too few blue pixels (%0d) — prims not rendered", n_blue); errors++; end
        if (n_green < 200)      begin $error("[cap] too few green pixels (%0d) — empty tiles not clear", n_green); errors++; end
        if (!core_halt)         begin $error("core_halt low"); errors++; end

        $display("[tb_top_psmct32_feeder_equiv_demo] handoff=%0b xfer_hi=%0b xfer_lo_pre=%0b feeder_early=%0b occ_max=%0d match=%0d/%0d blue=%0d green=%0d errors=%0d",
                 handoff_seen, xfer_went_high, xfer_low_before_feeder, feeder_emit_before_handoff, occ_max, n_match, n_check, n_blue, n_green, errors);
        if (errors==0) $display("[tb_top_psmct32_feeder_equiv_demo] PASS");
        else           $display("[tb_top_psmct32_feeder_equiv_demo] FAIL");
        $finish;
    end

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