// tb_gs_prim_list_feeder — Ch330 Brick 1
// Proves the feeder expands a normalized combined-TAZ triangle list into the exact
// gif_reg_* write sequence, and that the prim-completing vertex pauses on fifo_full.

`timescale 1ns/1ps

module tb_gs_prim_list_feeder;

    localparam int STG_ADDR_W = 12;

    logic clk = 0; always #5 clk = ~clk;
    logic rst_n;

    logic                   start, busy, done;
    logic [STG_ADDR_W-1:0]  stg_rd_addr;
    logic [63:0]            stg_rd_data;
    logic                   fifo_full;
    logic                   gif_reg_wr_en;
    logic [7:0]             gif_reg_num;
    logic [63:0]            gif_reg_data;

    localparam logic [7:0] REG_PRIM=8'h00, REG_RGBAQ=8'h01, REG_UV=8'h03, REG_XYZ2=8'h05,
                           REG_TEX0=8'h06, REG_ALPHA=8'h42, REG_TEST=8'h47, REG_CLAMP=8'h48,
                           REG_FRAME=8'h4C, REG_ZBUF=8'h4E;

    // ---- staging RAM (synchronous read) ----
    logic [63:0] stg [0:127];
    always_ff @(posedge clk) stg_rd_data <= stg[stg_rd_addr[6:0]];

    localparam logic [63:0] D_FRAME=64'h00F0, D_ALPHA=64'h00A0, D_TEST=64'h0070,
                            D_ZBUF=64'h00B0, D_TEX0=64'h0060, D_CLAMP=64'h0000_07fc_007f_c00a,
                            D_PRIM=64'h0003;
    function automatic logic [63:0] vtx_sentinel(input int ti, input int v, input int w);
        vtx_sentinel = 64'h1000_0000 * (ti+1) + 64'h100 * v + w;
    endfunction

    localparam int NTRI = 2;
    initial begin
        for (int i=0;i<128;i++) stg[i] = 64'd0;
        stg[0] = NTRI;
        stg[1]=D_FRAME; stg[2]=D_ALPHA; stg[3]=D_TEST; stg[4]=D_ZBUF; stg[5]=D_TEX0; stg[6]=D_PRIM;
        for (int t=0;t<NTRI;t++)
            for (int v=0;v<3;v++) begin
                stg[7 + 9*t + 3*v + 0] = vtx_sentinel(t,v,0);
                stg[7 + 9*t + 3*v + 1] = vtx_sentinel(t,v,1);
                stg[7 + 9*t + 3*v + 2] = vtx_sentinel(t,v,2);
            end
    end

    gs_prim_list_feeder #(.STG_ADDR_W(STG_ADDR_W)) dut (
        .clk(clk), .rst_n(rst_n), .start(start), .busy(busy), .done(done),
        .stg_rd_addr(stg_rd_addr), .stg_rd_data(stg_rd_data), .fifo_full(fifo_full),
        .gif_reg_wr_en(gif_reg_wr_en), .gif_reg_num(gif_reg_num), .gif_reg_data(gif_reg_data));

    // ---- capture emitted writes ----
    logic [7:0]  cap_num  [0:63];
    logic [63:0] cap_data [0:63];
    int          cap_n;
    always_ff @(posedge clk) if (rst_n && gif_reg_wr_en) begin
        cap_num[cap_n]  <= gif_reg_num;
        cap_data[cap_n] <= gif_reg_data;
        cap_n           <= cap_n + 1;
    end

    int errors;
    logic [7:0] exp_num [0:63]; logic [63:0] exp_data [0:63]; int exp_n;
    task automatic push_exp(input logic [7:0] n, input logic [63:0] d);
        exp_num[exp_n]=n; exp_data[exp_n]=d; exp_n++;
    endtask

    // setup(5) + PRIM(1) + v0/v1/v2 RGBAQ/UV(8) before the FIRST completing XYZ2 = 14 emits held.
    localparam int EMITS_BEFORE_FIRST_COMPLETING = 14;
    localparam int EMITS_TOTAL = 5 + NTRI*(1+9);   // 25

    int cap_snap;

    initial begin
        errors=0; cap_n=0; exp_n=0; start=0; fifo_full=0;
        rst_n=0; repeat(4) @(posedge clk); rst_n=1; repeat(2) @(posedge clk);

        push_exp(REG_FRAME,D_FRAME); push_exp(REG_ALPHA,D_ALPHA); push_exp(REG_TEST,D_TEST);
        push_exp(REG_ZBUF,D_ZBUF);   push_exp(REG_TEX0,D_TEX0);
        for (int t=0;t<NTRI;t++) begin
            push_exp(REG_PRIM,D_PRIM);
            for (int v=0;v<3;v++) begin
                push_exp(REG_RGBAQ, vtx_sentinel(t,v,0));
                push_exp(REG_UV,    vtx_sentinel(t,v,1));
                push_exp(REG_XYZ2,  vtx_sentinel(t,v,2));
            end
        end

        // ===== PROOF 1 — exact sequence, no backpressure =====
        fifo_full=0;
        @(negedge clk); start=1; @(negedge clk); start=0;
        wait (done==1'b1); repeat(2) @(posedge clk);
        if (cap_n != exp_n) begin $error("[feeder] emit count %0d, expected %0d", cap_n, exp_n); errors++; end
        for (int i=0;i<exp_n && i<cap_n;i++)
            if (cap_num[i]!==exp_num[i] || cap_data[i]!==exp_data[i]) begin
                $error("[feeder] emit %0d: got (num=%02x data=%016x) exp (num=%02x data=%016x)",
                       i, cap_num[i], cap_data[i], exp_num[i], exp_data[i]); errors++;
            end

        // ===== PROOF 2 — fifo_full PAUSES at the first completing XYZ2 =====
        cap_n=0; repeat(2) @(posedge clk);
        fifo_full=1;                                   // full for the whole approach
        @(negedge clk); start=1; @(negedge clk); start=0;
        // let it run up to (and hold at) the first completing kick
        repeat(120) @(posedge clk);
        cap_snap = cap_n;
        if (cap_snap != EMITS_BEFORE_FIRST_COMPLETING)
            begin $error("[feeder] under fifo_full emitted %0d (expected to hold at %0d)", cap_snap, EMITS_BEFORE_FIRST_COMPLETING); errors++; end
        if (done || !busy) begin $error("[feeder] not paused (done=%0b busy=%0b) under fifo_full", done, busy); errors++; end
        repeat(60) @(posedge clk);                     // stays held while full
        if (cap_n != cap_snap) begin $error("[feeder] advanced past the completing kick while fifo_full (%0d -> %0d)", cap_snap, cap_n); errors++; end
        // release → completes
        fifo_full=0;
        wait (done==1'b1); repeat(2) @(posedge clk);
        if (cap_n != EMITS_TOTAL) begin $error("[feeder] after release emitted %0d (expected %0d)", cap_n, EMITS_TOTAL); errors++; end

        // ===== PROOF 3 — Ch402 optional CLAMP header and word-8 vertex base =====
        cap_n=0; exp_n=0;
        stg[0]=NTRI|(64'h1<<34); stg[6]=D_CLAMP; stg[7]=D_PRIM;
        for (int t=0;t<NTRI;t++)
            for (int v=0;v<3;v++) begin
                stg[8 + 9*t + 3*v + 0] = vtx_sentinel(t,v,0);
                stg[8 + 9*t + 3*v + 1] = vtx_sentinel(t,v,1);
                stg[8 + 9*t + 3*v + 2] = vtx_sentinel(t,v,2);
            end
        push_exp(REG_FRAME,D_FRAME); push_exp(REG_ALPHA,D_ALPHA); push_exp(REG_TEST,D_TEST);
        push_exp(REG_ZBUF,D_ZBUF);   push_exp(REG_TEX0,D_TEX0); push_exp(REG_CLAMP,D_CLAMP);
        for (int t=0;t<NTRI;t++) begin
            push_exp(REG_PRIM,D_PRIM);
            for (int v=0;v<3;v++) begin
                push_exp(REG_RGBAQ, vtx_sentinel(t,v,0));
                push_exp(REG_UV,    vtx_sentinel(t,v,1));
                push_exp(REG_XYZ2,  vtx_sentinel(t,v,2));
            end
        end
        @(negedge clk); start=1; @(negedge clk); start=0;
        wait (done==1'b1); repeat(2) @(posedge clk);
        if (cap_n != exp_n) begin $error("[feeder-clamp] emit count %0d, expected %0d", cap_n, exp_n); errors++; end
        for (int i=0;i<exp_n && i<cap_n;i++)
            if (cap_num[i]!==exp_num[i] || cap_data[i]!==exp_data[i]) begin
                $error("[feeder-clamp] emit %0d: got (num=%02x data=%016x) exp (num=%02x data=%016x)",
                       i, cap_num[i], cap_data[i], exp_num[i], exp_data[i]); errors++;
            end

        // ===== PROOF 4 — GS per-vertex FOG: PRIM.FGE=1 tags the completing
        // vertex commit as XYZF2 (0x04) instead of XYZ2 (0x05); the RGBAQ/UV
        // words and the commit DATA (fog byte included) are byte-unchanged. =====
        cap_n=0; exp_n=0;
        // Restore the legacy (non-clamp) header layout and set PRIM.FGE (bit 5).
        for (int i=0;i<128;i++) stg[i] = 64'd0;
        stg[0]=NTRI;
        stg[1]=D_FRAME; stg[2]=D_ALPHA; stg[3]=D_TEST; stg[4]=D_ZBUF; stg[5]=D_TEX0;
        stg[6]=D_PRIM | (64'h1<<5);                    // PRIM + FGE
        for (int t=0;t<NTRI;t++)
            for (int v=0;v<3;v++) begin
                stg[7 + 9*t + 3*v + 0] = vtx_sentinel(t,v,0);
                stg[7 + 9*t + 3*v + 1] = vtx_sentinel(t,v,1);
                // commit word carries a distinct fog byte in [63:56].
                stg[7 + 9*t + 3*v + 2] = vtx_sentinel(t,v,2) | (64'h9A<<56);
            end
        push_exp(REG_FRAME,D_FRAME); push_exp(REG_ALPHA,D_ALPHA); push_exp(REG_TEST,D_TEST);
        push_exp(REG_ZBUF,D_ZBUF);   push_exp(REG_TEX0,D_TEX0);
        for (int t=0;t<NTRI;t++) begin
            push_exp(REG_PRIM, D_PRIM | (64'h1<<5));
            for (int v=0;v<3;v++) begin
                push_exp(REG_RGBAQ, vtx_sentinel(t,v,0));
                push_exp(REG_UV,    vtx_sentinel(t,v,1));
                push_exp(8'h04 /*XYZF2*/, vtx_sentinel(t,v,2) | (64'h9A<<56));
            end
        end
        @(negedge clk); start=1; @(negedge clk); start=0;
        wait (done==1'b1); repeat(2) @(posedge clk);
        if (cap_n != exp_n) begin $error("[feeder-fge] emit count %0d, expected %0d", cap_n, exp_n); errors++; end
        for (int i=0;i<exp_n && i<cap_n;i++)
            if (cap_num[i]!==exp_num[i] || cap_data[i]!==exp_data[i]) begin
                $error("[feeder-fge] emit %0d: got (num=%02x data=%016x) exp (num=%02x data=%016x)",
                       i, cap_num[i], cap_data[i], exp_num[i], exp_data[i]); errors++;
            end
        // Explicitly confirm the PRIM word kept its FGE bit and the commit reg is 0x04.
        if ((cap_data[5] & (64'h1<<5)) == 0) begin $error("[feeder-fge] PRIM lost FGE bit"); errors++; end
        if (cap_num[8] !== 8'h04) begin $error("[feeder-fge] completing vtx reg=%02x, expected 04 (XYZF2)", cap_num[8]); errors++; end
        if (cap_data[8][63:56] !== 8'h9A) begin $error("[feeder-fge] fog byte lost: got %02x", cap_data[8][63:56]); errors++; end

        $display("[tb_gs_prim_list_feeder] emits=%0d held@%0d errors=%0d", EMITS_TOTAL, EMITS_BEFORE_FIRST_COMPLETING, errors);
        if (errors==0) $display("[tb_gs_prim_list_feeder] PASS");
        else           $display("[tb_gs_prim_list_feeder] FAIL");
        $finish;
    end

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