aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/rfnoc/dds_wrapper.v
diff options
context:
space:
mode:
authorWade Fife <wade.fife@ettus.com>2021-06-15 14:14:14 -0500
committerWade Fife <wade.fife@ettus.com>2021-08-08 14:59:26 -0500
commit77975d108a704ce18ec52b4ee1764381b1893752 (patch)
tree21df90b7b78b67e8f28c6dc0a1de8a8e23d9e8fa /fpga/usrp3/lib/rfnoc/dds_wrapper.v
parentda4202e6f74796603072aa14544581604e81df02 (diff)
downloaduhd-77975d108a704ce18ec52b4ee1764381b1893752.tar.gz
uhd-77975d108a704ce18ec52b4ee1764381b1893752.tar.bz2
uhd-77975d108a704ce18ec52b4ee1764381b1893752.zip
fpga: rfnoc: Fix EOB loss in DUC
There were some rare corner cases where the EOB could get lost in the DUC due to the dds_timed logic not always passing it through as it should. This resulted in an underflow error message at the end of transmission. This commit also fixes an issue where part of the last packet used a frequency shift of 0 instead of the requested frequency shift, and an issue where the first few samples of a burst used the wrong frequency shift value. Part of the fix includes adding a TUSER port to dds_sin_cos_lut_only. The TUSER port is built into the IP but was disabled. It is now enabled and set to 1 bit wide. This has a very small effect on resource usage and can be left unconnected when not needed. The dds_freq_tune block was shared by the DUC and DDC. To avoid affecting the DDC, a new version, dds_freq_tune_duc, is being added for the DUC to use that has the necessary fixes. The new dds_wrapper.v is a wrapper for the dds_sin_cos_lut_only IP. This IP has the undesirable behavior that new inputs must be provided to push previous outputs through the IP. This wrapper hides that complexity by adding some logic to ensure all data gets pushed through automatically. This logic uses the TUSER port on the IP. Finally, a testbench for dds_timed was added.
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