//
// Copyright 2016 Ettus Research
// Copyright 2018 Ettus Research, a National Instruments Company
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//

module cic_interpolate #(
  parameter WIDTH = 16,
  parameter N = 4,
  parameter MAX_RATE = 128
)(
  input clk,
  input reset,
  input rate_stb,
  input [$clog2(MAX_RATE+1)-1:0] rate, // +1 due to $clog2() rounding
  input  strobe_in,
  output reg strobe_out,
  input [WIDTH-1:0] signal_in,
  output reg [WIDTH-1:0] signal_out
);

  wire [WIDTH+$clog2(MAX_RATE**(N-1))-1:0] signal_in_ext;
  reg  [WIDTH+$clog2(MAX_RATE**(N-1))-1:0] integrator [0:N-1];
  reg  [WIDTH+$clog2(MAX_RATE**(N-1))-1:0] differentiator [0:N-1];
  reg  [WIDTH+$clog2(MAX_RATE**(N-1))-1:0] pipeline [0:N-1];
  reg  [WIDTH+$clog2(MAX_RATE**(N-1))-1:0] sampler;

  reg  [N-1:0] strobe_diff;
  reg  [N-1:0] strobe_integ;
  reg          strobe_sampler;

  integer i;

  sign_extend #(WIDTH,WIDTH+$clog2(MAX_RATE**(N-1))) ext_input (.in(signal_in),.out(signal_in_ext));

  // Differentiate
  always @(posedge clk) begin
    if (reset) begin
      strobe_diff         <= 'd0;
      for (i = 0; i < N; i = i + 1) begin
        differentiator[i] <= 0;
        pipeline[i]       <= 0;
      end
    end else begin
      strobe_diff         <= {strobe_diff[N-2:0], strobe_in};
      if (strobe_in) begin
        differentiator[0] <= signal_in_ext;
        pipeline[0]       <= signal_in_ext - differentiator[0];
      end
      for (i = 1; i < N; i = i + 1) begin
        if (strobe_diff[i-1]) begin
          differentiator[i] <= pipeline[i-1];
          pipeline[i]       <= pipeline[i-1] - differentiator[i];
        end
      end
    end
  end

  // Strober
  reg [$clog2(MAX_RATE+1)-1:0] counter;
  wire strobe_out_int;

  always @(posedge clk) begin
    if (reset | rate_stb) begin
      counter <= rate;
    end else if (strobe_diff[N-1]) begin
      counter <= rate - 1;
    end else begin
      if (counter == 0) begin
        counter <= rate;
      end else if (counter < rate) begin
        counter <= counter - 1;
      end
    end
  end

  assign strobe_out_int = (counter < rate) & ~rate_stb;

  // Integrate
  always @(posedge clk) begin
    if (reset) begin
      strobe_sampler    <= 1'b0;
      strobe_integ      <= 'd0;
      for (i = 0; i < N; i = i + 1) begin
        integrator[i]   <= 0;
      end
    end else begin
      strobe_sampler      <= strobe_diff[N-1];
      if (strobe_diff[N-1]) begin
        sampler           <= pipeline[N-1];
      end
      strobe_integ        <= {strobe_integ[N-2:0],strobe_out_int};
      if (strobe_sampler) begin
        integrator[0] <= integrator[0] + sampler;
      end
      for (i = 1; i < N; i = i + 1) begin
        if (strobe_integ[i-1]) begin
          integrator[i] <= integrator[i] + integrator[i-1];
        end
      end
    end
  end

  genvar l;
  wire [WIDTH-1:0] signal_out_shifted[0:MAX_RATE];
  wire signal_out_shifted_strobe[0:MAX_RATE];
  generate
    for (l = 0; l <= MAX_RATE; l = l + 1) begin
      axi_round #(
        .WIDTH_IN((l == 0 || l == 1) ? WIDTH : $clog2(l**(N-1))+WIDTH),
        .WIDTH_OUT(WIDTH))
      axi_round (
        .clk(clk), .reset(reset),
        .i_tdata((l == 0 || l == 1) ? integrator[N-1][WIDTH-1:0] : integrator[N-1][$clog2(l**(N-1))+WIDTH-1:0]),
        .i_tlast(1'b0), .i_tvalid(strobe_integ[N-1]), .i_tready(),
        .o_tdata(signal_out_shifted[l]), .o_tlast(), .o_tvalid(signal_out_shifted_strobe[l]), .o_tready(1'b1));
    end
  endgenerate

  // Output register
  always @(posedge clk) begin
    if (reset) begin
      strobe_out <= 1'b0;
      signal_out <= 'd0;
    end else begin
      strobe_out <= signal_out_shifted_strobe[0]; // Any of the strobes will work here
      signal_out <= signal_out_shifted[rate];
    end
  end

endmodule