module duc #(
  parameter SR_PHASE_INC_ADDR = 0,
  parameter SR_SCALE_ADDR     = 1,
  parameter SR_INTERP_ADDR    = 2,
  parameter NUM_HB            = 2,
  parameter CIC_MAX_INTERP    = 128

)(
  input clk, input reset, input clear,
  input set_stb, input [7:0] set_addr, input [31:0] set_data,
  input [31:0] i_tdata, input [127:0] i_tuser, input i_tvalid, output i_tready,
  output [31:0] o_tdata, output [127:0] o_tuser, output o_tvalid, input o_tready
);

  localparam RESET_DELAY = 3;

  localparam WIDTH  = 16; // Input/output bitwidth of the module
  localparam CWIDTH = 24; // Internal bitwidth needed for CORDIC accuracy
  localparam PWIDTH = 32; // Phase accumulator bitwidth

  reg  [1:0] hb_rate;             // Current Halfband rate
  reg  [7:0] cic_interp_rate;     // Current CIC rate

  wire [1:0] hb_rate_int;
  wire [7:0] cic_interp_rate_int;

  wire [2*CWIDTH-1:0] o_tdata_halfbands;  // Halfband output
  wire o_tvalid_halfbands;

  wire rate_changed;    // Rate changed by the settings registers
  wire reset_on_change; // Reset the halfbands and the cic everytime there is a rate change
  wire reset_on_live_change; // Reset when rate changes while streaming

  wire [PWIDTH-1:0] o_tdata_phase;
  wire o_tvalid_phase;
  wire o_tlast_phase;
  wire i_tready_phase;

  wire [17:0] scale_factor;

 /**************************************************************************
  * Settings registers
  **************************************************************************/
  // AXI settings bus for phase values
  axi_setting_reg #(
    .ADDR(SR_PHASE_INC_ADDR), .AWIDTH(8), .WIDTH(PWIDTH), .STROBE_LAST(1), .REPEATS(1))
  axi_sr_phase (
    .clk(clk), .reset(reset),
    .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data),
    .o_tdata(o_tdata_phase), .o_tlast(o_tlast_phase), .o_tvalid(o_tvalid_phase), .o_tready(i_tready_phase));

  // AXI settings bus for scale
  setting_reg #(.my_addr(SR_SCALE_ADDR), .width(18)) sr_scale (
    .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr),
    .in(set_data),.out(scale_factor),.changed());

  // AXI settings bus for interpolation rate
  setting_reg #(.my_addr(SR_INTERP_ADDR), .width(10), .at_reset(1)) sr_interp
    (.clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr),
     .in(set_data),.out({hb_rate_int,cic_interp_rate_int}),.changed(rate_changed));

  // Changing interpolation rates while processing only when axi_rate_change sends a clear
  reg active, rate_changed_hold;
  reg [RESET_DELAY-1:0] shift_reset;
  always @(posedge clk) begin
    if (reset) begin
      active            <= 1'b0;
      rate_changed_hold <= 1'b0;
      cic_interp_rate   <= 'd1;
      hb_rate           <= 'd0;
      shift_reset       <= 'd0;
    end else begin
      if (clear | reset_on_change) begin
        active <= 1'b0;
      end else if (i_tready & i_tvalid) begin
        active <= 1'b1;
      end
      if (rate_changed & active) begin
        rate_changed_hold <= 1'b1;
      end
      if ((clear | ~active) & (rate_changed | rate_changed_hold)) begin
        rate_changed_hold <= 1'b0;
        cic_interp_rate   <= cic_interp_rate_int;
        hb_rate           <= hb_rate_int;
        shift_reset       <= {shift_reset[RESET_DELAY-1:0], 1'b1};
      end else begin
        shift_reset       <= {shift_reset[RESET_DELAY-1:0], 1'b0};
      end
    end
  end

  // Long reset for the halfbands
  assign reset_on_change = |shift_reset;
  assign reset_on_live_change =  (clear | reset_on_change | (~active & rate_changed));

 /**************************************************************************
  * Halfbands
  *************************************************************************/

  // Sign extend from 16 to 24 bits to increase the accuracy from the CORDIC
  wire [2*CWIDTH-1:0] o_tdata_extd;

  sign_extend #(.bits_in(WIDTH), .bits_out(CWIDTH)) sign_extend_in_i (
    .in(i_tdata[2*WIDTH-1:WIDTH]), .out(o_tdata_extd[2*CWIDTH-1:CWIDTH]));

  sign_extend #(.bits_in(WIDTH), .bits_out(CWIDTH)) sign_extend_in_q (
    .in(i_tdata[WIDTH-1:0]), .out(o_tdata_extd[CWIDTH-1:0]));

  // Halfband 1 wires
  wire i_tready_hb1;
  wire [2*CWIDTH-1:0] o_tdata_hb1;
  wire o_tvalid_hb1, o_tready_hb1;
  // Halfband 2 wires
  wire i_tready_hb2;
  wire [2*CWIDTH-1:0] o_tdata_hb2;
  wire o_tvalid_hb2, o_tready_hb2;
  // Halfband 3 wires
  wire i_tready_hb3;
  wire [2*CWIDTH-1:0] o_tdata_hb3;
  wire o_tvalid_hb3, o_tready_hb3;
  generate
  if( NUM_HB > 0 ) begin
  axi_hb47 halfband1 (
    .aclk(clk),
    .aresetn(~(reset | clear | reset_on_change)),
    .s_axis_data_tvalid(i_tvalid),
    .s_axis_data_tready(i_tready_hb1),
    .s_axis_data_tdata(o_tdata_extd),
    .m_axis_data_tvalid(o_tvalid_hb1),
    .m_axis_data_tready(o_tready_hb1),
    .m_axis_data_tdata(o_tdata_hb1)
  );
  end else begin
  assign o_tdata_hb1 = 'h0;
  assign o_tvalid_hb1 = 1'h0;
  assign i_tready_hb1 = 1'b0;
  end
  if( NUM_HB > 1 ) begin
  axi_hb47 halfband2 (
    .aclk(clk),
    .aresetn(~(reset | clear | reset_on_change)),
    .s_axis_data_tvalid(o_tvalid_hb1),
    .s_axis_data_tready(i_tready_hb2),
    .s_axis_data_tdata({o_tdata_hb1[2*CWIDTH-1:CWIDTH] << 2, o_tdata_hb1[CWIDTH-1:0] << 2}),
    .m_axis_data_tvalid(o_tvalid_hb2),
    .m_axis_data_tready(o_tready_hb2),
    .m_axis_data_tdata(o_tdata_hb2)
  );
  end else begin
  assign o_tdata_hb2 = 'h0;
  assign o_tvalid_hb2 = 1'h0;
  assign i_tready_hb2 = 1'b0;
  end
  if( NUM_HB > 2 ) begin
  axi_hb47 halfband3 (
    .aclk(clk),
    .aresetn(~(reset | clear | reset_on_change)),
    .s_axis_data_tvalid(o_tvalid_hb2),
    .s_axis_data_tready(i_tready_hb3),
    .s_axis_data_tdata({o_tdata_hb2[2*CWIDTH-1:CWIDTH] << 2, o_tdata_hb2[CWIDTH-1:0] << 2}),
    .m_axis_data_tvalid(o_tvalid_hb3),
    .m_axis_data_tready(o_tready_hb3),
    .m_axis_data_tdata(o_tdata_hb3)
  );
  end else begin
  assign o_tdata_hb3 = 'h0;
  assign o_tvalid_hb3 = 1'h0;
  assign i_tready_hb3 = 1'b0;
  end
  endgenerate
 /**************************************************************************
  * Halfband selection multiplexing
  *************************************************************************/
  wire [2*CWIDTH-1:0] o_tdata_cic;
  wire [2*CWIDTH-1:0] o_cic;
  wire o_tvalid_cic, i_tready_cic;
  wire o_tready_cic;

  assign o_tdata_halfbands = (hb_rate == 2'b0) ? o_tdata_extd :
                             (hb_rate == 2'b1) ?  {o_tdata_hb1[2*CWIDTH-1:CWIDTH] << 2, o_tdata_hb1[CWIDTH-1:0] << 2} :
                             (hb_rate == 2'b10) ? {o_tdata_hb2[2*CWIDTH-1:CWIDTH] << 2, o_tdata_hb2[CWIDTH-1:0] << 2} :
                                                  {o_tdata_hb3[2*CWIDTH-1:CWIDTH] << 2, o_tdata_hb3[CWIDTH-1:0] << 2};
  // Clearing valid on rate change as the halfbands take 2 cycles to clear
  assign o_tvalid_halfbands = reset_on_live_change ? 1'b0 :
                              (hb_rate == 2'b0)    ?  i_tvalid :
                              (hb_rate == 2'b1)    ?  o_tvalid_hb1 :
                              (hb_rate == 2'b10)    ? o_tvalid_hb2 :
                                                      o_tvalid_hb3;
  // Throttle input data while rate change is going on
  assign i_tready     =  reset_on_live_change ? 1'b0 :
                         (hb_rate == 2'b0)    ? i_tready_cic :
                                                i_tready_hb1;
  assign o_tready_hb1 =  reset_on_live_change ? 1'b0 :
                         (hb_rate == 2'b1)    ? i_tready_cic :
                                                i_tready_hb2;
  assign o_tready_hb2 =  reset_on_live_change ? 1'b0 :
                         (hb_rate == 2'b10)   ? i_tready_cic :
                                                i_tready_hb3;

  assign o_tready_hb3 =  reset_on_live_change ? 1'b0 : i_tready_cic;

 /**************************************************************************
  * Ettus CIC; the Xilinx CIC has a minimum interpolation of 4,
  * so we use the strobed version and convert to and from AXI.
  *************************************************************************/
  wire to_cic_stb, from_cic_stb;
  wire [2*CWIDTH-1:0] to_cic_data;
  wire [CWIDTH-1:0] i_cic;
  wire [CWIDTH-1:0] q_cic;

  // Convert from AXI to strobed and back to AXI again for the CIC interpolation module
  axi_to_strobed #(.WIDTH(2*CWIDTH), .FIFO_SIZE(1), .MIN_RATE(128)) axi_to_strobed (
    .clk(clk), .reset(reset | reset_on_change), .clear(clear),
    .out_rate(cic_interp_rate), .ready(i_tready_cartesian & o_tready), .error(),
    .i_tdata(o_tdata_halfbands), .i_tvalid(o_tvalid_halfbands), .i_tlast(1'b0), .i_tready(i_tready_cic),
    .out_stb(to_cic_stb), .out_last(), .out_data(to_cic_data)
  );

  cic_interpolate #(.WIDTH(CWIDTH), .N(4), .MAX_RATE(CIC_MAX_INTERP)) cic_interpolate_i (
    .clk(clk), .reset(reset | clear | reset_on_change),
    .rate_stb(reset_on_change),
    .rate(cic_interp_rate), .strobe_in(to_cic_stb), .strobe_out(from_cic_stb),
    .signal_in(to_cic_data[2*CWIDTH-1:CWIDTH]), .signal_out(i_cic)
  );

  cic_interpolate #(.WIDTH(CWIDTH), .N(4), .MAX_RATE(CIC_MAX_INTERP)) cic_interpolate_q (
    .clk(clk), .reset(reset | clear | reset_on_change),
    .rate_stb(reset_on_change),
    .rate(cic_interp_rate), .strobe_in(to_cic_stb), .strobe_out(),
    .signal_in(to_cic_data[CWIDTH-1:0]), .signal_out(q_cic)
  );

  assign o_cic = {i_cic, q_cic};

  //FIFO_SIZE = 8 infers a bram fifo
  strobed_to_axi #(.WIDTH(2*CWIDTH), .FIFO_SIZE(8)) strobed_to_axi (
    .clk(clk), .reset(reset | reset_on_change), .clear(clear),
    .in_stb(from_cic_stb), .in_data(o_cic), .in_last(1'b0),
    .o_tdata(o_tdata_cic), .o_tvalid(o_tvalid_cic), .o_tlast(), .o_tready(o_tready_cic)
  );


 /**************************************************************************
  * Clip back to 16 bits
  *************************************************************************/
  wire o_tvalid_clip;

  axi_round_and_clip_complex #(
    .WIDTH_IN(CWIDTH), .WIDTH_OUT(WIDTH), .CLIP_BITS(CWIDTH-WIDTH)) // No rounding, all clip
  axi_round_and_clip_complex (
    .clk(clk), .reset(reset | clear | reset_on_change),
    .i_tdata(o_tdata_cic), .i_tlast(1'b0), .i_tvalid(o_tvalid_cic), .i_tready(o_tready_cic),
    .o_tdata(o_tdata), .o_tlast(), .o_tvalid(o_tvalid_clip), .o_tready(i_tready_cartesian));

  assign o_tvalid = reset_on_live_change ? 1'b0 : o_tvalid_clip;
  assign i_tready_cartesian = reset_on_live_change ? 1'b0 : o_tready;

  // Note: To facilitate timed CORDIC tunes, the code has been moved outside
  //       the duc module to cordic_timed.v.

endmodule // duc