aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/rfnoc/dds_wrapper.v
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/dds_wrapper.v')
-rw-r--r--fpga/usrp3/lib/rfnoc/dds_wrapper.v160
1 files changed, 160 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/rfnoc/dds_wrapper.v b/fpga/usrp3/lib/rfnoc/dds_wrapper.v
new file mode 100644
index 000000000..d9151afa8
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/dds_wrapper.v
@@ -0,0 +1,160 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: dds_wrapper
+//
+// Description:
+//
+// This module computes the complex number e^(j*2*pi*phase). 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 a
+// phase in the range [0, 1.0). The output consists of two 16-bit signed
+// fixed-point values with 14 fractional bits. The value sin(2*pi*phase) is
+// in the upper 16 bits and cos(2*pi*phase) is in the lower 16-bits. This
+// puts the output in {Q,I} order.
+//
+// This is a wrapper for the dds_sin_cos_lut_only IP, which is based on the
+// Xilinx DDS Compiler. This IP has the undesirable behavior that input must
+// be provided to flush out any data stuck in its pipeline. This wrapper
+// hides that behavior so that every input causes a corresponding output,
+// even if the input stops.
+//
+// NOTE: The DDS IP requires at least 2 cycles of reset.
+//
+// Parameters:
+//
+// The parameters in this module should not be modified. They match the IP
+// configuration.
+//
+
+
+module dds_wrapper #(
+ parameter PHASE_W = 24,
+ parameter OUTPUT_W = 32
+) (
+ input wire clk,
+ input wire rst,
+
+ // Phase input
+ input wire [ PHASE_W-1:0] s_axis_phase_tdata,
+ input wire s_axis_phase_tvalid,
+ input wire s_axis_phase_tlast,
+ output wire s_axis_phase_tready,
+
+ // IQ output (Q in the upper, I in the lower bits)
+ output wire [OUTPUT_W-1:0] m_axis_data_tdata,
+ output wire m_axis_data_tvalid,
+ output wire m_axis_data_tlast,
+ input wire m_axis_data_tready
+);
+
+ // Width of number needed to represent the DDS fullness. This value was
+ // determined experimentally. The max fullness was 33.
+ localparam FULLNESS_W = 6;
+
+ wire [PHASE_W-1:0] phase_tdata;
+ wire phase_tvalid;
+ wire phase_tlast;
+ wire phase_tready;
+
+ wire [OUTPUT_W-1:0] dds_tdata;
+ wire dds_tvalid;
+ wire dds_tlast;
+ wire dds_tready;
+
+
+ //---------------------------------------------------------------------------
+ // DDS Fullness Counter
+ //---------------------------------------------------------------------------
+ //
+ // Count the number of valid samples in the DDS's data pipeline.
+ //
+ //---------------------------------------------------------------------------
+
+ // The fullness counter must be large enough for DDS's latency.
+ reg [FULLNESS_W-1:0] fullness = 0;
+ reg dds_has_data = 0;
+
+ wire increment = s_axis_phase_tvalid & s_axis_phase_tready;
+ wire decrement = m_axis_data_tvalid & m_axis_data_tready;
+
+ always @(posedge clk) begin
+ if (rst) begin
+ fullness <= 0;
+ dds_has_data <= 0;
+ end else begin
+ if (increment && !decrement) begin
+ //synthesis translate_off
+ if (fullness+1'b1 == 1'b0) begin
+ $display("ERROR: Fullness overflowed!");
+ end
+ //synthesis translate_on
+ fullness <= fullness + 1;
+ dds_has_data <= 1;
+ end else if (decrement && !increment) begin
+ //synthesis translate_off
+ if (fullness-1'b1 > fullness) begin
+ $display("ERROR: Fullness underflowed!");
+ end
+ //synthesis translate_on
+ fullness <= fullness - 1;
+ dds_has_data <= (fullness > 1);
+ end else begin
+ dds_has_data <= (fullness > 0);
+ end
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Input Logic
+ //---------------------------------------------------------------------------
+
+ assign s_axis_phase_tready = phase_tready;
+ assign phase_tlast = s_axis_phase_tlast;
+ assign phase_tdata = s_axis_phase_tdata;
+
+ // Always input something when the DDS has data stuck inside it so that all
+ // data gets flushed out automatically.
+ assign phase_tvalid = s_axis_phase_tvalid || dds_has_data;
+
+
+ //---------------------------------------------------------------------------
+ // DDS IP
+ //---------------------------------------------------------------------------
+
+ // Use the TUSER path on the DDS IP to indicate if the sample is empty and is
+ // just to flush the output.
+ wire flush_in = ~s_axis_phase_tvalid; // It's a flush if input is not valid
+ wire flush_out;
+
+ dds_sin_cos_lut_only dds_sin_cos_lut_only_i (
+ .aclk (clk),
+ .aresetn (~rst),
+ .s_axis_phase_tvalid (phase_tvalid),
+ .s_axis_phase_tready (phase_tready),
+ .s_axis_phase_tdata (phase_tdata),
+ .s_axis_phase_tlast (phase_tlast),
+ .s_axis_phase_tuser (flush_in),
+ .m_axis_data_tvalid (dds_tvalid),
+ .m_axis_data_tready (dds_tready),
+ .m_axis_data_tdata (dds_tdata),
+ .m_axis_data_tlast (dds_tlast),
+ .m_axis_data_tuser (flush_out)
+ );
+
+
+ //---------------------------------------------------------------------------
+ // Output Logic
+ //---------------------------------------------------------------------------
+
+ assign m_axis_data_tdata = dds_tdata;
+ assign m_axis_data_tlast = dds_tlast;
+
+ // Discard the current sample if it was for flushing.
+ assign m_axis_data_tvalid = dds_tvalid & ~flush_out;
+ assign dds_tready = m_axis_data_tready | flush_out;
+
+endmodule