module biquad_filter #(
  parameter DATA_W          = 16,
  parameter COEFF_W         = 16,
  parameter FEEDBACK_W      = 25,
  parameter ACCUM_HEADROOM  = 2
)(
  // Clocks and resets
  input  wire                   clk,
  input  wire                   reset,
  // Taps                       
  input  wire [COEFF_W-1:0]     set_b0,
  input  wire [COEFF_W-1:0]     set_b1,
  input  wire [COEFF_W-1:0]     set_b2,
  input  wire [COEFF_W-1:0]     set_a1,
  input  wire [COEFF_W-1:0]     set_a2,
  // Input data
  input  wire [(DATA_W*2)-1:0]  i_tdata,
  input  wire                   i_tlast,
  input  wire                   i_tvalid,
  output wire                   i_tready,
  // Output data
  output wire [(DATA_W*2)-1:0]  o_tdata,
  output wire                   o_tlast,
  output wire                   o_tvalid,
  input  wire                   o_tready
);

  // There are seven registers between the input and output
  // - 1 Input pipeline reg (reg_x0)
  // - 1 Feedforward product (reg_b0x0)
  // - 2 Feedforward sum (reg_ffsum_0 and reg_ffsum_1)
  // - 2 Feedback sum (reg_fbsum_0 and reg_ffsum_1)
  // - 1 Output pileline reg (data_out)
  localparam IN_TO_OUT_LATENCY = 7;

  // Pipeline settings for timing
  reg [COEFF_W-1:0] reg_b0 = 0, reg_b1 = 0, reg_b2 = 0;
  reg [COEFF_W-1:0] reg_a1 = 0, reg_a2 = 0;

  always @(posedge clk) begin
    reg_b0 <= set_b0;
    reg_b1 <= set_b1;
    reg_b2 <= set_b2;
    reg_a1 <= set_a1;
    reg_a2 <= set_a2;
  end

  //-----------------------------------------------------------
  // AXI-Stream wrapper
  //-----------------------------------------------------------
  wire [(DATA_W*2)-1:0]        dsp_data_in, dsp_data_out;
  wire [IN_TO_OUT_LATENCY-1:0] chain_en;

  // We are implementing an N-cycle DSP operation without AXI-Stream handshaking.
  // Use an axis_shift_register and the associated strobes to drive clock enables
  // on the DSP regs to ensure that data/valid/last sync up.
  axis_shift_register #(
    .WIDTH(2*DATA_W), .NSPC(1), .LATENCY(IN_TO_OUT_LATENCY),
    .SIDEBAND_DATAPATH(1), .PIPELINE("OUT")
  ) axis_shreg_i (
    .clk(clk), .reset(reset),
    .s_axis_tdata(i_tdata), .s_axis_tkeep(1'b1), .s_axis_tlast(i_tlast),
    .s_axis_tvalid(i_tvalid), .s_axis_tready(i_tready),
    .m_axis_tdata(o_tdata), .m_axis_tkeep(), .m_axis_tlast(o_tlast),
    .m_axis_tvalid(o_tvalid), .m_axis_tready(o_tready),
    .stage_stb(chain_en), .stage_eop(),
    .m_sideband_data(dsp_data_in), .m_sideband_keep(),
    .s_sideband_data(dsp_data_out)
  );

  //-----------------------------------------------------------
  // DSP datapath
  //-----------------------------------------------------------
  biquad_filter_impl #(
    .DATA_W(DATA_W), .COEFF_W(COEFF_W), .FEEDBACK_W(FEEDBACK_W),
    .FF_HEADROOM(2), .FB_HEADROOM(ACCUM_HEADROOM)
  ) impl_i_inst (
    .clk(clk), .reset(reset), .chain_en(chain_en),
    .set_b0(reg_b0), .set_b1(reg_b1), .set_b2(reg_b2),
    .set_a1(reg_a1), .set_a2(reg_a2),
    .data_in(dsp_data_in[(2*DATA_W)-1:DATA_W]), .data_out(dsp_data_out[(2*DATA_W)-1:DATA_W])
  );

  biquad_filter_impl #(
    .DATA_W(DATA_W), .COEFF_W(COEFF_W), .FEEDBACK_W(FEEDBACK_W),
    .FF_HEADROOM(2), .FB_HEADROOM(ACCUM_HEADROOM)
  ) impl_q_inst (
    .clk(clk), .reset(reset), .chain_en(chain_en),
    .set_b0(reg_b0), .set_b1(reg_b1), .set_b2(reg_b2),
    .set_a1(reg_a1), .set_a2(reg_a2),
    .data_in(dsp_data_in[DATA_W-1:0]), .data_out(dsp_data_out[DATA_W-1:0])
  );

endmodule // biquad_filter