// retroDE_ps2 — tb_gs_persp_uv (Ch301)
//
// Focused TB for gs_persp_uv — the per-pixel perspective-correct divide
// (recip + multiply + clamp) that recovers integer texel (u,v) from the
// interpolated perspective attributes (u/w, v/w, 1/w). Proves:
//
//   PROOF 1 — ROUND-TRIP: for known (u,v,w), feeding uq=(u<<FRAC)/w,
//             vq=(v<<FRAC)/w, q=(1<<FRAC)/w returns texel (u,v) within the
//             reciprocal's mantissa tolerance (<= ~1 texel for the demo range).
//             Sweep covers near (w small) and far (w large) — the regime where
//             perspective diverges from affine.
//
//   PROOF 2 — THROUGHPUT: back-to-back inputs, one result/cycle, no stall.
//
//   PROOF 3 — CLAMP: an over-range result saturates to TEXEL_MAX, not wrap.

`timescale 1ns/1ps

module tb_gs_persp_uv;

    localparam int ATTR_W  = 24;
    localparam int Q_W     = 24;
    localparam int FRAC    = 12;
    localparam int SCALE   = 24;
    localparam int TEXEL_W = 11;
    localparam int TEXEL_MAX = 63;   // demo texture max coord

    logic clk, rst_n;
    initial clk = 1'b0;
    always #5 clk = ~clk;

    logic                in_valid;
    logic [ATTR_W-1:0]   uq, vq;
    logic [Q_W-1:0]      q;
    logic                out_valid;
    logic [TEXEL_W-1:0]  u, v;

    gs_persp_uv #(
        .ATTR_W(ATTR_W), .Q_W(Q_W), .FRAC(FRAC), .SCALE(SCALE),
        .RECIP_W(25), .TEXEL_W(TEXEL_W), .TEXEL_MAX(TEXEL_MAX)
    ) dut (
        .clk(clk), .rst_n(rst_n),
        .in_valid(in_valid), .uq(uq), .vq(vq), .q(q),
        .out_valid(out_valid), .u(u), .v(v)
    );

    // input vectors: (u,v,w) triples + their fixed-point attributes
    localparam int NV = 512;
    int            tu  [0:NV-1];
    int            tv  [0:NV-1];
    int            tw  [0:NV-1];
    logic [ATTR_W-1:0] vuq [0:NV-1];
    logic [ATTR_W-1:0] vvq [0:NV-1];
    logic [Q_W-1:0]    vq_ [0:NV-1];
    int n;

    initial begin
        n = 0;
        // sweep texel coords 0..63 across a range of w (1..16 = near..far)
        for (int w = 1; w <= 16 && n < NV; w++) begin
            for (int c = 0; c <= 63 && n < NV; c += 7) begin
                tu[n] = c;
                tv[n] = 63 - c;
                tw[n] = w;
                vuq[n] = ATTR_W'((c        * (1 << FRAC)) / w);
                vvq[n] = ATTR_W'(((63 - c) * (1 << FRAC)) / w);
                vq_[n] = Q_W'((1 << FRAC) / w);
                n++;
            end
        end
        // an explicit over-range case to exercise the clamp (u/w*w would be 100>63)
        tu[n]=100; tv[n]=0; tw[n]=1;
        vuq[n]=ATTR_W'((100*(1<<FRAC))/1); vvq[n]='0; vq_[n]=Q_W'((1<<FRAC)/1); n++;
    end

    logic [TEXEL_W-1:0] ou [0:NV-1];
    logic [TEXEL_W-1:0] ov [0:NV-1];
    int oc;

    always_ff @(posedge clk) begin
        if (rst_n && out_valid) begin
            ou[oc] <= u;
            ov[oc] <= v;
            oc     <= oc + 1;
        end
    end

    int errors;
    function automatic int iabs(input int x); return (x < 0) ? -x : x; endfunction

    initial begin
        errors = 0; oc = 0;
        in_valid = 1'b0; uq = '0; vq = '0; q = '0;
        rst_n = 1'b0;
        repeat (4) @(posedge clk);
        rst_n = 1'b1;
        repeat (2) @(posedge clk);

        for (int i = 0; i < n; i++) begin
            @(negedge clk);
            in_valid = 1'b1; uq = vuq[i]; vq = vvq[i]; q = vq_[i];
        end
        @(negedge clk); in_valid = 1'b0; uq='0; vq='0; q='0;
        repeat (8) @(posedge clk);

        if (oc !== n) begin
            $error("[persp_uv] throughput: expected %0d outputs got %0d", n, oc); errors++;
        end

        for (int i = 0; i < oc && i < n; i++) begin
            int eu, ev;
            // clamp expected to TEXEL_MAX (the over-range case)
            eu = (tu[i] > TEXEL_MAX) ? TEXEL_MAX : tu[i];
            ev = (tv[i] > TEXEL_MAX) ? TEXEL_MAX : tv[i];
            if (iabs(int'(ou[i]) - eu) > 1) begin
                $error("[persp_uv] i=%0d (u=%0d,w=%0d) got_u=%0d exp=%0d", i, tu[i], tw[i], ou[i], eu);
                errors++;
            end
            if (iabs(int'(ov[i]) - ev) > 1) begin
                $error("[persp_uv] i=%0d (v=%0d,w=%0d) got_v=%0d exp=%0d", i, tv[i], tw[i], ov[i], ev);
                errors++;
            end
        end

        $display("[tb_gs_persp_uv] checked %0d, errors=%0d", oc, errors);
        if (errors == 0) $display("[tb_gs_persp_uv] PASS");
        else             $display("[tb_gs_persp_uv] FAIL");
        $finish;
    end

    initial begin
        #2000000;
        $error("[tb_gs_persp_uv] TIMEOUT");
        $finish;
    end

endmodule : tb_gs_persp_uv
