// retroDE_ps2 — tb_gs_alpha_blend (Brick 2a)
//
// Unit TB for the gs_alpha_blend datapath. Drives (Cs, Cd, As) and
// checks Cv == ((Cs - Cd) * As) >> 7 + Cd per channel, with clamping,
// against an independent software reference computed in the TB.
//
// Covers:
//   - As = 0   -> Cv == Cd                (fully transparent source)
//   - As = 128 -> Cv == Cs                (fully opaque source)
//   - As = 64  -> ~50% blend, mid value, NEITHER Cs nor Cd
//   - dest brighter than source (negative diff path)
//   - clamp-high and clamp-low edge values
//   - randomized sweep cross-checked against the reference model

`timescale 1ns/1ps

module tb_gs_alpha_blend;

    logic [7:0] cs_r, cs_g, cs_b, as;
    logic [7:0] cd_r, cd_g, cd_b;
    logic [7:0] cv_r, cv_g, cv_b, a_out;

    gs_alpha_blend dut (
        .cs_r(cs_r), .cs_g(cs_g), .cs_b(cs_b), .as(as),
        .cd_r(cd_r), .cd_g(cd_g), .cd_b(cd_b),
        .cv_r(cv_r), .cv_g(cv_g), .cv_b(cv_b), .a_out(a_out)
    );

    int errors = 0;

    // Independent reference: signed math + clamp, As clamped at 128.
    function automatic logic [7:0] ref_blend(input logic [7:0] cs,
                                             input logic [7:0] cd,
                                             input logic [7:0] alpha);
        int a_eff;
        int diff;
        int prod;
        int shifted;
        int sum;
        a_eff   = (alpha > 128) ? 128 : alpha;
        diff    = int'(cs) - int'(cd);
        prod    = diff * a_eff;
        // Arithmetic >> 7 (floor toward -inf for negatives, matching >>>).
        shifted = prod >>> 7;
        sum     = shifted + int'(cd);
        if (sum < 0)         return 8'd0;
        else if (sum > 255)  return 8'd255;
        else                 return 8'(sum);
    endfunction

    task automatic check(input logic [7:0] sr, sg, sb, a,
                         input logic [7:0] dr, dg, db,
                         input string label);
        logic [7:0] er, eg, eb;
        cs_r = sr; cs_g = sg; cs_b = sb; as = a;
        cd_r = dr; cd_g = dg; cd_b = db;
        #1;
        er = ref_blend(sr, dr, a);
        eg = ref_blend(sg, dg, a);
        eb = ref_blend(sb, db, a);
        if (cv_r !== er || cv_g !== eg || cv_b !== eb) begin
            $error("[%s] Cs=(%02x,%02x,%02x) As=%0d Cd=(%02x,%02x,%02x): got (%02x,%02x,%02x) exp (%02x,%02x,%02x)",
                   label, sr, sg, sb, a, dr, dg, db, cv_r, cv_g, cv_b, er, eg, eb);
            errors = errors + 1;
        end
        if (a_out !== a) begin
            $error("[%s] a_out passthrough got %02x exp %02x", label, a_out, a);
            errors = errors + 1;
        end
    endtask

    initial begin
        // As=0 -> Cv == Cd exactly.
        check(8'hFF, 8'h80, 8'h10,  8'd0,  8'h11, 8'h22, 8'h33, "as0_eq_cd");
        #1;
        if (cv_r !== 8'h11 || cv_g !== 8'h22 || cv_b !== 8'h33) begin
            $error("as=0 must equal Cd"); errors = errors + 1; end

        // As=128 -> Cv == Cs exactly.
        cs_r=8'hAB; cs_g=8'hCD; cs_b=8'hEF; as=8'd128;
        cd_r=8'h00; cd_g=8'h7F; cd_b=8'hFF; #1;
        if (cv_r !== 8'hAB || cv_g !== 8'hCD || cv_b !== 8'hEF) begin
            $error("as=128 must equal Cs: got (%02x,%02x,%02x)", cv_r, cv_g, cv_b);
            errors = errors + 1;
        end

        // As=64 (~0.5): mid blend, must be strictly between Cs and Cd.
        cs_r=8'd200; cs_g=8'd200; cs_b=8'd200; as=8'd64;
        cd_r=8'd0;   cd_g=8'd0;   cd_b=8'd0;   #1;
        // (200-0)*64>>7 + 0 = 12800>>7 = 100.
        if (cv_r !== 8'd100) begin
            $error("as=64 mid blend: got %0d exp 100", cv_r); errors = errors + 1; end
        if (cv_r === cs_r || cv_r === cd_r) begin
            $error("as=64 blend collapsed to pure src or dest"); errors = errors + 1; end

        // dest brighter than source -> negative diff path.
        check(8'd10, 8'd10, 8'd10, 8'd64, 8'd250, 8'd250, 8'd250, "neg_diff");

        // clamp edges and assorted values via reference cross-check.
        check(8'hFF, 8'hFF, 8'hFF, 8'd128, 8'h00, 8'h00, 8'h00, "white_over_black");
        check(8'h00, 8'h00, 8'h00, 8'd128, 8'hFF, 8'hFF, 8'hFF, "black_over_white");
        check(8'h40, 8'h80, 8'hC0, 8'd40,  8'h90, 8'h20, 8'h10, "assorted1");
        check(8'hF0, 8'h0F, 8'h55, 8'd96,  8'h0A, 8'hF5, 8'hAA, "assorted2");

        // As > 128 must clamp to 128 (== Cs).
        cs_r=8'h33; cs_g=8'h44; cs_b=8'h55; as=8'd200;
        cd_r=8'h99; cd_g=8'hAA; cd_b=8'hBB; #1;
        if (cv_r !== 8'h33 || cv_g !== 8'h44 || cv_b !== 8'h55) begin
            $error("as>128 must clamp to Cs: got (%02x,%02x,%02x)", cv_r, cv_g, cv_b);
            errors = errors + 1;
        end

        // Randomized sweep.
        for (int i = 0; i < 2000; i++) begin
            check(8'($urandom), 8'($urandom), 8'($urandom), 8'($urandom % 129),
                  8'($urandom), 8'($urandom), 8'($urandom), "rand");
        end

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

endmodule : tb_gs_alpha_blend
