// retroDE_ps2 — gs_alpha_blend
//
// Brick 2a — GS ALPHA blending (transparency), the source-over case.
//
// Computes, per RGB channel:
//   Cv = ((Cs - Cd) * As) >> 7 + Cd   (clamped to [0,255])
// where
//   Cs = source color  (the sprite's RGBAQ color channel)
//   Cd = destination   (the framebuffer pixel READ back at the write addr)
//   As = source alpha  (RGBAQ.A; PS2 0..128 scale where 0x80 == 1.0)
//
// This is the standard PS2 GS ALPHA register config A=0(Cs) B=1(Cd)
// C=0(As) D=1(Cd) — i.e. the canonical alpha-over blend. The brick-2a
// scope implements ONLY this config; selecting any other (A,B,C,D)
// tuple is handled by the caller (gs_stub) which falls back to an
// opaque write for unsupported configs, so this unit is always asked
// for the source-over result.
//
// Purely combinational: a subtract, a multiply by As (<= 8 bits), an
// arithmetic shift right by 7, an add, and a clamp. No divide. Fully
// synthesizable — there is NO `// synthesis translate_off` on this
// path. The >>7 is a wire shift; the *As is a single small multiply.
//
// The (Cs - Cd) term is signed (can be negative when the dest is
// brighter than the source), so the multiply and the shift are done
// in signed arithmetic and the final sum is clamped back into the
// unsigned [0,255] byte range. As is treated as unsigned 0..128; the
// caller passes RGBAQ.A[7:0] (real GS uses A[6:0]*2 internally for
// the 0..128 mapping, but A[7:0] already encodes 0x80=1.0 for the
// values our demo programs, and clamping As at 128 keeps a stray
// A>0x80 from over-shooting).
//
// Alpha (the A channel of the output) follows real-GS behavior for a
// framebuffer write: the SOURCE alpha is written through. brick-2a
// keeps the existing emit-lane A byte (= source A) unchanged; only
// R/G/B are blended. The 'a_out' port forwards the source A so the
// caller can repack the 32-bit ABGR.

`timescale 1ns/1ps

module gs_alpha_blend #(
    // Brick-2c — generic GS blend selector. Default OFF → the module is
    // BYTE-IDENTICAL to the original source-over-only implementation,
    // regardless of the selector inputs (which default to 0 when an
    // instantiation leaves them unconnected). When set, the generic
    // GS ALPHA datapath (A/B/C/D selectors + FIX) is used.
    parameter bit ALPHA_MODES = 1'b0
) (
    // Source (sprite) color channels + alpha.
    input  logic [7:0] cs_r,
    input  logic [7:0] cs_g,
    input  logic [7:0] cs_b,
    input  logic [7:0] as,        // source alpha, 0..128 scale (0x80 = 1.0)

    // Destination (framebuffer) color channels.
    input  logic [7:0] cd_r,
    input  logic [7:0] cd_g,
    input  logic [7:0] cd_b,

    // Brick-2c — generic GS ALPHA_1 selectors (only read when ALPHA_MODES=1).
    //   a_sel : A operand  0=Cs 1=Cd 2=0
    //   b_sel : B operand  0=Cs 1=Cd 2=0
    //   c_sel : C coeff    0=As 1=Ad 2=FIX
    //   d_sel : D operand  0=Cs 1=Cd 2=0
    //   ad    : destination alpha (8-bit, used when c_sel==1)
    //   fix   : fixed alpha coefficient (8-bit, used when c_sel==2)
    input  logic [1:0] a_sel,
    input  logic [1:0] b_sel,
    input  logic [1:0] c_sel,
    input  logic [1:0] d_sel,
    input  logic [7:0] ad,
    input  logic [7:0] fix,

    // Blended output.
    output logic [7:0] cv_r,
    output logic [7:0] cv_g,
    output logic [7:0] cv_b,
    output logic [7:0] a_out      // source alpha, passed through
);

    // Clamp As at 128 (0x80) — anything above 1.0 is treated as 1.0.
    logic [7:0] as_eff;
    assign as_eff = (as > 8'd128) ? 8'd128 : as;

    function automatic logic [7:0] blend_ch(input logic [7:0] cs,
                                            input logic [7:0] cd,
                                            input logic [7:0] alpha);
        logic signed [9:0]  diff;     // Cs - Cd, range -255..+255
        logic signed [17:0] prod;     // diff * alpha, alpha 0..128
        logic signed [17:0] shifted;  // prod >>> 7
        logic signed [17:0] sum;      // shifted + Cd
        diff    = $signed({2'b00, cs}) - $signed({2'b00, cd});
        prod    = diff * $signed({1'b0, alpha});
        shifted = prod >>> 7;                      // arithmetic shift
        sum     = shifted + $signed({10'd0, cd});
        // Clamp to [0,255].
        if (sum < 18'sd0)
            return 8'd0;
        else if (sum > 18'sd255)
            return 8'd255;
        else
            return sum[7:0];
    endfunction

    // ------------------------------------------------------------------
    // Brick-2c — generic GS blend selector datapath.
    //   Cv = clamp( (((A - B) * C) >>> 7) + D )    per RGB channel.
    //   A/B/D ∈ {Cs, Cd, 0}; C ∈ {As, Ad, FIX} (8-bit coeff, 0x80==1.0).
    // (A-B) is signed; *C is unsigned 0..255; >>>7 arithmetic; +D; clamp.
    // ------------------------------------------------------------------
    function automatic logic [7:0] blend_generic(
        input logic [7:0] cs, input logic [7:0] cd,
        input logic [1:0] asel, input logic [1:0] bsel,
        input logic [1:0] dsel, input logic [7:0] coef);
        logic [7:0]        op_a;
        logic [7:0]        op_b;
        logic [7:0]        op_d;
        logic signed [31:0] diff;     // signed (A - B)
        logic signed [31:0] prod;     // diff * coef (coef unsigned 0..255)
        logic signed [31:0] shifted;  // prod >>> 7
        logic signed [31:0] sum;      // shifted + D
        op_a = (asel == 2'd0) ? cs : (asel == 2'd1) ? cd : 8'd0;
        op_b = (bsel == 2'd0) ? cs : (bsel == 2'd1) ? cd : 8'd0;
        op_d = (dsel == 2'd0) ? cs : (dsel == 2'd1) ? cd : 8'd0;
        diff    = $signed({1'b0, op_a}) - $signed({1'b0, op_b});
        prod    = diff * $signed({24'd0, coef});
        shifted = prod >>> 7;                 // arithmetic shift
        sum     = shifted + $signed({24'd0, op_d});
        if (sum < 32'sd0)
            return 8'd0;
        else if (sum > 32'sd255)
            return 8'd255;
        else
            return sum[7:0];
    endfunction

    // Shared 8-bit C coefficient (same for all three channels).
    logic [7:0] coef_c;
    assign coef_c = (c_sel == 2'd0) ? as_eff :
                    (c_sel == 2'd1) ? ad     : fix;

    generate
        if (ALPHA_MODES) begin : g_generic
            assign cv_r = blend_generic(cs_r, cd_r, a_sel, b_sel, d_sel, coef_c);
            assign cv_g = blend_generic(cs_g, cd_g, a_sel, b_sel, d_sel, coef_c);
            assign cv_b = blend_generic(cs_b, cd_b, a_sel, b_sel, d_sel, coef_c);
        end else begin : g_source_over
            // EXACT original source-over expression — byte-identical.
            assign cv_r = blend_ch(cs_r, cd_r, as_eff);
            assign cv_g = blend_ch(cs_g, cd_g, as_eff);
            assign cv_b = blend_ch(cs_b, cd_b, as_eff);
        end
    endgenerate

    assign a_out = as;            // source alpha passes through unchanged

endmodule : gs_alpha_blend
