// retroDE_ps2 — tb_gs_persp_uv_farw (Ch351 far-W precision canary)
//
// Regression-committed protection for the Ch351 widened-perspective fix (the SH3 crop TB that found it is
// dump-derived and cannot be committed). Demonstrates the EXACT failure + fix mechanism on gs_persp_uv:
//
//   For a FAR surface (large w), the perspective denominator q = (1/w) << FRAC collapses under FRAC=12:
//   e.g. w=4096 -> q = 4096/4096 = 1. u = uq/q then has NO sub-texel precision and the recovered texel is
//   wildly wrong for a 512-wide texture. Widening the fixed-point scale (here FRAC=22) keeps q in the
//   hundreds/thousands, and the wider reciprocal LUT (RECIP_IDX_BITS=11) recovers the texel to <= 1.
//
// Two instances, same far-W (u,v,w) sweep over a 512-texel texture (TEXEL_MAX=2047):
//   dut_narrow : RECIP_IDX_BITS=8,  fed FRAC=12 attributes  -> MUST collapse (many texels >1 off).  [the bug]
//   dut_wide   : RECIP_IDX_BITS=11, fed FRAC=22 attributes  -> MUST stay <=1 texel for every sample.  [the fix]
// Asserting BOTH makes the test non-trivial: it proves the failure is real at FRAC=12 AND that the widened
// profile fixes it. The default RECIP_IDX_BITS=8 path itself is unchanged (proven byte-identical elsewhere);
// this canary guards the *widened* profile + documents the far-W collapse it cures.
`timescale 1ns/1ps

module tb_gs_persp_uv_farw;
    localparam int ATTR_W=24, Q_W=24, SCALE=24, TEXEL_W=11, TEXEL_MAX=2047;
    localparam int FRAC_N=12, FRAC_W=22;   // narrow (collapses far-W) vs widened

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

    logic              in_valid;
    logic [ATTR_W-1:0] uqn, vqn; logic [Q_W-1:0] qn;   // narrow-path inputs (FRAC=12)
    logic [ATTR_W-1:0] uqw, vqw; logic [Q_W-1:0] qw;   // wide-path inputs   (FRAC=22)
    logic ovn, ovw; logic [TEXEL_W-1:0] un, vn, uw, vw;

    gs_persp_uv #(.ATTR_W(ATTR_W),.Q_W(Q_W),.FRAC(FRAC_N),.SCALE(SCALE),
                  .RECIP_W(25),.TEXEL_W(TEXEL_W),.TEXEL_MAX(TEXEL_MAX),.RECIP_IDX_BITS(8)) dut_narrow (
        .clk(clk),.rst_n(rst_n),.in_valid(in_valid),.uq(uqn),.vq(vqn),.q(qn),.out_valid(ovn),.u(un),.v(vn));
    gs_persp_uv #(.ATTR_W(ATTR_W),.Q_W(Q_W),.FRAC(FRAC_W),.SCALE(SCALE),
                  .RECIP_W(25),.TEXEL_W(TEXEL_W),.TEXEL_MAX(TEXEL_MAX),.RECIP_IDX_BITS(11)) dut_wide (
        .clk(clk),.rst_n(rst_n),.in_valid(in_valid),.uq(uqw),.vq(vqw),.q(qw),.out_valid(ovw),.u(uw),.v(vw));

    localparam int NV=600;
    int tu[0:NV-1], tv[0:NV-1], tw[0:NV-1];
    logic [ATTR_W-1:0] iuqn[0:NV-1], ivqn[0:NV-1], iuqw[0:NV-1], ivqw[0:NV-1];
    logic [Q_W-1:0]    iqn[0:NV-1], iqw[0:NV-1];
    int n;
    initial begin
        n=0;
        // FAR-W sweep over NON-power-of-2 w (so q*2^FRAC is FRACTIONAL and rounds): w in {1500,3000,5000,7000}.
        // (Powers of 2 give an exactly-representable q and would NOT exercise the collapse — the real SH3 W is
        // arbitrary.) texels 0..480, all < w so uqw < 2^24 (no 24-bit ATTR overflow on the widened FRAC=22 path).
        begin int wtab[4]; wtab[0]=1500; wtab[1]=3000; wtab[2]=5000; wtab[3]=7000;
        for (int wi=0; wi<4 && n<NV; wi++) begin
            int w; w = wtab[wi];
            for (int c=0; c<=480 && n<NV; c+=32) begin
                tu[n]=c; tv[n]=480-c; tw[n]=w;
                iuqn[n]=ATTR_W'((c        * (1<<FRAC_N))/w); ivqn[n]=ATTR_W'(((480-c)*(1<<FRAC_N))/w); iqn[n]=Q_W'((1<<FRAC_N)/w);
                iuqw[n]=ATTR_W'((c        * (1<<FRAC_W))/w); ivqw[n]=ATTR_W'(((480-c)*(1<<FRAC_W))/w); iqw[n]=Q_W'((1<<FRAC_W)/w);
                n++;
            end
        end
        end
    end

    logic [TEXEL_W-1:0] oun[0:NV-1], ovn_[0:NV-1], ouw[0:NV-1], ovw_[0:NV-1];
    int ocn, ocw;
    always_ff @(posedge clk) begin
        if (rst_n && ovn) begin oun[ocn]<=un; ovn_[ocn]<=vn; ocn<=ocn+1; end
        if (rst_n && ovw) begin ouw[ocw]<=uw; ovw_[ocw]<=vw; ocw<=ocw+1; end
    end

    function automatic int iabs(input int x); return (x<0)?-x:x; endfunction
    int errors, narrow_bad, wide_max;
    initial begin
        errors=0; narrow_bad=0; wide_max=0; ocn=0; ocw=0;
        in_valid=0; uqn='0; vqn='0; qn='0; uqw='0; vqw='0; qw='0;
        rst_n=0; repeat(4) @(posedge clk); rst_n=1; repeat(2) @(posedge clk);
        for (int i=0;i<n;i++) begin
            @(negedge clk); in_valid=1;
            uqn=iuqn[i]; vqn=ivqn[i]; qn=iqn[i]; uqw=iuqw[i]; vqw=ivqw[i]; qw=iqw[i];
        end
        @(negedge clk); in_valid=0; uqn='0; vqn='0; qn='0; uqw='0; vqw='0; qw='0;
        repeat(8) @(posedge clk);

        if (ocn!==n || ocw!==n) begin $error("[farw] throughput n=%0d ocn=%0d ocw=%0d", n, ocn, ocw); errors++; end
        for (int i=0;i<n && i<ocn;i++) begin
            int du, dv;
            // NARROW (FRAC=12): count samples that collapse (>1 texel off) — the bug we cured.
            du=iabs(int'(oun[i])-tu[i]); dv=iabs(int'(ovn_[i])-tv[i]);
            if (du>1 || dv>1) narrow_bad++;
            // WIDE (FRAC=22 + recip-11): MUST be <=1 texel for every far-W sample — the fix.
            du=iabs(int'(ouw[i])-tu[i]); dv=iabs(int'(ovw_[i])-tv[i]);
            if (du>wide_max) wide_max=du;
            if (dv>wide_max) wide_max=dv;
            if (du>1 || dv>1) begin
                if (errors<8) $display("  WIDE miss i=%0d (u=%0d v=%0d w=%0d) got=(%0d,%0d)",
                                       i, tu[i], tv[i], tw[i], ouw[i], ovw_[i]);
                errors++;
            end
        end
        $display("[tb_gs_persp_uv_farw] far-W samples=%0d  NARROW(FRAC12,recip8) collapsed=%0d/%0d  WIDE(FRAC22,recip11) max-err=%0d texel",
                 n, narrow_bad, n, wide_max);
        // non-triviality: FRAC=12 MUST genuinely collapse on far-W (else the test proves nothing).
        if (narrow_bad < n/2) begin $error("[farw] FRAC=12 did NOT collapse on far-W (%0d/%0d) — test not exercising the bug", narrow_bad, n); errors++; end
        if (errors==0) $display("[tb_gs_persp_uv_farw] PASS");
        else           $display("[tb_gs_persp_uv_farw] FAIL (%0d errors)", errors);
        $finish;
    end
    initial begin #3000000; $error("[tb_gs_persp_uv_farw] TIMEOUT"); $finish; end
endmodule : tb_gs_persp_uv_farw
