aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/rfnoc/dds_freq_tune.v
blob: f1dc44b1746357f55665e3b86e7eb5bb79b5833d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
//
// 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