//
// Copyright 2021 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: dds_freq_tune
//
// Description:
//
//   Performs a frequency shift on a signal by multiplying it with a complex
//   sinusoid synthesized from a DDS. This module expects samples data to be in
//   {Q,I} order.
//

module dds_freq_tune #(
  parameter WIDTH         = 24,
  parameter PHASE_WIDTH   = 24,
  parameter SIN_COS_WIDTH = 16,
  parameter OUTPUT_WIDTH  = 24
) (
  input clk,
  input reset,

  input eob,
  input rate_changed,

  input [15:0] dds_input_fifo_occupied,

  // IQ input
  input  [WIDTH*2-1:0] s_axis_din_tdata,
  input                s_axis_din_tlast,
  input                s_axis_din_tvalid,
  output               s_axis_din_tready,

  // Phase input from NCO
  input  [PHASE_WIDTH-1:0] s_axis_phase_tdata,
  input                    s_axis_phase_tlast,
  input                    s_axis_phase_tvalid,
  output                   s_axis_phase_tready,

  // IQ output
  output [OUTPUT_WIDTH*2-1:0] m_axis_dout_tdata,
  output                      m_axis_dout_tlast,
  output                      m_axis_dout_tvalid,
  input                       m_axis_dout_tready,

  // Debug signals
  output [                2:0] state_out,
  output                       phase_valid_hold_out,
  output [                7:0] phase_invalid_wait_count_out,
  output                       reset_dds_out,
  output                       m_axis_dds_tlast_out,
  output                       m_axis_dds_tvalid_out,
  output                       m_axis_dds_tready_out,
  output [SIN_COS_WIDTH*2-1:0] m_axis_dds_tdata_out
);

  // Wires for DDS output
  wire                       m_axis_dds_tlast;
  wire                       m_axis_dds_tvalid;
  wire                       m_axis_dds_tready;
  wire [SIN_COS_WIDTH*2-1:0] m_axis_dds_tdata;  // [31:16] = sin|q, [15:0]= cos|i

  reg        reset_reg;
  reg        phase_valid_hold;
  reg  [7:0] phase_invalid_wait_count;
  reg  [2:0] state;
  reg        phase_ready_wait;
  wire       s_axis_phase_tready_dds;

  // Initialize DDS resets to 1, since simulation model requires reset at time
  // 0 to avoid failure.
  reg reset_dds     = 1'b1;
  reg reset_dds_reg = 1'b1;

  // When we're holding valid, make ready low so no new data comes in.
  assign s_axis_phase_tready = s_axis_phase_tready_dds & ~phase_valid_hold;

  localparam INIT       = 3'b000;
  localparam VALID      = 3'b001;
  localparam WAIT       = 3'b010;
  localparam HOLD_VALID = 3'b011;

  // Reset needs to be 2 clk cycles minimum for Xilinx DDS IP
  always @(posedge clk) begin
    reset_reg     <= reset;
    reset_dds_reg <= reset_dds;
  end

  // This state machine resets the DDS when data stops coming and also holds
  // valid high until the last packet has been flushed through the DDS.
  always @(posedge clk) begin
    if(reset) begin
      state                    <= INIT;
      phase_valid_hold         <= 1'b0;
      phase_invalid_wait_count <= 16'h00;
      reset_dds                <= 1'b0;
    end else begin
      case(state)
        INIT : begin
          phase_valid_hold         <= 1'b0;
          phase_invalid_wait_count <= 16'h0000;
          reset_dds                <= 1'b0;
          if(s_axis_phase_tvalid) begin
            state <= VALID;
          end
        end
        VALID : begin
          if(~s_axis_phase_tvalid) begin
            state <= WAIT;
          end
        end
        WAIT : begin
          // Wait until we either get valid data or don't.
          if(m_axis_dds_tready) begin
            // Only increment when the downstream can accept data.
            phase_invalid_wait_count <= phase_invalid_wait_count + 4'b1;
          end
          if(s_axis_phase_tvalid) begin
            // If we get valid data shortly after, then don't push data through
            // and reset.
            state <= INIT;
          end else begin
            if(eob | (phase_invalid_wait_count >= 16'h40) | rate_changed) begin
              // If a valid never comes (EOB)
              state <= HOLD_VALID;
            end
          end
        end
        HOLD_VALID : begin
          // Hold valid to flush data through the DDS. The DDS IP won't empty
          // without additional transfers.
          phase_valid_hold <= 1'b1;
          // Wait for input FIFO to be empty
          if (~s_axis_din_tvalid) begin
            state     <= INIT;
            reset_dds <= 1'b1;
          end
        end
      endcase
    end
  end

  // DDS to generate sin/cos data from phase. It takes in a 24-bit phase value
  // and outputs two 16-bit values, with the sine value in the upper 16 bits
  // and the cosine value in the lower 16-bits.
  //
  // The phase input can be thought of as a 24-bit unsigned fixed-point value
  // with 24 fractional bits. In other words, the integer range of the input
  // maps to the the range [0, 2*pi) in radians.
  //
  // The output consists of two 16-bit signed fixed-point values with 14
  // fractional bits.
  //
  // This IP effectively computes Euler's formula, e^(j*2*pi*x) = cos(2*pi*x) +
  // j*sin(2*pi*x), where x is the phase value, and the output has the real
  // component in the lower bits and the imaginary component in the upper bits.
  dds_sin_cos_lut_only dds_sin_cos_lut_only_i (
    .aclk                (clk),
    .aresetn             (~(reset | reset_reg | reset_dds | reset_dds_reg)),
    .s_axis_phase_tvalid (s_axis_phase_tvalid | phase_valid_hold),
    .s_axis_phase_tready (s_axis_phase_tready_dds),
    .s_axis_phase_tlast  (s_axis_phase_tlast),
    .s_axis_phase_tdata  (s_axis_phase_tdata),   // [23 : 0]
    .m_axis_data_tvalid  (m_axis_dds_tvalid),
    .m_axis_data_tready  (m_axis_dds_tready),
    .m_axis_data_tlast   (m_axis_dds_tlast),
    .m_axis_data_tdata   (m_axis_dds_tdata)      // [31 : 0]
  );

  wire [        WIDTH*2-1:0] mult_in_a_tdata;
  wire                       mult_in_a_tvalid;
  wire                       mult_in_a_tready;
  wire                       mult_in_a_tlast;
  wire [SIN_COS_WIDTH*2-1:0] mult_in_b_tdata;
  wire                       mult_in_b_tvalid;
  wire                       mult_in_b_tready;
  wire                       mult_in_b_tlast;
  wire [           2*32-1:0] mult_out_tdata;
  wire                       mult_out_tvalid;
  wire                       mult_out_tready;
  wire                       mult_out_tlast;

  axi_sync #(
    .SIZE      (2),
    .WIDTH_VEC ({SIN_COS_WIDTH*2, WIDTH*2}),
    .FIFO_SIZE (0)
  ) axi_sync_i (
    .clk      (clk),
    .reset    (reset),
    .clear    (),
    .i_tdata  ({ m_axis_dds_tdata,  s_axis_din_tdata  }),
    .i_tlast  ({ m_axis_dds_tlast,  s_axis_din_tlast  }),
    .i_tvalid ({ m_axis_dds_tvalid, s_axis_din_tvalid }),
    .i_tready ({ m_axis_dds_tready, s_axis_din_tready }),
    .o_tdata  ({ mult_in_b_tdata,   mult_in_a_tdata   }),
    .o_tlast  ({ mult_in_b_tlast,   mult_in_a_tlast   }),
    .o_tvalid ({ mult_in_b_tvalid,  mult_in_a_tvalid  }),
    .o_tready ({ mult_in_b_tready,  mult_in_a_tready  })
  );

  // Use a complex multiplier to multiply the input sample (A) by the NCO
  // output (B). This multiplier has a 21-bit input A, 16-bit input B, and
  // 32-bit output. Due to AXI-Stream requirements, A is rounded up to 24-bit.
  //
  // Assuming default parameters and unchanged IP, The A input (sample) is
  // 21-bit with 15 fractional bits, and the B input (NCO) is 16-bit with 14
  // fractional bits. The full result would be 21+16+1 = 38 bits, but the
  // output is configured for 32, dropping the lower 6 bits. Therefore, the
  // result has 15+14-6 = 23 fractional bits.
  //
  // a = Input IQ data stream as 48-bit, lower bits i, upper bits q.
  // b = Output of DDS as 32 bit cos/sin, lower bits cos, upper bits sin.
  complex_multiplier_dds complex_multiplier_dds_i (
    .aclk               (clk),
    .aresetn            (~(reset | reset_reg)),
    .s_axis_a_tvalid    (mult_in_a_tvalid),
    .s_axis_a_tready    (mult_in_a_tready),
    .s_axis_a_tlast     (mult_in_a_tlast),
    .s_axis_a_tdata     ({mult_in_a_tdata}),    // [47 : 0]
    .s_axis_b_tvalid    (mult_in_b_tvalid),
    .s_axis_b_tready    (mult_in_b_tready),
    .s_axis_b_tlast     (mult_in_b_tlast),
    .s_axis_b_tdata     (mult_in_b_tdata),      // [31 : 0]
    .m_axis_dout_tvalid (mult_out_tvalid),
    .m_axis_dout_tready (mult_out_tready),
    .m_axis_dout_tlast  (mult_out_tlast),
    .m_axis_dout_tdata  (mult_out_tdata)        // [63 : 0]
  );

  // Round the 32-bit multiplier result down to 24 bits. This moves the binary
  // point so that we go from 23 fractional bits down to 15 fractional bits.
  axi_round_complex #(
    .WIDTH_IN  (32),
    .WIDTH_OUT (OUTPUT_WIDTH)
  ) axi_round_complex_i (
    .clk      (clk),
    .reset    (reset | reset_reg),
    .i_tdata  (mult_out_tdata),
    .i_tlast  (mult_out_tlast),
    .i_tvalid (mult_out_tvalid),
    .i_tready (mult_out_tready),
    .o_tdata  (m_axis_dout_tdata),
    .o_tlast  (m_axis_dout_tlast),
    .o_tvalid (m_axis_dout_tvalid),
    .o_tready (m_axis_dout_tready)
  );

  // Debug
  assign state_out                    = state;
  assign phase_valid_hold_out         = phase_valid_hold;
  assign phase_invalid_wait_count_out = phase_invalid_wait_count;
  assign reset_dds_out                = reset_dds;
  assign m_axis_dds_tlast_out         = m_axis_dds_tlast;
  assign m_axis_dds_tvalid_out        = m_axis_dds_tvalid;
  assign m_axis_dds_tready_out        = m_axis_dds_tready;
  assign m_axis_dds_tdata_out         = m_axis_dds_tdata;

endmodule
