module biquad_filter_impl #(
  parameter DATA_W      = 16,
  parameter COEFF_W     = 16,
  parameter FEEDBACK_W  = 25,
  parameter FF_HEADROOM = 2,
  parameter FB_HEADROOM = 2
)(
  input  wire                      clk,
  input  wire                      reset,
  input  wire        [6:0]         chain_en,
  input  wire signed [COEFF_W-1:0] set_b0,
  input  wire signed [COEFF_W-1:0] set_b1,
  input  wire signed [COEFF_W-1:0] set_b2,
  input  wire signed [COEFF_W-1:0] set_a1,
  input  wire signed [COEFF_W-1:0] set_a2,
  input  wire signed [DATA_W-1:0]  data_in,
  output reg  signed [DATA_W-1:0]  data_out
);

  localparam FF_PROD_W  = DATA_W + COEFF_W - 1;       // Feedforward product width (signed multiply)
  localparam FF_SUM_W   = FF_PROD_W + FF_HEADROOM;    // Feedforward sum width (3 term add)
  localparam FB_PROD_W  = FEEDBACK_W + COEFF_W - 1;   // Feedback product width (signed multiply)
  localparam FB_SUM_W   = FB_PROD_W + FB_HEADROOM;    // Feedback sum width (3 term add) 

  reg  signed [DATA_W-1:0]      reg_x0 = 0, reg_x1 = 0, reg_x2 = 0;
  reg  signed [FF_PROD_W-1:0]   reg_b0x0 = 0, reg_b1x1 = 0, reg_b2x2 = 0, reg_b2x2_del = 0;
  reg  signed [FF_SUM_W-1:0]    reg_ffsum_0 = 0, reg_ffsum_1 = 0;
  wire signed [FB_PROD_W-1:0]   prod_yxa1, prod_yxa2;
  reg  signed [FB_SUM_W-1:0]    reg_fb_sum0 = 0, reg_fb_sum1 = 0;
  wire signed [FEEDBACK_W-1:0]  fb_sum1_trunc;
  wire signed [DATA_W-1:0]      out_rnd;

  always @(posedge clk) begin
    if (reset) begin
      {reg_x2, reg_x1, reg_x0} <= 0;
      reg_b0x0 <= 0;
      reg_b1x1 <= 0;
      reg_b2x2 <= 0;
      reg_b2x2_del <= 0;
      reg_ffsum_0 <= 0;
      reg_ffsum_1 <= 0;
      reg_fb_sum0 <= 0;
      reg_fb_sum1 <= 0;
      data_out <= 0;
    end else begin
      if (chain_en[0]) begin
        // Input pipeline register and delay line
        {reg_x2, reg_x1, reg_x0} <= {reg_x1, reg_x0, data_in};
      end
      if (chain_en[1]) begin
        // Feedforward products
        reg_b0x0 <= reg_x0 * set_b0;
        reg_b1x1 <= reg_x1 * set_b1;
        reg_b2x2 <= reg_x2 * set_b2;
        reg_b2x2_del <= reg_b2x2;
      end
      // Feedforward sums (in two pipelined stages)
      if (chain_en[2]) begin
        reg_ffsum_0 <= reg_b0x0 + reg_b1x1;
      end
      if (chain_en[3]) begin
        reg_ffsum_1 <= reg_ffsum_0 + reg_b2x2_del;
      end
      // Feedback sums (in two pipelined stages)
      if (chain_en[4]) begin
        reg_fb_sum0 <= (reg_ffsum_1 <<< (FB_SUM_W-FF_SUM_W-FB_HEADROOM)) + prod_yxa2;
      end
      if (chain_en[5]) begin
        reg_fb_sum1 <= reg_fb_sum0 + prod_yxa1;
      end
      if (chain_en[6]) begin
        // Output pipeline register
        data_out <= out_rnd;
      end
    end
  end
  // Feedback products
  assign prod_yxa1 = fb_sum1_trunc * set_a1;
  assign prod_yxa2 = fb_sum1_trunc * set_a2;
  // Truncate feedback
  assign fb_sum1_trunc = (reg_fb_sum1 >>> (FB_SUM_W-FEEDBACK_W-FB_HEADROOM));

  round #(
    .bits_in(FB_SUM_W-FB_HEADROOM-FF_HEADROOM), .bits_out(DATA_W)
  ) round_i (
    .in(reg_fb_sum1[FB_SUM_W-FB_HEADROOM-FF_HEADROOM-1:0]), .out(out_rnd), .err()
  );

endmodule // biquad_filter_impl