// In-order LPDDR destination-read / generic GS ALPHA_1 blend stage.
module gs_lpddr_color_blend (
    input logic clk, input logic rst_n,
    input logic in_valid, output logic in_ready,
    input logic [31:0] in_addr, input logic [31:0] in_color, input logic [16:0] in_alpha,
    input logic [3:0] in_be,
    output logic out_valid, input logic out_ready, output logic [31:0] out_addr, output logic [31:0] out_color, output logic idle,
    output logic [31:0] araddr, output logic [7:0] arlen, output logic [2:0] arsize, output logic [1:0] arburst,
    output logic arvalid, input logic arready, input logic [255:0] rdata, input logic [1:0] rresp,
    input logic rlast, input logic rvalid, output logic rready
);
    // The EMIF user clock is ~310 MHz.  Keep the destination read, operand
    // select/subtract, multiply, and add/clamp on separate registered stages;
    // the original combinational gs_alpha_blend instance put all of them on
    // the rdata -> out_color path and could not meet a 3.225 ns period.
    typedef enum logic [2:0] {IDLE, AR, R, SEL, PREP, MUL, MAC, SUM} state_t; state_t state;
    logic [31:0] src_addr, src_color; logic [16:0] src_alpha; logic [3:0] src_be; logic [2:0] lane;
    logic [255:0] rdata_q;
    logic [31:0] dst_q;
    wire [1:0] a_sel=src_alpha[15:14], b_sel=src_alpha[13:12], c_sel=src_alpha[11:10], d_sel=src_alpha[9:8];
    wire [31:0] dst_lane=(lane==0)?rdata_q[31:0]:(lane==1)?rdata_q[63:32]:(lane==2)?rdata_q[95:64]:(lane==3)?rdata_q[127:96]:(lane==4)?rdata_q[159:128]:(lane==5)?rdata_q[191:160]:(lane==6)?rdata_q[223:192]:rdata_q[255:224];

    function automatic logic [7:0] color_operand(
        input logic [7:0] cs, input logic [7:0] cd,
        input logic [1:0] sel);
        return (sel == 2'd0) ? cs : (sel == 2'd1) ? cd : 8'd0;
    endfunction

    function automatic logic [7:0] clamp_sum(
        input logic signed [17:0] prod, input logic [7:0] op_d);
        logic signed [18:0] sum;
        sum = (prod >>> 7) + $signed({11'd0, op_d});
        if (sum < 19'sd0) return 8'd0;
        else if (sum > 19'sd255) return 8'd255;
        else return sum[7:0];
    endfunction

    // FRAME.FBMSK is compressed to a byte-enable before entering this block.
    // A disabled byte preserves the destination value after either the GS
    // ALPHA equation (ABE=1) or the opaque source selection (ABE=0).
    function automatic logic [31:0] merge_be(
        input logic [31:0] src, input logic [31:0] dst,
        input logic [3:0] be);
        for (int i=0; i<4; i++)
            merge_be[i*8 +: 8] = be[i] ? src[i*8 +: 8] : dst[i*8 +: 8];
    endfunction

    wire [7:0] ar_op = color_operand(src_color[7:0],   dst_q[7:0],   a_sel);
    wire [7:0] ag_op = color_operand(src_color[15:8],  dst_q[15:8],  a_sel);
    wire [7:0] ab_op = color_operand(src_color[23:16], dst_q[23:16], a_sel);
    wire [7:0] br_op = color_operand(src_color[7:0],   dst_q[7:0],   b_sel);
    wire [7:0] bg_op = color_operand(src_color[15:8],  dst_q[15:8],  b_sel);
    wire [7:0] bb_op = color_operand(src_color[23:16], dst_q[23:16], b_sel);
    wire [7:0] dr_op = color_operand(src_color[7:0],   dst_q[7:0],   d_sel);
    wire [7:0] dg_op = color_operand(src_color[15:8],  dst_q[15:8],  d_sel);
    wire [7:0] db_op = color_operand(src_color[23:16], dst_q[23:16], d_sel);
    wire [7:0] src_alpha_eff = (src_color[31:24] > 8'h80) ? 8'h80 : src_color[31:24];
    wire [7:0] coef = (c_sel == 2'd0) ? src_alpha_eff :
                      (c_sel == 2'd1) ? dst_q[31:24] : src_alpha[7:0];

    logic signed [8:0] diff_r_q, diff_g_q, diff_b_q;
    logic signed [17:0] prod_r_q, prod_g_q, prod_b_q;
    logic signed [17:0] mcand_r_q, mcand_g_q, mcand_b_q;
    logic [7:0] multiplier_q;
    logic [2:0] mac_count_q;
    logic [7:0] coef_q, d_r_q, d_g_q, d_b_q, alpha_q;
    assign in_ready=(state==IDLE)&&!out_valid; assign idle=(state==IDLE)&&!out_valid; assign araddr={src_addr[31:5],5'd0};
    assign arlen=0; assign arsize=3'b101; assign arburst=2'b01; assign arvalid=(state==AR); assign rready=(state==R);
    always_ff @(posedge clk or negedge rst_n) begin
      if(!rst_n) begin
        state<=IDLE;out_valid<=0;out_addr<=0;out_color<=0;
        src_addr<=0;src_color<=0;src_alpha<=0;src_be<=4'hF;lane<=0;rdata_q<=0;dst_q<=0;
        diff_r_q<=0;diff_g_q<=0;diff_b_q<=0;
        prod_r_q<=0;prod_g_q<=0;prod_b_q<=0;
        mcand_r_q<=0;mcand_g_q<=0;mcand_b_q<=0;
        multiplier_q<=0;mac_count_q<=0;
        coef_q<=0;d_r_q<=0;d_g_q<=0;d_b_q<=0;alpha_q<=0;
      end
      else begin
        if(out_valid&&out_ready) out_valid<=0;
        case(state)
          IDLE: if(in_valid&&in_ready) begin
            src_addr<=in_addr;src_color<=in_color;src_alpha<=in_alpha;src_be<=in_be;lane<=in_addr[4:2];
            // A partial write is a destination RMW even when ABE is off.
            if(in_alpha[16] || (in_be != 4'hF)) state<=AR;
            else begin out_valid<=1;out_addr<=in_addr;out_color<=in_color;end
          end
          AR: if(arready) state<=R;
          R: if(rvalid&&rlast) begin
            rdata_q <= rdata;
            state<=SEL;
          end
          SEL: begin
            dst_q <= dst_lane;
            state<=PREP;
          end
          PREP: begin
            diff_r_q <= $signed({1'b0, ar_op}) - $signed({1'b0, br_op});
            diff_g_q <= $signed({1'b0, ag_op}) - $signed({1'b0, bg_op});
            diff_b_q <= $signed({1'b0, ab_op}) - $signed({1'b0, bb_op});
            coef_q <= coef;
            d_r_q <= dr_op; d_g_q <= dg_op; d_b_q <= db_op;
            alpha_q <= src_color[31:24];
            state<=MUL;
          end
          MUL: begin
            // Exact signed-difference * unsigned-coefficient multiply,
            // implemented as eight short shift/add cycles.  Agilex 5's
            // inferred 18x18 DSP has a 3.751 ns minimum period in this
            // configuration, slower than the 3.225 ns EMIF user clock.
            // The emitter is already single-request/in-order, so latency is
            // harmless and this avoids both the primitive limit and any
            // timing exception.
            prod_r_q<=0;prod_g_q<=0;prod_b_q<=0;
            mcand_r_q<={{9{diff_r_q[8]}},diff_r_q};
            mcand_g_q<={{9{diff_g_q[8]}},diff_g_q};
            mcand_b_q<={{9{diff_b_q[8]}},diff_b_q};
            multiplier_q<=coef_q;mac_count_q<=0;state<=MAC;
          end
          MAC: begin
            if(multiplier_q[0]) begin
              prod_r_q<=prod_r_q+mcand_r_q;
              prod_g_q<=prod_g_q+mcand_g_q;
              prod_b_q<=prod_b_q+mcand_b_q;
            end
            mcand_r_q<=mcand_r_q<<<1;
            mcand_g_q<=mcand_g_q<<<1;
            mcand_b_q<=mcand_b_q<<<1;
            multiplier_q<=multiplier_q>>1;
            if(mac_count_q==3'd7) state<=SUM;
            else mac_count_q<=mac_count_q+1'b1;
          end
          SUM: begin
            out_valid<=1;out_addr<=src_addr;
            out_color<=merge_be(
                src_alpha[16]
                    ? {alpha_q,clamp_sum(prod_b_q,d_b_q),clamp_sum(prod_g_q,d_g_q),clamp_sum(prod_r_q,d_r_q)}
                    : src_color,
                dst_q, src_be);
            state<=IDLE;
          end
        endcase
      end
    end
endmodule
