1//
2// Copyright 2016 Ettus Research, a National Instruments Company
3//
4// SPDX-License-Identifier: LGPL-3.0-or-later
5//
6// DDS that supports timed commands via the settings bus
7
8module dds_timed #(
9  parameter SR_FREQ_ADDR      = 0,
10  parameter SR_SCALE_IQ_ADDR  = 1,
11  parameter CMD_FIFO_SIZE     = 5,
12  parameter WIDTH             = 16,
13  parameter DDS_WIDTH         = 24,
14  parameter PHASE_WIDTH       = 24,
15  parameter PHASE_ACCUM_WIDTH = 32,
16  parameter SCALING_WIDTH     = 18,
17  parameter HEADER_WIDTH      = 128,
18  parameter HEADER_FIFO_SIZE  = 5,
19  parameter SR_AWIDTH         = 8,
20  parameter SR_DWIDTH         = 32,
21  parameter SR_TWIDTH         = 64
22)(
23  input clk, input reset, input clear,
24  output timed_cmd_fifo_full,
25  input set_stb, input [SR_AWIDTH-1:0] set_addr, input [SR_DWIDTH-1:0] set_data,
26  input [SR_TWIDTH-1:0] set_time, input set_has_time,
27  input [2*WIDTH-1:0] i_tdata, input i_tlast, input i_tvalid, output i_tready, input [HEADER_WIDTH-1:0] i_tuser,
28  output [2*WIDTH-1:0] o_tdata, output o_tlast, output o_tvalid, input o_tready, output [HEADER_WIDTH-1:0] o_tuser
29);
30
31  /**************************************************************************
32  * Track VITA time
33  *************************************************************************/
34  wire [2*WIDTH-1:0] int_tdata;
35  wire [HEADER_WIDTH-1:0] int_tuser;
36  wire int_tlast, int_tvalid, int_tready, int_tag;
37  wire [SR_AWIDTH-1:0] out_set_addr, timed_set_addr;
38  wire [SR_DWIDTH-1:0] out_set_data, timed_set_data;
39  wire out_set_stb, timed_set_stb;
40  wire eob;
41
42  axi_tag_time #(
43    .WIDTH(2*WIDTH),
44    .NUM_TAGS(1),
45    .SR_TAG_ADDRS(SR_FREQ_ADDR))
46  axi_tag_time (
47    .clk(clk),
48    .reset(reset),
49    .clear(clear),
50    .tick_rate(16'd1),
51    .timed_cmd_fifo_full(timed_cmd_fifo_full),
52    .s_axis_data_tdata(i_tdata), .s_axis_data_tlast(i_tlast),
53    .s_axis_data_tvalid(i_tvalid), .s_axis_data_tready(i_tready),
54    .s_axis_data_tuser(i_tuser),
55    .m_axis_data_tdata(int_tdata), .m_axis_data_tlast(int_tlast),
56    .m_axis_data_tvalid(int_tvalid), .m_axis_data_tready(int_tready),
57    .m_axis_data_tuser(int_tuser), .m_axis_data_tag(int_tag),
58    .in_set_stb(set_stb), .in_set_addr(set_addr), .in_set_data(set_data),
59    .in_set_time(set_time), .in_set_has_time(set_has_time),
60    .out_set_stb(out_set_stb), .out_set_addr(out_set_addr), .out_set_data(out_set_data),
61    .timed_set_stb(timed_set_stb), .timed_set_addr(timed_set_addr), .timed_set_data(timed_set_data));
62
63  wire [2*WIDTH-1:0] dds_in_tdata, unused_tdata;
64  wire [HEADER_WIDTH-1:0] header_in_tdata, header_out_tdata, unused_tuser;
65  wire dds_in_tlast, dds_in_tvalid, dds_in_tready, dds_in_tag;
66  wire header_in_tvalid, header_in_tready, header_in_tlast, unused_tag;
67  wire header_out_tvalid, header_out_tready;
68
69  split_stream #(
70    .WIDTH(2*WIDTH+HEADER_WIDTH+1), .ACTIVE_MASK(4'b0011))
71  split_head (
72    .clk(clk), .reset(reset), .clear(clear),
73    .i_tdata({int_tdata,int_tuser,int_tag}), .i_tlast(int_tlast),
74    .i_tvalid(int_tvalid), .i_tready(int_tready),
75    .o0_tdata({dds_in_tdata,unused_tuser,dds_in_tag}), .o0_tlast(dds_in_tlast),
76    .o0_tvalid(dds_in_tvalid), .o0_tready(dds_in_tready),
77    .o1_tdata({unused_tdata,header_in_tdata,unused_tag}), .o1_tlast(header_in_tlast),
78    .o1_tvalid(header_in_tvalid), .o1_tready(header_in_tready),
79    .o2_tready(1'b0), .o3_tready(1'b0));
80
81  axi_fifo #(
82    .WIDTH(HEADER_WIDTH), .SIZE(HEADER_FIFO_SIZE))
83  axi_fifo_header (
84    .clk(clk), .reset(reset), .clear(clear),
85    .i_tdata(header_in_tdata), .i_tvalid(header_in_tvalid & header_in_tlast), .i_tready(header_in_tready),
86    .o_tdata(header_out_tdata), .o_tvalid(header_out_tvalid),
87    .o_tready(header_out_tready), // Consume header on last output sample
88    .space(), .occupied());
89
90  assign eob = header_in_tdata[124];
91
92  /**************************************************************************
93  * Settings Regs
94  *************************************************************************/
95  wire [PHASE_ACCUM_WIDTH-1:0] phase_inc_tdata, phase_inc_timed_tdata;
96  wire phase_inc_tlast, phase_inc_tvalid, phase_inc_tready;
97  wire phase_inc_timed_tlast, phase_inc_timed_tready , phase_inc_timed_tvalid;
98
99  axi_setting_reg #(
100    .ADDR(SR_FREQ_ADDR), .AWIDTH(SR_AWIDTH), .WIDTH(PHASE_ACCUM_WIDTH), .STROBE_LAST(1))
101  set_freq (
102    .clk(clk), .reset(reset),
103    .set_stb(out_set_stb), .set_addr(out_set_addr), .set_data(out_set_data),
104    .o_tdata(phase_inc_tdata), .o_tlast(phase_inc_tlast), .o_tvalid(phase_inc_tvalid), .o_tready(phase_inc_tready));
105
106  axi_setting_reg #(
107    .ADDR(SR_FREQ_ADDR), .USE_FIFO(1), .FIFO_SIZE(CMD_FIFO_SIZE), .AWIDTH(SR_AWIDTH), .WIDTH(PHASE_ACCUM_WIDTH), .STROBE_LAST(1))
108  set_freq_timed (
109    .clk(clk), .reset(reset),
110    .set_stb(timed_set_stb), .set_addr(timed_set_addr), .set_data(timed_set_data),
111    .o_tdata(phase_inc_timed_tdata), .o_tlast(phase_inc_timed_tlast), .o_tvalid(phase_inc_timed_tvalid), .o_tready(phase_inc_timed_tready));
112
113  wire [SCALING_WIDTH-1:0] scaling_tdata;
114  wire scaling_tvalid, scaling_tready;
115
116  axi_setting_reg #(
117    .ADDR(SR_SCALE_IQ_ADDR), .AWIDTH(SR_AWIDTH), .WIDTH(SCALING_WIDTH), .REPEATS(1))
118  set_scale (
119    .clk(clk), .reset(reset),
120    .set_stb(out_set_stb), .set_addr(out_set_addr), .set_data(out_set_data),
121    .o_tdata(scaling_tdata), .o_tlast(), .o_tvalid(scaling_tvalid), .o_tready(scaling_tready));
122
123  /**************************************************************************
124  * DDS + Complex Mult + Phase Accumulator
125  *************************************************************************/
126  wire [PHASE_ACCUM_WIDTH-1:0] phase_inc_mux_tdata;
127  reg [PHASE_ACCUM_WIDTH-1:0] phase_inc;
128  wire phase_inc_mux_tlast, phase_inc_mux_tvalid, phase_inc_mux_tready;
129  reg [PHASE_ACCUM_WIDTH-1:0] phase;
130
131  wire [PHASE_WIDTH-1:0] phase_tdata = phase[PHASE_ACCUM_WIDTH-1:PHASE_ACCUM_WIDTH-PHASE_WIDTH];
132  wire phase_tvalid, phase_tready, phase_tlast;
133
134  wire [WIDTH*2-1:0] dds_in_fifo_tdata;
135  wire dds_in_fifo_tvalid, dds_in_fifo_tready, dds_in_fifo_tlast;
136  wire dds_out_tlast, dds_out_tvalid, dds_out_tready;
137
138  wire [DDS_WIDTH-1:0] dds_in_i_tdata, dds_in_q_tdata;
139  wire [DDS_WIDTH-1:0] dds_out_i_tdata, dds_out_q_tdata;
140  wire [15:0] dds_input_fifo_space, dds_input_fifo_occupied;
141
142  wire [WIDTH*2-1:0] dds_in_sync_tdata;
143  wire dds_in_sync_tvalid, dds_in_sync_tready, dds_in_sync_tlast;
144  wire [PHASE_WIDTH-1:0] phase_sync_tdata;
145  wire phase_sync_tvalid, phase_sync_tready, phase_sync_tlast;
146
147  assign phase_inc_mux_tdata    = phase_inc_timed_tready ? phase_inc_timed_tdata : phase_inc_tdata;
148  assign phase_inc_mux_tlast    = phase_inc_timed_tready ? phase_inc_timed_tlast : phase_inc_tlast;
149  assign phase_inc_mux_tvalid   = phase_inc_timed_tready ? phase_inc_timed_tvalid : phase_inc_tvalid;
150  assign phase_inc_tready       = phase_inc_mux_tready;
151  assign phase_inc_timed_tready = phase_inc_mux_tready & dds_in_tag;
152  assign phase_inc_mux_tready = phase_tready;
153
154  // phase is only valid when input i/q data stream is valid
155  assign phase_tvalid = dds_in_tvalid;
156  assign phase_tlast = dds_in_tlast;
157
158  always @(posedge clk) begin
159    if (reset | clear) begin
160      phase_inc <= 0;
161    end else if (phase_inc_mux_tvalid & phase_inc_mux_tready) begin
162      phase_inc <= phase_inc_mux_tdata;
163    end
164  end
165
166  // NCO, increment phase input to DDS SIN/COS LUT
167  always @(posedge clk) begin
168    if (reset | clear | (phase_inc_mux_tvalid & phase_inc_mux_tready) | eob) begin
169      phase <= 0;
170    end else if (dds_in_tvalid & dds_in_tready) begin //only increment phase when data into dds is valid and data fifo is ready
171      phase <= phase + phase_inc;
172    end
173  end
174
175
176  // Sync the two path's pipeline delay.
177  // This is needed to ensure that applying the phase update happens on the
178  // correct sample regardless of differing downstream path delays.
179  axi_sync #(
180    .SIZE(2),
181    .WIDTH_VEC({PHASE_WIDTH,2*WIDTH}), // Vector of widths, each width is defined by a 32-bit value
182    .FIFO_SIZE(0))
183  axi_sync (
184    .clk(clk), .reset(reset), .clear(clear),
185    .i_tdata({phase_tdata,dds_in_tdata}),
186    .i_tlast({phase_tlast,dds_in_tlast}),
187    .i_tvalid({phase_tvalid,dds_in_tvalid}),
188    .i_tready({phase_tready,dds_in_tready}),
189    .o_tdata({phase_sync_tdata,dds_in_sync_tdata}),
190    .o_tlast({phase_sync_tlast,dds_in_sync_tlast}),
191    .o_tvalid({phase_sync_tvalid,dds_in_sync_tvalid}),
192    .o_tready({phase_sync_tready,dds_in_sync_tready}));
193
194  // fifo to hold input data while pipeline catches up in dds
195  // this is blocked by the axi_sync following the dds
196  axi_fifo #(.WIDTH(2*WIDTH+1), .SIZE(5)) dds_input_fifo(
197    .clk(clk), .reset(reset), .clear(clear),
198    .i_tdata({dds_in_sync_tlast,dds_in_sync_tdata}), .i_tvalid(dds_in_sync_tvalid), .i_tready(dds_in_sync_tready),
199    .o_tdata({dds_in_fifo_tlast,dds_in_fifo_tdata}), .o_tvalid(dds_in_fifo_tvalid), .o_tready(dds_in_fifo_tready),
200    .space(dds_input_fifo_space), .occupied(dds_input_fifo_occupied)
201  );
202
203  // after fifo, do q quick sign extend op to get up to 24 bits. to match how the cordic deals with the data path.
204  sign_extend #(
205    .bits_in(WIDTH), .bits_out(DDS_WIDTH))
206  sign_extend_dds_i (
207    .in(dds_in_fifo_tdata[2*WIDTH-1:WIDTH]), .out(dds_in_i_tdata));
208
209  sign_extend #(
210    .bits_in(WIDTH), .bits_out(DDS_WIDTH))
211  sign_extend_dds_q (
212    .in(dds_in_fifo_tdata[WIDTH-1:0]), .out(dds_in_q_tdata));
213
214
215  // Wrapper for Xilinx IP AXI DDS + Complex Multiply
216  // NOTE: Seems Xilinx IP expects opposite I/Q combined complex data buses, so they are swapped here.
217  dds_freq_tune dds_freq_tune_inst (
218    .clk(clk),
219    .reset(reset | clear),
220    .eob(eob),
221    .rate_changed(1'b0),
222    .dds_input_fifo_occupied(dds_input_fifo_occupied),
223    /* IQ input */
224    .s_axis_din_tlast(dds_in_fifo_tlast),
225    .s_axis_din_tvalid(dds_in_fifo_tvalid),
226    .s_axis_din_tready(dds_in_fifo_tready),
227    .s_axis_din_tdata({dds_in_q_tdata, dds_in_i_tdata}),
228    /* Phase input from NCO */
229    .s_axis_phase_tlast(phase_sync_tlast),
230    .s_axis_phase_tvalid(phase_sync_tvalid),
231    .s_axis_phase_tready(phase_sync_tready),
232    .s_axis_phase_tdata(phase_sync_tdata), //24 bit
233    /* IQ output */
234    .m_axis_dout_tlast(dds_out_tlast),
235    .m_axis_dout_tvalid(dds_out_tvalid),
236    .m_axis_dout_tready(dds_out_tready),
237    .m_axis_dout_tdata({dds_out_q_tdata, dds_out_i_tdata})
238     //debug signals
239  );
240  /************************************************************************
241  * Perform scaling on the IQ output
242  ************************************************************************/
243  wire [DDS_WIDTH+SCALING_WIDTH-1:0] scaled_i_tdata, scaled_q_tdata;
244  wire scaled_tlast, scaled_tvalid, scaled_tready;
245
246  mult #(
247   .WIDTH_A(DDS_WIDTH),
248   .WIDTH_B(SCALING_WIDTH),
249   .WIDTH_P(DDS_WIDTH+SCALING_WIDTH),
250   .DROP_TOP_P(4),
251   .LATENCY(3),
252   .CASCADE_OUT(0))
253  i_mult (
254    .clk(clk), .reset(reset | clear),
255    .a_tdata(dds_out_i_tdata), .a_tlast(dds_out_tlast), .a_tvalid(dds_out_tvalid), .a_tready(dds_out_tready),
256    .b_tdata(scaling_tdata), .b_tlast(1'b0), .b_tvalid(dds_out_tvalid /* aligning scaling_tdata with dds_tdata */), .b_tready(scaling_tready),
257    .p_tdata(scaled_i_tdata), .p_tlast(scaled_tlast), .p_tvalid(scaled_tvalid), .p_tready(scaled_tready));
258
259  mult #(
260   .WIDTH_A(DDS_WIDTH),
261   .WIDTH_B(SCALING_WIDTH),
262   .WIDTH_P(DDS_WIDTH+SCALING_WIDTH),
263   .DROP_TOP_P(4),
264   .LATENCY(3),
265   .CASCADE_OUT(0))
266  q_mult (
267    .clk(clk), .reset(reset | clear),
268    .a_tdata(dds_out_q_tdata), .a_tlast(), .a_tvalid(dds_out_tvalid), .a_tready(),
269    .b_tdata(scaling_tdata), .b_tlast(1'b0), .b_tvalid(dds_out_tvalid /* aligning scaling_tdata with dds_tdata */), .b_tready(),
270    .p_tdata(scaled_q_tdata), .p_tlast(), .p_tvalid(), .p_tready(scaled_tready));
271
272  wire [2*WIDTH-1:0] sample_tdata;
273  wire sample_tlast, sample_tvalid, sample_tready;
274
275  axi_round_and_clip_complex #(
276    .WIDTH_IN(DDS_WIDTH+SCALING_WIDTH), .WIDTH_OUT(WIDTH), .CLIP_BITS(12))
277  axi_round_and_clip_complex (
278    .clk(clk), .reset(reset | clear),
279    .i_tdata({scaled_i_tdata, scaled_q_tdata}), .i_tlast(scaled_tlast), .i_tvalid(scaled_tvalid), .i_tready(scaled_tready),
280    .o_tdata(sample_tdata), .o_tlast(sample_tlast), .o_tvalid(sample_tvalid), .o_tready(sample_tready));
281
282  // Throttle output on last sample if header is not valid
283  assign header_out_tready = sample_tlast & sample_tvalid & o_tready;
284  assign sample_tready     = (sample_tvalid & sample_tlast) ? (header_out_tvalid & o_tready) : o_tready;
285  assign o_tvalid          = (sample_tvalid & sample_tlast) ? header_out_tvalid : sample_tvalid;
286  assign o_tlast           = sample_tlast;
287  assign o_tdata           = sample_tdata;
288  assign o_tuser           = header_out_tdata;
289
290endmodule
291