aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/lib')
-rw-r--r--fpga/usrp3/lib/ip/dds_sin_cos_lut_only/dds_sin_cos_lut_only.xci14
-rw-r--r--fpga/usrp3/lib/rfnoc/Makefile.srcs2
-rw-r--r--fpga/usrp3/lib/rfnoc/dds_freq_tune_duc.v228
-rw-r--r--fpga/usrp3/lib/rfnoc/dds_timed.v718
-rw-r--r--fpga/usrp3/lib/rfnoc/dds_wrapper.v160
-rw-r--r--fpga/usrp3/lib/rfnoc/sim/dds_timed_tb/Makefile68
-rw-r--r--fpga/usrp3/lib/rfnoc/sim/dds_timed_tb/dds_timed_tb.sv886
7 files changed, 1858 insertions, 218 deletions
diff --git a/fpga/usrp3/lib/ip/dds_sin_cos_lut_only/dds_sin_cos_lut_only.xci b/fpga/usrp3/lib/ip/dds_sin_cos_lut_only/dds_sin_cos_lut_only.xci
index 4220fa7c4..0efbb6929 100644
--- a/fpga/usrp3/lib/ip/dds_sin_cos_lut_only/dds_sin_cos_lut_only.xci
+++ b/fpga/usrp3/lib/ip/dds_sin_cos_lut_only/dds_sin_cos_lut_only.xci
@@ -35,7 +35,7 @@
<spirit:configurableElementValue spirit:referenceId="BUSIFPARAM_VALUE.M_AXIS_DATA.TDATA_NUM_BYTES">4</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="BUSIFPARAM_VALUE.M_AXIS_DATA.TDEST_WIDTH">0</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="BUSIFPARAM_VALUE.M_AXIS_DATA.TID_WIDTH">0</spirit:configurableElementValue>
- <spirit:configurableElementValue spirit:referenceId="BUSIFPARAM_VALUE.M_AXIS_DATA.TUSER_WIDTH">0</spirit:configurableElementValue>
+ <spirit:configurableElementValue spirit:referenceId="BUSIFPARAM_VALUE.M_AXIS_DATA.TUSER_WIDTH">1</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="BUSIFPARAM_VALUE.M_AXIS_PHASE.CLK_DOMAIN"/>
<spirit:configurableElementValue spirit:referenceId="BUSIFPARAM_VALUE.M_AXIS_PHASE.FREQ_HZ">100000000</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="BUSIFPARAM_VALUE.M_AXIS_PHASE.HAS_TKEEP">0</spirit:configurableElementValue>
@@ -74,7 +74,7 @@
<spirit:configurableElementValue spirit:referenceId="BUSIFPARAM_VALUE.S_AXIS_PHASE.TDATA_NUM_BYTES">3</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="BUSIFPARAM_VALUE.S_AXIS_PHASE.TDEST_WIDTH">0</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="BUSIFPARAM_VALUE.S_AXIS_PHASE.TID_WIDTH">0</spirit:configurableElementValue>
- <spirit:configurableElementValue spirit:referenceId="BUSIFPARAM_VALUE.S_AXIS_PHASE.TUSER_WIDTH">0</spirit:configurableElementValue>
+ <spirit:configurableElementValue spirit:referenceId="BUSIFPARAM_VALUE.S_AXIS_PHASE.TUSER_WIDTH">1</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_ACCUMULATOR_WIDTH">24</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_AMPLITUDE">1</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_CHANNELS">1</spirit:configurableElementValue>
@@ -95,7 +95,7 @@
<spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_MEM_TYPE">1</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_MODE_OF_OPERATION">0</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_MODULUS">9</spirit:configurableElementValue>
- <spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_M_DATA_HAS_TUSER">0</spirit:configurableElementValue>
+ <spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_M_DATA_HAS_TUSER">2</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_M_DATA_TDATA_WIDTH">32</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_M_DATA_TUSER_WIDTH">1</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_M_PHASE_HAS_TUSER">0</spirit:configurableElementValue>
@@ -117,7 +117,7 @@
<spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_RESYNC">0</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_S_CONFIG_SYNC_MODE">0</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_S_CONFIG_TDATA_WIDTH">1</spirit:configurableElementValue>
- <spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_S_PHASE_HAS_TUSER">0</spirit:configurableElementValue>
+ <spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_S_PHASE_HAS_TUSER">2</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_S_PHASE_TDATA_WIDTH">24</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_S_PHASE_TUSER_WIDTH">1</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="MODELPARAM_VALUE.C_USE_DSP48">0</spirit:configurableElementValue>
@@ -136,7 +136,7 @@
<spirit:configurableElementValue spirit:referenceId="PARAM_VALUE.Has_TREADY">true</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="PARAM_VALUE.Latency">12</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="PARAM_VALUE.Latency_Configuration">Configurable</spirit:configurableElementValue>
- <spirit:configurableElementValue spirit:referenceId="PARAM_VALUE.M_DATA_Has_TUSER">Not_Required</spirit:configurableElementValue>
+ <spirit:configurableElementValue spirit:referenceId="PARAM_VALUE.M_DATA_Has_TUSER">User_Field</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="PARAM_VALUE.M_PHASE_Has_TUSER">Not_Required</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="PARAM_VALUE.Memory_Type">Block_ROM</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="PARAM_VALUE.Mode_of_Operation">Standard</spirit:configurableElementValue>
@@ -220,7 +220,7 @@
<spirit:configurableElementValue spirit:referenceId="PARAM_VALUE.Phase_offset">None</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="PARAM_VALUE.Resync">false</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="PARAM_VALUE.S_CONFIG_Sync_Mode">On_Vector</spirit:configurableElementValue>
- <spirit:configurableElementValue spirit:referenceId="PARAM_VALUE.S_PHASE_Has_TUSER">Not_Required</spirit:configurableElementValue>
+ <spirit:configurableElementValue spirit:referenceId="PARAM_VALUE.S_PHASE_Has_TUSER">User_Field</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="PARAM_VALUE.S_PHASE_TUSER_Width">1</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="PARAM_VALUE.Spurious_Free_Dynamic_Range">45</spirit:configurableElementValue>
<spirit:configurableElementValue spirit:referenceId="PARAM_VALUE.explicit_period">false</spirit:configurableElementValue>
@@ -256,6 +256,7 @@
<xilinx:configElementInfo xilinx:referenceId="BUSIFPARAM_VALUE.M_AXIS_DATA.TDATA_NUM_BYTES" xilinx:valueSource="auto"/>
<xilinx:configElementInfo xilinx:referenceId="BUSIFPARAM_VALUE.M_AXIS_DATA.TDEST_WIDTH" xilinx:valueSource="constant"/>
<xilinx:configElementInfo xilinx:referenceId="BUSIFPARAM_VALUE.M_AXIS_DATA.TID_WIDTH" xilinx:valueSource="constant"/>
+ <xilinx:configElementInfo xilinx:referenceId="BUSIFPARAM_VALUE.M_AXIS_DATA.TUSER_WIDTH" xilinx:valueSource="auto"/>
<xilinx:configElementInfo xilinx:referenceId="BUSIFPARAM_VALUE.M_AXIS_PHASE.HAS_TKEEP" xilinx:valueSource="constant"/>
<xilinx:configElementInfo xilinx:referenceId="BUSIFPARAM_VALUE.M_AXIS_PHASE.HAS_TREADY" xilinx:valueSource="auto"/>
<xilinx:configElementInfo xilinx:referenceId="BUSIFPARAM_VALUE.M_AXIS_PHASE.HAS_TSTRB" xilinx:valueSource="constant"/>
@@ -275,6 +276,7 @@
<xilinx:configElementInfo xilinx:referenceId="BUSIFPARAM_VALUE.S_AXIS_PHASE.TDATA_NUM_BYTES" xilinx:valueSource="auto"/>
<xilinx:configElementInfo xilinx:referenceId="BUSIFPARAM_VALUE.S_AXIS_PHASE.TDEST_WIDTH" xilinx:valueSource="constant"/>
<xilinx:configElementInfo xilinx:referenceId="BUSIFPARAM_VALUE.S_AXIS_PHASE.TID_WIDTH" xilinx:valueSource="constant"/>
+ <xilinx:configElementInfo xilinx:referenceId="BUSIFPARAM_VALUE.S_AXIS_PHASE.TUSER_WIDTH" xilinx:valueSource="auto"/>
<xilinx:configElementInfo xilinx:referenceId="PARAM_VALUE.Amplitude_Mode" xilinx:valueSource="user"/>
<xilinx:configElementInfo xilinx:referenceId="PARAM_VALUE.Channels" xilinx:valueSource="user"/>
<xilinx:configElementInfo xilinx:referenceId="PARAM_VALUE.DATA_Has_TLAST" xilinx:valueSource="user"/>
diff --git a/fpga/usrp3/lib/rfnoc/Makefile.srcs b/fpga/usrp3/lib/rfnoc/Makefile.srcs
index ab4a9e179..512569748 100644
--- a/fpga/usrp3/lib/rfnoc/Makefile.srcs
+++ b/fpga/usrp3/lib/rfnoc/Makefile.srcs
@@ -101,6 +101,8 @@ fir_filter_slice.v \
axi_fir_filter_dec.v \
addsub.vhd \
dds_freq_tune.v \
+dds_freq_tune_duc.v \
dds_timed.v \
+dds_wrapper.v \
datapath_gatekeeper.v \
))
diff --git a/fpga/usrp3/lib/rfnoc/dds_freq_tune_duc.v b/fpga/usrp3/lib/rfnoc/dds_freq_tune_duc.v
new file mode 100644
index 000000000..4b8a299b8
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/dds_freq_tune_duc.v
@@ -0,0 +1,228 @@
+//
+// Copyright 2021 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: dds_freq_tune_duc
+//
+// 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.
+//
+// The din input is expected to contain a complex 24-bit signed fixed-point
+// values with 15 fractional bits. The phase input is expected to contain
+// unsigned 24-bit fixed-point with 24 fractional bits, and therefore
+// represents the range [0,1), which corresponds to the range [0,2π) radians.
+// The output will then be a complex 24-bit signed fixed-point with 15
+// fractional bits.
+//
+// This version does the same thing as dds_freq_tune, but does not
+// reset/flush the DDS between packets or when an EOB occurs, and it includes
+// a FIFO on the din data path. This separate version was created to avoid
+// affecting the behavior of the DDC.
+//
+// ┌───┐
+// phase >──┤DDS├──┐ ┌───────┐
+// └───┘ └─┤Complex│ ┌─────┐
+// │ Mult ├──┤Round├───> dout
+// ┌────┐ ┌─┤ │ └─────┘
+// din >──┤FIFO├─┘ └───────┘
+// └────┘
+//
+// Parameters:
+//
+// Note: The parameters should NOT be changed, since they depend on the IP
+// configurations.
+//
+// INPUT_W : Width of each component of din.
+// PHASE_W : Width of the phase input.
+// OUTPUT_W : Width of each component of dout.
+//
+
+`default_nettype none
+
+
+module dds_freq_tune_duc #(
+ parameter INPUT_W = 24,
+ parameter PHASE_W = 24,
+ parameter OUTPUT_W = 24
+) (
+ input wire clk,
+ input wire reset,
+
+ // IQ input (Q in the upper, I in the lower bits)
+ input wire [INPUT_W*2-1:0] s_axis_din_tdata,
+ input wire s_axis_din_tlast,
+ input wire s_axis_din_tvalid,
+ output wire s_axis_din_tready,
+
+ // Phase input from NCO
+ input wire [PHASE_W-1:0] s_axis_phase_tdata,
+ input wire s_axis_phase_tlast,
+ input wire s_axis_phase_tvalid,
+ output wire s_axis_phase_tready,
+
+ // IQ output (Q in the upper, I in the lower bits)
+ output wire [OUTPUT_W*2-1:0] m_axis_dout_tdata,
+ output wire m_axis_dout_tlast,
+ output wire m_axis_dout_tvalid,
+ input wire m_axis_dout_tready
+);
+
+ //---------------------------------------------------------------------------
+ // Reset Generation
+ //---------------------------------------------------------------------------
+
+ reg reset_d1, reset_int;
+
+ // Create a local reset, named reset_int, which will always be asserted for
+ // at least 2 clock cycles, which is required by Xilinx DDS and complex
+ // multiplier IP.
+ always @(posedge clk) begin
+ reset_d1 <= reset;
+ reset_int <= reset | reset_d1;
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Data Input FIFO
+ //---------------------------------------------------------------------------
+ //
+ // We want the din and phase inputs paths to be balanced, so that a new
+ // data/phase pair can be input on each clock cycles. This FIFO allows the
+ // din data path to queue up samples while the DDS is processing.
+ //
+ //---------------------------------------------------------------------------
+
+ wire [INPUT_W*2-1:0] s_axis_fifo_tdata;
+ wire s_axis_fifo_tlast;
+ wire s_axis_fifo_tvalid;
+ wire s_axis_fifo_tready;
+
+ axi_fifo #(
+ .WIDTH (2*INPUT_W+1),
+ .SIZE (5)
+ ) axi_fifo_i (
+ .clk (clk),
+ .reset (reset),
+ .clear (1'b0),
+ .i_tdata ({ s_axis_din_tlast, s_axis_din_tdata }),
+ .i_tvalid (s_axis_din_tvalid),
+ .i_tready (s_axis_din_tready),
+ .o_tdata ({ s_axis_fifo_tlast, s_axis_fifo_tdata }),
+ .o_tvalid (s_axis_fifo_tvalid),
+ .o_tready (s_axis_fifo_tready),
+ .space (),
+ .occupied ()
+ );
+
+
+ //---------------------------------------------------------------------------
+ // DDS/NCO
+ //---------------------------------------------------------------------------
+
+ // Width of each component of the DDS output. This width is fixed by the IP
+ // configuration.
+ parameter DDS_W = 16;
+
+ wire m_axis_dds_tlast;
+ wire m_axis_dds_tvalid;
+ wire m_axis_dds_tready;
+ wire [DDS_W*2-1:0] m_axis_dds_tdata;
+
+ // DDS to convert the phase input to a unit-length complex number with that
+ // phase. It takes in an unsigned 24-bit phase with 24 fractional bits and
+ // outputs two signed 16-bit fixed point values with 14 fractional bits. The
+ // output has sin(2*pi*phase) in the upper 16 bits and cos(2*pi*phase) in the
+ // lower 16-bits.
+ dds_wrapper dds_wrapper_i (
+ .clk (clk),
+ .rst (reset_int),
+ .s_axis_phase_tdata (s_axis_phase_tdata),
+ .s_axis_phase_tvalid (s_axis_phase_tvalid),
+ .s_axis_phase_tlast (s_axis_phase_tlast),
+ .s_axis_phase_tready (s_axis_phase_tready),
+ .m_axis_data_tdata (m_axis_dds_tdata),
+ .m_axis_data_tvalid (m_axis_dds_tvalid),
+ .m_axis_data_tlast (m_axis_dds_tlast),
+ .m_axis_data_tready (m_axis_dds_tready)
+ );
+
+
+ //---------------------------------------------------------------------------
+ // Complex Multiplier
+ //---------------------------------------------------------------------------
+ //
+ // Use a complex multiplier to multiply the DDS complex sinusoid by the input
+ // data samples.
+ //
+ //---------------------------------------------------------------------------
+
+ // Width of each component on the output of the complex_multiplier_dds IP.
+ // This width is fixed by the IP configuration.
+ localparam MULT_OUT_W = 32;
+
+ // Width is set by the IP
+ wire [2*MULT_OUT_W-1:0] mult_out_tdata;
+ wire mult_out_tvalid;
+ wire mult_out_tready;
+ wire mult_out_tlast;
+
+ // The complex multiplier IP is configured so that the A input is 21 bits
+ // with 15 fractional bits, and the B input (dds) is 16 bits with 14
+ // fractional bits. Due to AXI-Stream requirements, A is rounded up to 24
+ // bits in width. The full multiplier output 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.
+ //
+ // The IP is configured to pass the TLAST from port A through, but we connect
+ // the B path anyway for completeness.
+ complex_multiplier_dds complex_multiplier_dds_i (
+ .aclk (clk),
+ .aresetn (~reset_int),
+ .s_axis_a_tvalid (s_axis_fifo_tvalid),
+ .s_axis_a_tready (s_axis_fifo_tready),
+ .s_axis_a_tlast (s_axis_fifo_tlast),
+ .s_axis_a_tdata (s_axis_fifo_tdata),
+ .s_axis_b_tvalid (m_axis_dds_tvalid),
+ .s_axis_b_tready (m_axis_dds_tready),
+ .s_axis_b_tlast (m_axis_dds_tlast),
+ .s_axis_b_tdata (m_axis_dds_tdata),
+ .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)
+ );
+
+
+ //---------------------------------------------------------------------------
+ // Round
+ //---------------------------------------------------------------------------
+ //
+ // 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 (MULT_OUT_W),
+ .WIDTH_OUT (OUTPUT_W)
+ ) axi_round_complex_i (
+ .clk (clk),
+ .reset (reset_int),
+ .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)
+ );
+
+endmodule
+
+
+`default_nettype wire
diff --git a/fpga/usrp3/lib/rfnoc/dds_timed.v b/fpga/usrp3/lib/rfnoc/dds_timed.v
index fd03f6a23..1af82683d 100644
--- a/fpga/usrp3/lib/rfnoc/dds_timed.v
+++ b/fpga/usrp3/lib/rfnoc/dds_timed.v
@@ -1,9 +1,75 @@
//
-// Copyright 2016 Ettus Research, a National Instruments Company
+// Copyright 2021 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
-// DDS that supports timed commands via the settings bus
+// Module: dds_timed
+//
+// Description:
+//
+// DDS (direct digital synthesis) and frequency shift block that supports
+// timed commands via the settings bus.
+//
+// This block takes in samples on i_t* and performs a complex multiplication
+// with a digitally synthesized oscillator to implement a digital RF mixer.
+// The output is then scaled (optionally), rounded, and clipped if necessary,
+// then output on o_t*.
+//
+// Timed commands allow you to update the SR_FREQ register (the phase
+// increment) at the desired time.
+//
+// The TUSER port contains the packet header information:
+//
+// tuser[125] : Has timestamp
+// tuser[124] : End of burst (EOB)
+// tuser[63:0] : Timestamp
+//
+// For the input, i_tuser should be valid for the duration of the packet. For
+// the output, o_tuser is only guaranteed to be valid for the last sample of
+// the packet.
+//
+// Registers:
+//
+// SR_FREQ : Frequency shift to apply to the input signal. This can be
+// thought of as an unsigned PHASE_ACCUM_WIDTH-bit register
+// with PHASE_ACCUM_WIDTH fractional bits. That is, the range
+// of this register maps to the real values [0,1). This
+// register controls the amount by which the phase accumulator
+// for the DDS is incremented each clock cycle. It can
+// therefore be thought of as a phase angle corresponding to
+// the range [0,2π) radians.
+// SR_SCALE_IQ : Scaler by which to multiply the IQ outputs. This is a
+// SCALING_WIDTH-bit signed fixed-point register with 15
+// fractional bits. If SCALING_WIDTH is 18, then it has the
+// range [-4,4).
+//
+// Parameters:
+//
+// Note: Care must be used when overriding these parameters because there are
+// many dependencies on them. For example, the DDS_WIDTH and
+// PHASE_WIDTH depend on the configuration of the underlying DDS IP and
+// should only be modified to match that IP.
+//
+// SR_FREQ_ADDR : Register offset to assign to the SR_FREQ register,
+// which contains the phase increment per sample needed
+// to achieve the desired DDS frequency.
+// SR_SCALE_IQ_ADDR : Register offset to assign to the SR_SCALE_IQ register.
+// CMD_FIFO_SIZE : Log2 of the size of the timed command FIFO to use.
+// WIDTH : Data width of the I/Q components of the input/output
+// samples, typically 16.
+// DDS_WIDTH : Bit width to use for the DDS and complex multiplier.
+// PHASE_WIDTH : Bit width to use for the phase provided to the DDS IP.
+// PHASE_ACCUM_WIDTH : Bit width to use for the phase increment values.
+// SCALING_WIDTH : Bit width to use for the IQ scale registers.
+// HEADER_WIDTH : Width of the header info (tuser).
+// HEADER_FIFO_SIZE : Log2 of the size of the header FIFO.
+// SR_AWIDTH : Settings bus address width.
+// SR_DWIDTH : Settings bus data width.
+// SR_TWIDTH : Settings bus time width.
+//
+
+`default_nettype none
+
module dds_timed #(
parameter SR_FREQ_ADDR = 0,
@@ -19,142 +85,280 @@ module dds_timed #(
parameter SR_AWIDTH = 8,
parameter SR_DWIDTH = 32,
parameter SR_TWIDTH = 64
-)(
- input clk, input reset, input clear,
- output timed_cmd_fifo_full,
- input set_stb, input [SR_AWIDTH-1:0] set_addr, input [SR_DWIDTH-1:0] set_data,
- input [SR_TWIDTH-1:0] set_time, input set_has_time,
- input [2*WIDTH-1:0] i_tdata, input i_tlast, input i_tvalid, output i_tready, input [HEADER_WIDTH-1:0] i_tuser,
- output [2*WIDTH-1:0] o_tdata, output o_tlast, output o_tvalid, input o_tready, output [HEADER_WIDTH-1:0] o_tuser
+) (
+ input wire clk,
+ input wire reset,
+ input wire clear,
+
+ // Indicates if the timed command FIFO is full
+ output wire timed_cmd_fifo_full,
+
+ // Settings bus for register access
+ input wire set_stb,
+ input wire [SR_AWIDTH-1:0] set_addr,
+ input wire [SR_DWIDTH-1:0] set_data,
+ input wire [SR_TWIDTH-1:0] set_time,
+ input wire set_has_time,
+
+ // Input sample stream
+ input wire [ 2*WIDTH-1:0] i_tdata,
+ input wire i_tlast,
+ input wire i_tvalid,
+ output wire i_tready,
+ input wire [HEADER_WIDTH-1:0] i_tuser,
+
+ // Output sample stream
+ output wire [ 2*WIDTH-1:0] o_tdata,
+ output wire o_tlast,
+ output wire o_tvalid,
+ input wire o_tready,
+ output wire [HEADER_WIDTH-1:0] o_tuser
);
- /**************************************************************************
- * Track VITA time
- *************************************************************************/
- wire [2*WIDTH-1:0] int_tdata;
+ //---------------------------------------------------------------------------
+ // Time Tracking
+ //---------------------------------------------------------------------------
+
+ wire [ 2*WIDTH-1:0] int_tdata;
wire [HEADER_WIDTH-1:0] int_tuser;
- wire int_tlast, int_tvalid, int_tready, int_tag;
- wire [SR_AWIDTH-1:0] out_set_addr, timed_set_addr;
- wire [SR_DWIDTH-1:0] out_set_data, timed_set_data;
- wire out_set_stb, timed_set_stb;
- wire eob;
+ wire int_tlast;
+ wire int_tvalid;
+ wire int_tready;
+ wire int_tag;
+ wire [ SR_AWIDTH-1:0] out_set_addr;
+ wire [ SR_AWIDTH-1:0] timed_set_addr;
+ wire [ SR_DWIDTH-1:0] out_set_data;
+ wire [ SR_DWIDTH-1:0] timed_set_data;
+ wire out_set_stb;
+ wire timed_set_stb;
+ // This module checks for timed writes to SR_FREQ_ADDR and outputs the
+ // register write on timed_set_* (if it was timed) or set_* (if it was not
+ // timed). It then tags the sample for which the timed command to
+ // SR_FREQ_ADDR should occur by asserting m_axis_data_tag when that sample is
+ // output.
axi_tag_time #(
- .WIDTH(2*WIDTH),
- .NUM_TAGS(1),
- .SR_TAG_ADDRS(SR_FREQ_ADDR))
- axi_tag_time (
- .clk(clk),
- .reset(reset),
- .clear(clear),
- .tick_rate(16'd1),
- .timed_cmd_fifo_full(timed_cmd_fifo_full),
- .s_axis_data_tdata(i_tdata), .s_axis_data_tlast(i_tlast),
- .s_axis_data_tvalid(i_tvalid), .s_axis_data_tready(i_tready),
- .s_axis_data_tuser(i_tuser),
- .m_axis_data_tdata(int_tdata), .m_axis_data_tlast(int_tlast),
- .m_axis_data_tvalid(int_tvalid), .m_axis_data_tready(int_tready),
- .m_axis_data_tuser(int_tuser), .m_axis_data_tag(int_tag),
- .in_set_stb(set_stb), .in_set_addr(set_addr), .in_set_data(set_data),
- .in_set_time(set_time), .in_set_has_time(set_has_time),
- .out_set_stb(out_set_stb), .out_set_addr(out_set_addr), .out_set_data(out_set_data),
- .timed_set_stb(timed_set_stb), .timed_set_addr(timed_set_addr), .timed_set_data(timed_set_data));
-
- wire [2*WIDTH-1:0] dds_in_tdata, unused_tdata;
- wire [HEADER_WIDTH-1:0] header_in_tdata, header_out_tdata, unused_tuser;
- wire dds_in_tlast, dds_in_tvalid, dds_in_tready, dds_in_tag;
- wire header_in_tvalid, header_in_tready, header_in_tlast, unused_tag;
- wire header_out_tvalid, header_out_tready;
+ .WIDTH (2*WIDTH),
+ .NUM_TAGS (1),
+ .SR_TAG_ADDRS (SR_FREQ_ADDR)
+ ) axi_tag_time (
+ .clk (clk),
+ .reset (reset),
+ .clear (clear),
+ .tick_rate (16'd1),
+ .timed_cmd_fifo_full (timed_cmd_fifo_full),
+ .s_axis_data_tdata (i_tdata),
+ .s_axis_data_tlast (i_tlast),
+ .s_axis_data_tvalid (i_tvalid),
+ .s_axis_data_tready (i_tready),
+ .s_axis_data_tuser (i_tuser),
+ .m_axis_data_tdata (int_tdata),
+ .m_axis_data_tlast (int_tlast),
+ .m_axis_data_tvalid (int_tvalid),
+ .m_axis_data_tready (int_tready),
+ .m_axis_data_tuser (int_tuser),
+ .m_axis_data_tag (int_tag),
+ .in_set_stb (set_stb),
+ .in_set_addr (set_addr),
+ .in_set_data (set_data),
+ .in_set_time (set_time),
+ .in_set_has_time (set_has_time),
+ .out_set_stb (out_set_stb),
+ .out_set_addr (out_set_addr),
+ .out_set_data (out_set_data),
+ .timed_set_stb (timed_set_stb),
+ .timed_set_addr (timed_set_addr),
+ .timed_set_data (timed_set_data)
+ );
+
+ wire [ 2*WIDTH-1:0] dds_in_tdata;
+ wire [ 2*WIDTH-1:0] unused_tdata;
+ wire [HEADER_WIDTH-1:0] header_in_tdata;
+ wire [HEADER_WIDTH-1:0] header_out_tdata;
+ wire [HEADER_WIDTH-1:0] dds_in_tuser;
+ wire dds_in_tlast;
+ wire dds_in_tvalid;
+ wire dds_in_tready;
+ wire dds_in_tag;
+ wire header_in_tvalid;
+ wire header_in_tready;
+ wire header_in_tlast;
+ wire unused_tag;
+ wire header_out_tvalid;
+ wire header_out_tready;
+
+
+ //---------------------------------------------------------------------------
+ // Split Stream
+ //---------------------------------------------------------------------------
+ //
+ // Split the data stream into two streams, one with the data/tag (dds_in_t*)
+ // and the other with the header (header_in_t*).
+ //
+ //---------------------------------------------------------------------------
split_stream #(
- .WIDTH(2*WIDTH+HEADER_WIDTH+1), .ACTIVE_MASK(4'b0011))
- split_head (
- .clk(clk), .reset(reset), .clear(clear),
- .i_tdata({int_tdata,int_tuser,int_tag}), .i_tlast(int_tlast),
- .i_tvalid(int_tvalid), .i_tready(int_tready),
- .o0_tdata({dds_in_tdata,unused_tuser,dds_in_tag}), .o0_tlast(dds_in_tlast),
- .o0_tvalid(dds_in_tvalid), .o0_tready(dds_in_tready),
- .o1_tdata({unused_tdata,header_in_tdata,unused_tag}), .o1_tlast(header_in_tlast),
- .o1_tvalid(header_in_tvalid), .o1_tready(header_in_tready),
- .o2_tready(1'b0), .o3_tready(1'b0));
+ .WIDTH (2*WIDTH+HEADER_WIDTH+1),
+ .ACTIVE_MASK (4'b0011)
+ ) split_head (
+ .clk (clk),
+ .reset (reset),
+ .clear (clear),
+ .i_tdata ({ int_tdata, int_tuser, int_tag }),
+ .i_tlast (int_tlast),
+ .i_tvalid (int_tvalid),
+ .i_tready (int_tready),
+ .o0_tdata ({ dds_in_tdata, dds_in_tuser, dds_in_tag }),
+ .o0_tlast (dds_in_tlast),
+ .o0_tvalid (dds_in_tvalid),
+ .o0_tready (dds_in_tready),
+ .o1_tdata ({ unused_tdata, header_in_tdata, unused_tag }),
+ .o1_tlast (header_in_tlast),
+ .o1_tvalid (header_in_tvalid),
+ .o1_tready (header_in_tready),
+ .o2_tdata (),
+ .o2_tlast (),
+ .o2_tvalid (),
+ .o2_tready (1'b0),
+ .o3_tdata (),
+ .o3_tlast (),
+ .o3_tvalid (),
+ .o3_tready (1'b0)
+ );
+
+
+ //---------------------------------------------------------------------------
+ // Header FIFO
+ //---------------------------------------------------------------------------
+ //
+ // Store each packet header in a FIFO to be read out when the packet is
+ // output.
+ //
+ //---------------------------------------------------------------------------
axi_fifo #(
- .WIDTH(HEADER_WIDTH), .SIZE(HEADER_FIFO_SIZE))
- axi_fifo_header (
- .clk(clk), .reset(reset), .clear(clear),
- .i_tdata(header_in_tdata), .i_tvalid(header_in_tvalid & header_in_tlast), .i_tready(header_in_tready),
- .o_tdata(header_out_tdata), .o_tvalid(header_out_tvalid),
- .o_tready(header_out_tready), // Consume header on last output sample
- .space(), .occupied());
-
- assign eob = header_in_tdata[124];
-
- /**************************************************************************
- * Settings Regs
- *************************************************************************/
- wire [PHASE_ACCUM_WIDTH-1:0] phase_inc_tdata, phase_inc_timed_tdata;
- wire phase_inc_tlast, phase_inc_tvalid, phase_inc_tready;
- wire phase_inc_timed_tlast, phase_inc_timed_tready , phase_inc_timed_tvalid;
+ .WIDTH (HEADER_WIDTH),
+ .SIZE (HEADER_FIFO_SIZE)
+ ) axi_fifo_header (
+ .clk (clk),
+ .reset (reset),
+ .clear (clear),
+ .i_tdata (header_in_tdata),
+ .i_tvalid (header_in_tvalid & header_in_tlast),
+ .i_tready (header_in_tready),
+ .o_tdata (header_out_tdata),
+ .o_tvalid (header_out_tvalid),
+ .o_tready (header_out_tready), // Consume header on last output sample
+ .space (),
+ .occupied ()
+ );
+
+
+ //---------------------------------------------------------------------------
+ // Settings Bus Registers
+ //---------------------------------------------------------------------------
+ wire [PHASE_ACCUM_WIDTH-1:0] phase_inc_tdata;
+ wire [PHASE_ACCUM_WIDTH-1:0] phase_inc_timed_tdata;
+ wire phase_inc_tlast;
+ wire phase_inc_tvalid;
+ wire phase_inc_tready;
+ wire phase_inc_timed_tlast;
+ wire phase_inc_timed_tready;
+ wire phase_inc_timed_tvalid;
+
+ // Frequency register (phase increment) used for *un-timed* commands
axi_setting_reg #(
- .ADDR(SR_FREQ_ADDR), .AWIDTH(SR_AWIDTH), .WIDTH(PHASE_ACCUM_WIDTH), .STROBE_LAST(1))
- set_freq (
- .clk(clk), .reset(reset),
- .set_stb(out_set_stb), .set_addr(out_set_addr), .set_data(out_set_data),
- .o_tdata(phase_inc_tdata), .o_tlast(phase_inc_tlast), .o_tvalid(phase_inc_tvalid), .o_tready(phase_inc_tready));
+ .ADDR (SR_FREQ_ADDR),
+ .AWIDTH (SR_AWIDTH),
+ .WIDTH (PHASE_ACCUM_WIDTH),
+ .STROBE_LAST (1)
+ ) set_freq (
+ .clk (clk),
+ .reset (reset),
+ .set_stb (out_set_stb),
+ .set_addr (out_set_addr),
+ .set_data (out_set_data),
+ .o_tdata (phase_inc_tdata),
+ .o_tlast (phase_inc_tlast),
+ .o_tvalid (phase_inc_tvalid),
+ .o_tready (phase_inc_tready)
+ );
+ // Frequency register (phase increment) used for *timed* commands
axi_setting_reg #(
- .ADDR(SR_FREQ_ADDR), .USE_FIFO(1), .FIFO_SIZE(CMD_FIFO_SIZE), .AWIDTH(SR_AWIDTH), .WIDTH(PHASE_ACCUM_WIDTH), .STROBE_LAST(1))
- set_freq_timed (
- .clk(clk), .reset(reset),
- .set_stb(timed_set_stb), .set_addr(timed_set_addr), .set_data(timed_set_data),
- .o_tdata(phase_inc_timed_tdata), .o_tlast(phase_inc_timed_tlast), .o_tvalid(phase_inc_timed_tvalid), .o_tready(phase_inc_timed_tready));
+ .ADDR (SR_FREQ_ADDR),
+ .USE_FIFO (1),
+ .FIFO_SIZE (CMD_FIFO_SIZE),
+ .AWIDTH (SR_AWIDTH),
+ .WIDTH (PHASE_ACCUM_WIDTH),
+ .STROBE_LAST (1)
+ ) set_freq_timed (
+ .clk (clk),
+ .reset (reset),
+ .set_stb (timed_set_stb),
+ .set_addr (timed_set_addr),
+ .set_data (timed_set_data),
+ .o_tdata (phase_inc_timed_tdata),
+ .o_tlast (phase_inc_timed_tlast),
+ .o_tvalid (phase_inc_timed_tvalid),
+ .o_tready (phase_inc_timed_tready)
+ );
wire [SCALING_WIDTH-1:0] scaling_tdata;
- wire scaling_tvalid, scaling_tready;
+ wire scaling_tready;
+ // Scale value register
axi_setting_reg #(
- .ADDR(SR_SCALE_IQ_ADDR), .AWIDTH(SR_AWIDTH), .WIDTH(SCALING_WIDTH), .REPEATS(1))
- set_scale (
- .clk(clk), .reset(reset),
- .set_stb(out_set_stb), .set_addr(out_set_addr), .set_data(out_set_data),
- .o_tdata(scaling_tdata), .o_tlast(), .o_tvalid(scaling_tvalid), .o_tready(scaling_tready));
-
- /**************************************************************************
- * DDS + Complex Mult + Phase Accumulator
- *************************************************************************/
+ .ADDR (SR_SCALE_IQ_ADDR),
+ .AWIDTH (SR_AWIDTH),
+ .WIDTH (SCALING_WIDTH),
+ .REPEATS (1)
+ ) set_scale (
+ .clk (clk),
+ .reset (reset),
+ .set_stb (out_set_stb),
+ .set_addr (out_set_addr),
+ .set_data (out_set_data),
+ .o_tdata (scaling_tdata),
+ .o_tlast (),
+ .o_tvalid (),
+ .o_tready (scaling_tready)
+ );
+
+
+ //---------------------------------------------------------------------------
+ // Phase Accumulator for DDS
+ //---------------------------------------------------------------------------
+
wire [PHASE_ACCUM_WIDTH-1:0] phase_inc_mux_tdata;
- reg [PHASE_ACCUM_WIDTH-1:0] phase_inc;
- wire phase_inc_mux_tlast, phase_inc_mux_tvalid, phase_inc_mux_tready;
- reg [PHASE_ACCUM_WIDTH-1:0] phase;
-
- wire [PHASE_WIDTH-1:0] phase_tdata = phase[PHASE_ACCUM_WIDTH-1:PHASE_ACCUM_WIDTH-PHASE_WIDTH];
- wire phase_tvalid, phase_tready, phase_tlast;
-
- wire [WIDTH*2-1:0] dds_in_fifo_tdata;
- wire dds_in_fifo_tvalid, dds_in_fifo_tready, dds_in_fifo_tlast;
- wire dds_out_tlast, dds_out_tvalid, dds_out_tready;
-
- wire [DDS_WIDTH-1:0] dds_in_i_tdata, dds_in_q_tdata;
- wire [DDS_WIDTH-1:0] dds_out_i_tdata, dds_out_q_tdata;
- wire [15:0] dds_input_fifo_space, dds_input_fifo_occupied;
-
- wire [WIDTH*2-1:0] dds_in_sync_tdata;
- wire dds_in_sync_tvalid, dds_in_sync_tready, dds_in_sync_tlast;
- wire [PHASE_WIDTH-1:0] phase_sync_tdata;
- wire phase_sync_tvalid, phase_sync_tready, phase_sync_tlast;
-
- assign phase_inc_mux_tdata = phase_inc_timed_tready ? phase_inc_timed_tdata : phase_inc_tdata;
- assign phase_inc_mux_tlast = phase_inc_timed_tready ? phase_inc_timed_tlast : phase_inc_tlast;
+ reg [PHASE_ACCUM_WIDTH-1:0] phase_inc;
+ wire phase_inc_mux_tlast;
+ wire phase_inc_mux_tvalid;
+ wire phase_inc_mux_tready;
+ reg [PHASE_ACCUM_WIDTH-1:0] phase;
+
+ wire [PHASE_WIDTH-1:0] phase_tdata = phase[PHASE_ACCUM_WIDTH-1:PHASE_ACCUM_WIDTH-PHASE_WIDTH];
+ wire phase_tvalid;
+ wire phase_tready;
+ wire phase_tlast;
+
+ wire dds_in_teob = dds_in_tuser[124];
+
+ // Multiplexer to select between the timed and un-timed phase registers.
+ assign phase_inc_mux_tdata = phase_inc_timed_tready ? phase_inc_timed_tdata : phase_inc_tdata;
+ assign phase_inc_mux_tlast = phase_inc_timed_tready ? phase_inc_timed_tlast : phase_inc_tlast;
assign phase_inc_mux_tvalid = phase_inc_timed_tready ? phase_inc_timed_tvalid : phase_inc_tvalid;
assign phase_inc_tready = phase_inc_mux_tready;
assign phase_inc_timed_tready = phase_inc_mux_tready & dds_in_tag;
- assign phase_inc_mux_tready = phase_tready;
+ assign phase_inc_mux_tready = phase_tready;
- // phase is only valid when input i/q data stream is valid
+ // Phase is only valid when input IQ data stream is valid
assign phase_tvalid = dds_in_tvalid;
- assign phase_tlast = dds_in_tlast;
+ assign phase_tlast = dds_in_tlast;
+ // Phase increment register, sourced from either the timed or un-timed
+ // SR_FREQ register.
always @(posedge clk) begin
if (reset | clear) begin
phase_inc <= 0;
@@ -162,129 +366,219 @@ module dds_timed #(
phase_inc <= phase_inc_mux_tdata;
end
end
-
- // NCO, increment phase input to DDS SIN/COS LUT
+
+ // Phase accumulator for DDS. This increments the "phase" input provided to
+ // the DDS IP.
always @(posedge clk) begin
- if (reset | clear | (phase_inc_mux_tvalid & phase_inc_mux_tready) | eob) begin
+ if (reset | clear | (phase_inc_mux_tvalid & phase_inc_mux_tready)) begin
+ // Reset the phase on reset or clear, but also whenever the phase
+ // increment is updated.
phase <= 0;
- end else if (dds_in_tvalid & dds_in_tready) begin //only increment phase when data into dds is valid and data fifo is ready
- phase <= phase + phase_inc;
+ end else if (dds_in_tvalid & dds_in_tready) begin
+ if (dds_in_tlast & dds_in_teob) begin
+ // Reset the phase at the end of each burst so we get predictable
+ // output.
+ phase <= 0;
+ end else begin
+ // Increment the phase for each new sample.
+ phase <= phase + phase_inc;
+ end
end
end
+ //---------------------------------------------------------------------------
+ // AXI Sync
+ //---------------------------------------------------------------------------
+ //
+ // Sync the IQ and phase paths' pipeline delay. This is needed to ensure that
+ // applying the phase update happens on the correct sample regardless of
+ // differences in path delays.
+ //
+ //---------------------------------------------------------------------------
+
+
+ wire [PHASE_WIDTH-1:0] phase_sync_tdata;
+ wire phase_sync_tvalid;
+ wire phase_sync_tready;
+ wire phase_sync_tlast;
+
+ wire [ WIDTH*2-1:0] dds_in_sync_tdata;
+ wire dds_in_sync_tvalid;
+ wire dds_in_sync_tready;
+ wire dds_in_sync_tlast;
- // Sync the two path's pipeline delay.
- // This is needed to ensure that applying the phase update happens on the
- // correct sample regardless of differing downstream path delays.
axi_sync #(
- .SIZE(2),
- .WIDTH_VEC({PHASE_WIDTH,2*WIDTH}), // Vector of widths, each width is defined by a 32-bit value
- .FIFO_SIZE(0))
- axi_sync (
- .clk(clk), .reset(reset), .clear(clear),
- .i_tdata({phase_tdata,dds_in_tdata}),
- .i_tlast({phase_tlast,dds_in_tlast}),
- .i_tvalid({phase_tvalid,dds_in_tvalid}),
- .i_tready({phase_tready,dds_in_tready}),
- .o_tdata({phase_sync_tdata,dds_in_sync_tdata}),
- .o_tlast({phase_sync_tlast,dds_in_sync_tlast}),
- .o_tvalid({phase_sync_tvalid,dds_in_sync_tvalid}),
- .o_tready({phase_sync_tready,dds_in_sync_tready}));
-
- // fifo to hold input data while pipeline catches up in dds
- // this is blocked by the axi_sync following the dds
- axi_fifo #(.WIDTH(2*WIDTH+1), .SIZE(5)) dds_input_fifo(
- .clk(clk), .reset(reset), .clear(clear),
- .i_tdata({dds_in_sync_tlast,dds_in_sync_tdata}), .i_tvalid(dds_in_sync_tvalid), .i_tready(dds_in_sync_tready),
- .o_tdata({dds_in_fifo_tlast,dds_in_fifo_tdata}), .o_tvalid(dds_in_fifo_tvalid), .o_tready(dds_in_fifo_tready),
- .space(dds_input_fifo_space), .occupied(dds_input_fifo_occupied)
+ .SIZE (2),
+ .WIDTH_VEC ({ PHASE_WIDTH, 2*WIDTH }), // Vector of 32-bit width values
+ .FIFO_SIZE (0)
+ ) axi_sync_i (
+ .clk (clk),
+ .reset (reset),
+ .clear (clear),
+ .i_tdata ({ phase_tdata, dds_in_tdata }),
+ .i_tlast ({ phase_tlast, dds_in_tlast }),
+ .i_tvalid ({ phase_tvalid, dds_in_tvalid }),
+ .i_tready ({ phase_tready, dds_in_tready }),
+ .o_tdata ({ phase_sync_tdata, dds_in_sync_tdata }),
+ .o_tlast ({ phase_sync_tlast, dds_in_sync_tlast }),
+ .o_tvalid ({ phase_sync_tvalid, dds_in_sync_tvalid }),
+ .o_tready ({ phase_sync_tready, dds_in_sync_tready })
);
-
- // after fifo, do q quick sign extend op to get up to 24 bits. to match how the cordic deals with the data path.
- sign_extend #(
- .bits_in(WIDTH), .bits_out(DDS_WIDTH))
- sign_extend_dds_i (
- .in(dds_in_fifo_tdata[2*WIDTH-1:WIDTH]), .out(dds_in_i_tdata));
+
+ //---------------------------------------------------------------------------
+ // DDS and Complex Multiplier
+ //---------------------------------------------------------------------------
+
+ wire [DDS_WIDTH-1:0] dds_in_i_tdata;
+ wire [DDS_WIDTH-1:0] dds_in_q_tdata;
+
+ wire [DDS_WIDTH-1:0] dds_out_i_tdata;
+ wire [DDS_WIDTH-1:0] dds_out_q_tdata;
+ wire dds_out_tlast;
+ wire dds_out_tvalid;
+ wire dds_out_tready;
+
+ // Sign extend I and Q to get up to 24 bits.
sign_extend #(
- .bits_in(WIDTH), .bits_out(DDS_WIDTH))
- sign_extend_dds_q (
- .in(dds_in_fifo_tdata[WIDTH-1:0]), .out(dds_in_q_tdata));
-
-
- // Wrapper for Xilinx IP AXI DDS + Complex Multiply
- // NOTE: Seems Xilinx IP expects opposite I/Q combined complex data buses, so they are swapped here.
- dds_freq_tune dds_freq_tune_inst (
- .clk(clk),
- .reset(reset | clear),
- .eob(eob),
- .rate_changed(1'b0),
- .dds_input_fifo_occupied(dds_input_fifo_occupied),
- /* IQ input */
- .s_axis_din_tlast(dds_in_fifo_tlast),
- .s_axis_din_tvalid(dds_in_fifo_tvalid),
- .s_axis_din_tready(dds_in_fifo_tready),
- .s_axis_din_tdata({dds_in_q_tdata, dds_in_i_tdata}),
- /* Phase input from NCO */
- .s_axis_phase_tlast(phase_sync_tlast),
+ .bits_in (WIDTH),
+ .bits_out (DDS_WIDTH)
+ ) sign_extend_i (
+ .in (dds_in_sync_tdata[2*WIDTH-1:WIDTH]),
+ .out (dds_in_i_tdata)
+ );
+ sign_extend #(
+ .bits_in (WIDTH),
+ .bits_out (DDS_WIDTH)
+ ) sign_extend_q (
+ .in (dds_in_sync_tdata[WIDTH-1:0]),
+ .out (dds_in_q_tdata)
+ );
+
+ // Wrapper for DDS + Complex Multiply. This block expects {q,i} instead of
+ // {i,q} data ordering.
+ dds_freq_tune_duc dds_freq_tune_duc_i (
+ .clk (clk),
+ .reset (reset | clear),
+ // IQ input (signed 24-bit number with 15 fractional bits)
+ .s_axis_din_tlast (dds_in_sync_tlast),
+ .s_axis_din_tvalid (dds_in_sync_tvalid),
+ .s_axis_din_tready (dds_in_sync_tready),
+ .s_axis_din_tdata ({ dds_in_q_tdata, dds_in_i_tdata }),
+ // Phase input from DDS (unsigned 24-bit number with 24 fractional bits)
+ .s_axis_phase_tlast (phase_sync_tlast),
.s_axis_phase_tvalid(phase_sync_tvalid),
.s_axis_phase_tready(phase_sync_tready),
- .s_axis_phase_tdata(phase_sync_tdata), //24 bit
- /* IQ output */
- .m_axis_dout_tlast(dds_out_tlast),
- .m_axis_dout_tvalid(dds_out_tvalid),
- .m_axis_dout_tready(dds_out_tready),
- .m_axis_dout_tdata({dds_out_q_tdata, dds_out_i_tdata})
- //debug signals
+ .s_axis_phase_tdata (phase_sync_tdata), // 24-bit
+ // IQ output (signed 24-bit number with 15 fractional bits)
+ .m_axis_dout_tlast (dds_out_tlast),
+ .m_axis_dout_tvalid (dds_out_tvalid),
+ .m_axis_dout_tready (dds_out_tready),
+ .m_axis_dout_tdata ({dds_out_q_tdata, dds_out_i_tdata})
);
- /************************************************************************
- * Perform scaling on the IQ output
- ************************************************************************/
- wire [DDS_WIDTH+SCALING_WIDTH-1:0] scaled_i_tdata, scaled_q_tdata;
- wire scaled_tlast, scaled_tvalid, scaled_tready;
+
+
+ //---------------------------------------------------------------------------
+ // Scale the IQ Output
+ //---------------------------------------------------------------------------
+
+ wire [DDS_WIDTH+SCALING_WIDTH-1:0] scaled_i_tdata;
+ wire [DDS_WIDTH+SCALING_WIDTH-1:0] scaled_q_tdata;
+ wire scaled_tlast;
+ wire scaled_tvalid;
+ wire scaled_tready;
mult #(
- .WIDTH_A(DDS_WIDTH),
- .WIDTH_B(SCALING_WIDTH),
- .WIDTH_P(DDS_WIDTH+SCALING_WIDTH),
- .DROP_TOP_P(4),
- .LATENCY(3),
- .CASCADE_OUT(0))
- i_mult (
- .clk(clk), .reset(reset | clear),
- .a_tdata(dds_out_i_tdata), .a_tlast(dds_out_tlast), .a_tvalid(dds_out_tvalid), .a_tready(dds_out_tready),
- .b_tdata(scaling_tdata), .b_tlast(1'b0), .b_tvalid(dds_out_tvalid /* aligning scaling_tdata with dds_tdata */), .b_tready(scaling_tready),
- .p_tdata(scaled_i_tdata), .p_tlast(scaled_tlast), .p_tvalid(scaled_tvalid), .p_tready(scaled_tready));
+ .WIDTH_A (DDS_WIDTH),
+ .WIDTH_B (SCALING_WIDTH),
+ .WIDTH_P (DDS_WIDTH+SCALING_WIDTH),
+ .DROP_TOP_P (4),
+ .LATENCY (3),
+ .CASCADE_OUT (0)
+ ) mult_i (
+ .clk (clk),
+ .reset (reset | clear),
+ .a_tdata (dds_out_i_tdata),
+ .a_tlast (dds_out_tlast),
+ .a_tvalid (dds_out_tvalid),
+ .a_tready (dds_out_tready),
+ .b_tdata (scaling_tdata),
+ .b_tlast (1'b0),
+ .b_tvalid (dds_out_tvalid), // Align scaling_tdata with dds_out_tdata
+ .b_tready (scaling_tready),
+ .p_tdata (scaled_i_tdata),
+ .p_tlast (scaled_tlast),
+ .p_tvalid (scaled_tvalid),
+ .p_tready (scaled_tready)
+ );
mult #(
- .WIDTH_A(DDS_WIDTH),
- .WIDTH_B(SCALING_WIDTH),
- .WIDTH_P(DDS_WIDTH+SCALING_WIDTH),
- .DROP_TOP_P(4),
- .LATENCY(3),
- .CASCADE_OUT(0))
- q_mult (
- .clk(clk), .reset(reset | clear),
- .a_tdata(dds_out_q_tdata), .a_tlast(), .a_tvalid(dds_out_tvalid), .a_tready(),
- .b_tdata(scaling_tdata), .b_tlast(1'b0), .b_tvalid(dds_out_tvalid /* aligning scaling_tdata with dds_tdata */), .b_tready(),
- .p_tdata(scaled_q_tdata), .p_tlast(), .p_tvalid(), .p_tready(scaled_tready));
+ .WIDTH_A (DDS_WIDTH),
+ .WIDTH_B (SCALING_WIDTH),
+ .WIDTH_P (DDS_WIDTH+SCALING_WIDTH),
+ .DROP_TOP_P (4),
+ .LATENCY (3),
+ .CASCADE_OUT (0)
+ ) mult_q (
+ .clk (clk),
+ .reset (reset | clear),
+ .a_tdata (dds_out_q_tdata),
+ .a_tlast (),
+ .a_tvalid (dds_out_tvalid),
+ .a_tready (),
+ .b_tdata (scaling_tdata),
+ .b_tlast (1'b0),
+ .b_tvalid (dds_out_tvalid), // Align scaling_tdata with dds_out_tdata
+ .b_tready (),
+ .p_tdata (scaled_q_tdata),
+ .p_tlast (),
+ .p_tvalid (),
+ .p_tready (scaled_tready)
+ );
+
+
+ //---------------------------------------------------------------------------
+ // Round
+ //---------------------------------------------------------------------------
wire [2*WIDTH-1:0] sample_tdata;
- wire sample_tlast, sample_tvalid, sample_tready;
+ wire sample_tlast;
+ wire sample_tvalid;
+ wire sample_tready;
axi_round_and_clip_complex #(
- .WIDTH_IN(DDS_WIDTH+SCALING_WIDTH), .WIDTH_OUT(WIDTH), .CLIP_BITS(12))
- axi_round_and_clip_complex (
- .clk(clk), .reset(reset | clear),
- .i_tdata({scaled_i_tdata, scaled_q_tdata}), .i_tlast(scaled_tlast), .i_tvalid(scaled_tvalid), .i_tready(scaled_tready),
- .o_tdata(sample_tdata), .o_tlast(sample_tlast), .o_tvalid(sample_tvalid), .o_tready(sample_tready));
+ .WIDTH_IN (DDS_WIDTH+SCALING_WIDTH),
+ .WIDTH_OUT (WIDTH),
+ .CLIP_BITS (12)
+ ) axi_round_and_clip_complex_i (
+ .clk (clk),
+ .reset (reset | clear),
+ .i_tdata ({scaled_i_tdata, scaled_q_tdata}),
+ .i_tlast (scaled_tlast),
+ .i_tvalid (scaled_tvalid),
+ .i_tready (scaled_tready),
+ .o_tdata (sample_tdata),
+ .o_tlast (sample_tlast),
+ .o_tvalid (sample_tvalid),
+ .o_tready (sample_tready)
+ );
+
+
+ //---------------------------------------------------------------------------
+ // Output Logic
+ //---------------------------------------------------------------------------
// Throttle output on last sample if header is not valid
assign header_out_tready = sample_tlast & sample_tvalid & o_tready;
- assign sample_tready = (sample_tvalid & sample_tlast) ? (header_out_tvalid & o_tready) : o_tready;
- assign o_tvalid = (sample_tvalid & sample_tlast) ? header_out_tvalid : sample_tvalid;
+ assign sample_tready = (sample_tvalid & sample_tlast) ?
+ (header_out_tvalid & o_tready) : o_tready;
+ assign o_tvalid = (sample_tvalid & sample_tlast) ?
+ header_out_tvalid : sample_tvalid;
assign o_tlast = sample_tlast;
assign o_tdata = sample_tdata;
assign o_tuser = header_out_tdata;
-
+
endmodule
+
+
+`default_nettype wire
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
diff --git a/fpga/usrp3/lib/rfnoc/sim/dds_timed_tb/Makefile b/fpga/usrp3/lib/rfnoc/sim/dds_timed_tb/Makefile
new file mode 100644
index 000000000..22dd93ecb
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/sim/dds_timed_tb/Makefile
@@ -0,0 +1,68 @@
+#
+# Copyright 2021 Ettus Research, A National Instruments Brand
+#
+# SPDX-License-Identifier: LGPL-3.0-or-later
+#
+
+#-------------------------------------------------
+# Top-of-Makefile
+#-------------------------------------------------
+# Define BASE_DIR to point to the "top" dir
+BASE_DIR = $(abspath ../../../../top)
+# Include viv_sim_preamble after defining BASE_DIR
+include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak
+
+#-------------------------------------------------
+# Design Specific
+#-------------------------------------------------
+# Include makefiles and sources for the DUT and its dependencies
+include $(BASE_DIR)/../lib/rfnoc/Makefile.srcs
+
+DESIGN_SRCS += $(abspath \
+$(RFNOC_SRCS) \
+)
+
+#-------------------------------------------------
+# IP Specific
+#-------------------------------------------------
+# If simulation contains IP, define the IP_DIR and point
+# it to the base level IP directory.
+IP_DIR = $(BASE_DIR)/x300/ip
+
+# Include makefiles and sources for all IP components
+# *after* defining the IP_DIR
+include $(LIB_IP_DIR)/complex_multiplier_dds/Makefile.inc
+include $(LIB_IP_DIR)/dds_sin_cos_lut_only/Makefile.inc
+
+DESIGN_SRCS += $(abspath \
+$(LIB_IP_COMPLEX_MULTIPLIER_DDS_SRCS) \
+$(LIB_IP_DDS_SIN_COS_LUT_ONLY_SRCS) \
+)
+
+#-------------------------------------------------
+# ModelSim Specific
+#-------------------------------------------------
+
+modelsim vlint : DESIGN_SRCS += $(abspath \
+$(IP_BUILD_DIR)/dds_sin_cos_lut_only/sim/dds_sin_cos_lut_only.vhd \
+$(IP_BUILD_DIR)/complex_multiplier_dds/sim/complex_multiplier_dds.vhd \
+)
+
+MODELSIM_ARGS = glbl
+
+#-------------------------------------------------
+# Testbench Specific
+#-------------------------------------------------
+SIM_TOP ?= dds_timed_tb
+
+SIM_SRCS = \
+$(abspath $(SIM_TOP).sv) \
+$(VIVADO_PATH)/data/verilog/src/glbl.v \
+
+#-------------------------------------------------
+# Bottom-of-Makefile
+#-------------------------------------------------
+# Include all simulator specific makefiles here
+# Each should define a unique target to simulate
+# e.g. xsim, vsim, etc and a common "clean" target
+include $(BASE_DIR)/../tools/make/viv_simulator.mak
diff --git a/fpga/usrp3/lib/rfnoc/sim/dds_timed_tb/dds_timed_tb.sv b/fpga/usrp3/lib/rfnoc/sim/dds_timed_tb/dds_timed_tb.sv
new file mode 100644
index 000000000..c9cc29edd
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/sim/dds_timed_tb/dds_timed_tb.sv
@@ -0,0 +1,886 @@
+//
+// Copyright 2021 Ettus Research, A National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: dds_timed_tb
+//
+
+`default_nettype none
+
+
+module dds_timed_tb;
+
+ // Include macros and time declarations for use with PkgTestExec
+ `include "test_exec.svh"
+
+ import PkgTestExec::*;
+ import PkgAxiStreamBfm::*;
+ import PkgComplex::*;
+ import PkgMath::*;
+ import PkgRandom::*;
+
+ //---------------------------------------------------------------------------
+ // Testbench Configuration
+ //---------------------------------------------------------------------------
+
+ localparam real CLK_PERIOD = 10.0;
+
+ // Values needed by the DUT (use the same values as the DUC)
+ localparam int SR_FREQ_ADDR = 132;
+ localparam int SR_SCALE_IQ_ADDR = 133;
+ localparam int SR_AWIDTH = 8;
+ localparam int SR_DWIDTH = 32;
+ localparam int SR_TWIDTH = 64;
+ localparam int PHASE_ACCUM_WIDTH = 32;
+ localparam int SCALING_WIDTH = 18;
+
+ // Bit widths for our sample size
+ localparam int FRAC_W = 15; // Number of fixed point fractional bits
+ localparam int COMP_W = 16; // Width of just the imag/real part
+ localparam int SAMPLE_W = 2*COMP_W; // Width of a complex sample
+
+ // Max min possible values for the components of a sample
+ localparam bit signed [COMP_W-1:0] MAX_COMP = 2**(COMP_W-1) - 1;
+ localparam bit signed [COMP_W-1:0] MIN_COMP = -2**(COMP_W-1);
+
+ // Max/min possible values for the scale register
+ localparam real MAX_SCALE = +(2**(SCALING_WIDTH-1) - 1) / (2.0**FRAC_W);
+ localparam real MIN_SCALE = -(2**(SCALING_WIDTH-1) ) / (2.0**FRAC_W);
+
+ // TUSER bit positions
+ localparam int HAS_TIME_POS = 125;
+ localparam int EOB_POS = 124;
+ localparam int TIMESTAMP_POS = 0;
+
+ // AXI-Stream data bus parameters
+ localparam int DATA_W = SAMPLE_W;
+ localparam int USER_W = 128;
+
+ // Amount of rounding error to allow (in ULPs). We generally expect the error
+ // in computation to be +/- 1 ULP, but since the DUT performs several
+ // computations, error can accumulate. The testbench computations also
+ // introduce some error. In particular, using the scale register in the DUT
+ // also scales the error. The MAX_ERROR can be reduced to 2 if you keep the
+ // scale register < 1.0.
+ localparam MAX_ERROR = 8;
+
+
+ //---------------------------------------------------------------------------
+ // Type Definitions
+ //---------------------------------------------------------------------------
+
+ // Burst test configuration
+ typedef struct {
+ int spp; // Samples per packet to generate.
+ int spp_last; // Length of last packet, if different from SPP.
+ // Set to -1 to use spp value.
+ int num_packets; // Number of packets in each burst.
+ int num_bursts; // Number of bursts to send.
+ real amp; // Amplitude of the test signal to generate.
+ real freq; // Normalized frequency of test signal to generate.
+ bit timed; // Set to 1 for timed packet, 0 for non-timed. If
+ // doing a timed tune, this must be 1.
+ real scale; // Scale value to use, in the range [-4,4).
+ real freq_shift; // Initial frequency shift to use.
+ real tune_freq_shift; // New frequency shift to tune to.
+ longint tune_time; // Time after which to tune the frequency (set a
+ // new frequency shift). Set to -1 to disable.
+ } burst_cfg_t;
+
+ typedef AxiStreamPacket #(.DATA_WIDTH(DATA_W), .USER_WIDTH(USER_W)) axis_pkt_t;
+ typedef axis_pkt_t axis_pkt_queue_t[$];
+
+ // Default settings to use for burst_cfg_t. This creates a nice complex
+ // sinusoid and the output should match the input, unchanged.
+ localparam burst_cfg_t DEFAULT_BURST_CFG = '{
+ spp : 256,
+ spp_last : -1,
+ num_packets : 1,
+ num_bursts : 1,
+ freq : 1.0/16.0,
+ amp : 0.75,
+ timed : 1,
+ scale : 1.0,
+ freq_shift : 0.0,
+ tune_freq_shift : 0.0,
+ tune_time : -1.0
+ };
+
+
+ //---------------------------------------------------------------------------
+ // Clocks and Resets
+ //---------------------------------------------------------------------------
+
+ bit clk, rst;
+
+ sim_clock_gen #(CLK_PERIOD) clk_gen (clk, rst);
+
+
+ //---------------------------------------------------------------------------
+ // AXI-Stream BFM
+ //---------------------------------------------------------------------------
+
+ // AXI-Stream interfaces to/from DUT
+ AxiStreamIf #(.DATA_WIDTH(DATA_W), .USER_WIDTH(USER_W), .TKEEP(0))
+ to_dut (clk, rst);
+ AxiStreamIf #(.DATA_WIDTH(DATA_W), .USER_WIDTH(USER_W), .TKEEP(0))
+ from_dut (clk, rst);
+
+ // BFM for the AXI-Stream interface to DUT
+ AxiStreamBfm #(.DATA_WIDTH(DATA_W), .USER_WIDTH(USER_W), .TKEEP(0))
+ axis_bfm = new(to_dut, from_dut);
+
+
+ //---------------------------------------------------------------------------
+ // DUT
+ //---------------------------------------------------------------------------
+
+ logic clear = 1'b0;
+ logic timed_cmd_fifo_full;
+ logic set_stb = 1'b0;
+ logic [SR_AWIDTH-1:0] set_addr;
+ logic [SR_DWIDTH-1:0] set_data;
+ logic [SR_TWIDTH-1:0] set_time;
+ logic set_has_time;
+ logic [ SAMPLE_W-1:0] i_tdata;
+ logic i_tlast;
+ logic i_tvalid;
+ logic i_tready;
+ logic [ USER_W-1:0] i_tuser;
+ logic [ SAMPLE_W-1:0] o_tdata;
+ logic o_tlast;
+ logic o_tvalid;
+ logic o_tready;
+ logic [ USER_W-1:0] o_tuser;
+
+ dds_timed #(
+ .SR_FREQ_ADDR (SR_FREQ_ADDR),
+ .SR_SCALE_IQ_ADDR (SR_SCALE_IQ_ADDR),
+ .PHASE_ACCUM_WIDTH (PHASE_ACCUM_WIDTH),
+ .SCALING_WIDTH (SCALING_WIDTH),
+ .SR_AWIDTH (SR_AWIDTH),
+ .SR_DWIDTH (SR_DWIDTH),
+ .SR_TWIDTH (SR_TWIDTH)
+ ) dds_timed_i (
+ .clk (clk),
+ .reset (rst),
+ .clear (clear),
+ .timed_cmd_fifo_full (timed_cmd_fifo_full),
+ .set_stb (set_stb),
+ .set_addr (set_addr),
+ .set_data (set_data),
+ .set_time (set_time),
+ .set_has_time (set_has_time),
+ .i_tdata (to_dut.tdata),
+ .i_tlast (to_dut.tlast),
+ .i_tvalid (to_dut.tvalid),
+ .i_tready (to_dut.tready),
+ .i_tuser (to_dut.tuser),
+ .o_tdata (from_dut.tdata),
+ .o_tlast (from_dut.tlast),
+ .o_tvalid (from_dut.tvalid),
+ .o_tready (from_dut.tready),
+ .o_tuser (from_dut.tuser)
+ );
+
+
+ //---------------------------------------------------------------------------
+ // Timer
+ //---------------------------------------------------------------------------
+ //
+ // Count the samples going into the DUT so we have something that tracks
+ // packet timestamp in the testbench.
+ //
+ //---------------------------------------------------------------------------
+
+ longint current_time = 0;
+
+ always @(posedge clk) begin
+ if (to_dut.tvalid && to_dut.tready) begin
+ current_time <= current_time + 1;
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Expected Output
+ //---------------------------------------------------------------------------
+ //
+ // This assigns the expected output to a signal so we can visualize what the
+ // testbench is expecting. Error checking isn't done here. This is only to
+ // aid in debug.
+ //
+ //---------------------------------------------------------------------------
+
+ mailbox #(axis_pkt_t) exp_pkts_mb = new();
+ bit exp_data_mismatch = 0;
+ bit exp_user_mismatch = 0;
+
+ logic [SAMPLE_W-1:0] exp_tdata;
+ logic [ USER_W-1:0] exp_tuser;
+
+ always @(posedge clk) begin
+ if (rst) begin
+ exp_tdata = 'X;
+ exp_tuser = 'X;
+ end else begin
+ static axis_pkt_t exp_pkt = null;
+ static bit out_valid = 0;
+
+ // Give time for the DUT to update its status, so we know what to do.
+ #(0.01ns);
+
+ // Output the next expected sample if we haven't done so already
+ if (from_dut.tvalid && !out_valid) begin
+ int rval;
+
+ // Get the next packet from the mailbox if needed
+ if (exp_pkt == null) begin
+ rval = exp_pkts_mb.try_get(exp_pkt);
+ `ASSERT_ERROR(rval, "Couldn't get first packet from exp_pkts_mb.");
+ end else if (exp_pkt.data.size() == 0) begin
+ rval = exp_pkts_mb.try_get(exp_pkt);
+ `ASSERT_ERROR(rval, "Couldn't get next packet from exp_pkts_mb.");
+ end
+
+ // Output the next sample
+ `ASSERT_ERROR(exp_pkt.data.size(), "exp_pkt.data is empty");
+ exp_tdata = exp_pkt.data.pop_front();
+ `ASSERT_ERROR(exp_pkt.user.size(), "exp_pkt.user is empty");
+ exp_tuser = exp_pkt.user.pop_front();
+ out_valid = 1;
+ end
+
+ exp_data_mismatch = compare_samples(exp_tdata, from_dut.tdata);
+ exp_user_mismatch = compare_samples(exp_tuser, from_dut.tuser);
+
+ // Check if the output has been accepted and needs to update
+ if (from_dut.tvalid && from_dut.tready) begin
+ out_valid = 0;
+ end
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Helper Functions
+ //---------------------------------------------------------------------------
+
+ // Round a floating point number to num_bits bits of precision.
+ function automatic real round_bits(real num, int num_bits);
+ return real'(longint'(num * (2.0**num_bits))) / (2.0**num_bits);
+ endfunction : round_bits
+
+
+ // Compare the samples a and b to see if either component differs by more
+ // than MAX_ERROR.
+ function automatic bit compare_samples(sc16_t a, sc16_t b);
+ Math #(s16_t) m;
+ sc16_t diff;
+ diff = sub_sc16(a, b);
+ if (m.abs(diff.re) > MAX_ERROR || m.abs(diff.im) > MAX_ERROR) return 1;
+ return 0;
+ endfunction : compare_samples
+
+
+ // Compare the packets, sample by sample. Returns a string error message
+ // explaining the nature of the mismatch. If packets match, an empty string
+ // is returned.
+ function automatic string compare_packets(axis_pkt_t actual, axis_pkt_t expected);
+ if (actual.data.size() != expected.data.size()) begin
+ return $sformatf("Packet lengths do not match. Actual is %0d, expected is %0d.",
+ actual.data.size(), expected.data.size());
+ end
+
+ foreach(actual.data[i]) begin
+ sc16_t a, b;
+
+ // Check the samples in TDATA.
+ // Calculate the difference between the actual end expected values.
+ a = actual.data[i];
+ b = expected.data[i];
+ if (compare_samples(a, b)) begin
+ `ASSERT_WARNING(0, "compare_packets: Skipping rest of packet due to mismatch.")
+ return $sformatf("Word %0d in packet TDATA does not match. Actual is 0x%X, expected is 0x%X.",
+ i, actual.data[i], expected.data[i]);
+ end
+
+ // Check TUSER. This is only guaranteed to be valid on the last sample of
+ // each packet due to the way it's currently implemented.
+ if (i == actual.data.size()-1 && actual.user[i] != expected.user[i]) begin
+ string fields;
+ if (actual.user[i][EOB_POS] != expected.user[i][EOB_POS]) begin
+ fields = {fields, "(EOB)"};
+ end
+ if (actual.user[i][HAS_TIME_POS] != expected.user[i][HAS_TIME_POS]) begin
+ fields = {fields, "(HAS_TIME)"};
+ end
+ if (actual.user[i][TIMESTAMP_POS+:64] != expected.user[i][TIMESTAMP_POS+:64]) begin
+ fields = {fields, "(TIMESTAMP)"};
+ end
+ if (fields == "") fields = "<None>";
+ `ASSERT_WARNING(0, "compare_packets: Skipping rest of packet due to mismatch.")
+ return $sformatf({
+ "Word %0d in packet TUSER does not match. ",
+ "Fields not matching: %s. ",
+ "Actual is %X, expected is %X."},
+ i, fields, actual.user[i], expected.user[i]);
+ end
+ end
+ // Return empty string if all is well
+ return "";
+ endfunction : compare_packets
+
+
+ // Generate a test packet containing a complex sinusoid signal e^(j∙2π∙f∙t)
+ // and return it.
+ //
+ // length: The length of the packet to generate in samples.
+ // freq: Normalized frequency of the signal to generate.
+ // eob: EOB flag for the packet.
+ // timed: Set to 1 for a timed packet, 0 for non-timed. Timed is the
+ // default.
+ // timestamp: Timestamp for the first packet. Leave at the default value
+ // to continue from the time of the previous packet.
+ // init: Initial phase value to use (t in e^jt). Leave at the default
+ // value to use the last value of the previous packet. Must be
+ // in the range [0,1), where 1.0 corresponds to 2*pi radians.
+ //
+ function automatic axis_pkt_t gen_test_packet(
+ int length,
+ real freq,
+ real amp = 0.75,
+ bit eob = 0,
+ longint timed = 1,
+ longint timestamp = -1,
+ real init = -1.0
+ );
+ static real phase;
+ static longint next_time = 0;
+ bit signed [COMP_W-1:0] re, im;
+ int re_int, im_int;
+ logic [USER_W-1:0] user;
+ axis_pkt_t packet;
+
+ if (init != -1.0) begin
+ phase = init;
+ end
+
+ if (timestamp >= 0) begin
+ next_time = timestamp;
+ end
+
+ packet = new();
+ for (int sample_num = 0; sample_num < length; sample_num++) begin
+ // Calculate I/Q
+ re_int = $cos(phase*TAU) * amp * 2**FRAC_W;
+ im_int = $sin(phase*TAU) * amp * 2**FRAC_W;
+
+ // Saturate
+ if(re_int > MAX_COMP) re = MAX_COMP;
+ else if(re_int < MIN_COMP) re = MIN_COMP;
+ else re = re_int;
+ if(im_int > MAX_COMP) im = MAX_COMP;
+ else if(im_int < MIN_COMP) im = MIN_COMP;
+ else im = im_int;
+
+ // Calculate TUSER (header)
+ user = '0;
+ user[EOB_POS] = eob;
+ user[HAS_TIME_POS] = timed;
+ user[TIMESTAMP_POS +: 64] = timed ? next_time : 'X;
+
+ // Enqueue the sample
+ packet.data.push_back({re, im});
+ packet.user.push_back(user);
+ phase += freq;
+ end
+
+ // Calculate the timestamp for the next packet
+ next_time += length;
+
+ return packet;
+ endfunction : gen_test_packet
+
+
+ // Apply a frequency shift to the packet data, by multiplying each sample by
+ // the output of a complex NCO. The implementation here models the HDL so
+ // that we don't accumulate error over time.
+ //
+ // packet : Input packet with the samples to frequency shift.
+ // freq : Normalized frequency shift to apply.
+ // reset_nco : If 1, reset the NCO to 0 before beginning. Otherwise
+ // continue from previous value.
+ // first_sample : First sample to frequency shift
+ // last_sample : Last sample to frequency shift (inclusive)
+ //
+ // Returns: A new packet with the frequency-shifted data.
+ //
+ function automatic axis_pkt_t freq_shift_pkt(
+ axis_pkt_t packet,
+ real freq,
+ bit reset_nco = 0,
+ int first_sample = 0,
+ int last_sample = -1
+ );
+ // Normalized phase angle in the range [0,1), corresponding to [0,2π)
+ // radians.
+ static bit [PHASE_ACCUM_WIDTH-1:0] phase = 0;
+ bit [PHASE_ACCUM_WIDTH-1:0] phase_inc;
+ axis_pkt_t new_packet;
+
+ new_packet = packet.copy();
+
+ phase_inc = freq * (2.0**PHASE_ACCUM_WIDTH);
+ if (reset_nco) begin
+ phase = 0;
+ end
+
+ if (packet == null) return null;
+
+ last_sample = last_sample < 0 ? packet.data.size()-1 : last_sample;
+ for (int i = first_sample; i <= last_sample; i++) begin
+ // There are a lot of redundant variables in this loop. This was done to
+ // aid in debugging so we can correlate what's calculated here to what
+ // the DUT computes, and to have both fixed-point and floating point
+ // values.
+ sc16_t in_sc16, out_sc16;
+ complex_t nco;
+ complex_t in_c, out_c;
+ real phase_real;
+
+ // Get the next input sample and convert it
+ in_sc16 = packet.data[i];
+ in_c = sc16_to_complex(in_sc16);
+
+ // Convert the phase
+ phase_real = real'(phase) / (2.0**PHASE_ACCUM_WIDTH);
+
+ // Compute the new NCO value: nco = exp(j∙2π∙phase)
+ nco = polar_to_complex(1.0, TAU * phase_real);
+
+ // Compute the new data output: sample_out = nco * sample_in
+ out_c = mul(nco, in_c);
+ out_sc16 = complex_to_sc16(out_c);
+ new_packet.data[i] = out_sc16;
+
+ // Update the phase for the next iteration
+ phase = phase + phase_inc;
+ end
+ return new_packet;
+ endfunction : freq_shift_pkt
+
+ // Return a scaled version of the input data packet. That is, where each
+ // sample is multiplied by scale. This models the precision provided by the
+ // scaler in the DUT.
+ function automatic axis_pkt_t scale_packet(axis_pkt_t packet, real scale);
+ bit [SAMPLE_W-1:0] sample;
+ bit signed [COMP_W-1:0] re, im, a, b;
+ int re_tmp, im_tmp;
+ axis_pkt_t new_packet;
+
+ // Make sure scale is in the range supported by hardware
+ if (scale > MAX_SCALE) scale = MAX_SCALE;
+ else if (scale < MIN_SCALE) scale = MIN_SCALE;
+
+ new_packet = packet.copy();
+ foreach (packet.data[i]) begin
+ sample = packet.data[i];
+ re = sample[1*COMP_W +: COMP_W];
+ im = sample[0*COMP_W +: COMP_W];
+ // Scale with full precision
+ re_tmp = re * scale;
+ im_tmp = im * scale;
+ // Saturate the values
+ if (re_tmp > MAX_COMP) re = MAX_COMP;
+ else if (re_tmp < MIN_COMP) re = MIN_COMP;
+ else re = re_tmp;
+ if (im_tmp > MAX_COMP) im = MAX_COMP;
+ else if (im_tmp < MIN_COMP) im = MIN_COMP;
+ else im = im_tmp;
+ new_packet.data[i] = { re, im };
+ end
+ return new_packet;
+ endfunction : scale_packet
+
+ // Generate the output packets we expect from the DUT given the provided
+ // burst of packets and configuration.
+ //
+ // cfg : Burst test configuration used
+ // packets : Queue of packets that were input to the DUT
+ //
+ // returns : Expected packets from DUT
+ //
+ function automatic axis_pkt_queue_t generate_expected(
+ burst_cfg_t cfg,
+ axis_pkt_queue_t packets
+ );
+ static longint timestamp = 0;
+ axis_pkt_t expected[$];
+ axis_pkt_t packet;
+ bit reset_nco;
+ int first_sample;
+ int last_sample;
+ real freq_shift;
+
+ freq_shift = cfg.freq_shift;
+
+ foreach(packets[i]) begin
+ // Make a copy of the input
+ packet = packets[i].copy();
+
+ // Check if we're supposed to tune the frequency in this packet
+ first_sample = 0;
+ if (cfg.timed && timestamp <= cfg.tune_time &&
+ timestamp + packet.data.size() > cfg.tune_time) begin
+ last_sample = cfg.tune_time - timestamp;
+ end else begin
+ last_sample = -1;
+ end
+
+ // Apply a frequency shift (reset the NCO before each burst)
+ reset_nco = i % cfg.num_packets == 0;
+ packet = freq_shift_pkt(packet, freq_shift, reset_nco, first_sample, last_sample);
+
+ // If there was a tune, shift the rest of the packet differently
+ if (last_sample >= 0 && last_sample < packet.data.size()) begin
+ freq_shift = cfg.tune_freq_shift;
+ reset_nco = 1;
+ first_sample = last_sample + 1;
+ last_sample = -1;
+ packet = freq_shift_pkt(packet, freq_shift, reset_nco, first_sample, last_sample);
+ end
+
+ // Multiply packet samples by a scaler
+ packet = scale_packet(packet, cfg.scale);
+
+ // Add this packet to the queue
+ expected.push_back(packet);
+
+ // Send this packet to the expected packets mailbox, for debug
+ `ASSERT_ERROR(exp_pkts_mb.try_put(packet.copy()), "Unable to put expected packet");
+
+ // Calculate new timestamp
+ timestamp += packet.data.size();
+ end
+
+ return expected;
+ endfunction : generate_expected
+
+
+ // Generate a queue of packets modeled after the burst test configuration
+ // defined by cfg.
+ function automatic axis_pkt_queue_t generate_bursts(burst_cfg_t cfg);
+ axis_pkt_t packets[$];
+
+ // Reset initial phase and time to 0 in generated packets by calling the
+ // generator with init and timestamp set to 0.
+ void'(gen_test_packet(.length(0), .freq(0), .init(0)));
+
+ // Build the packets to send
+ for (int burst_num = 0; burst_num < cfg.num_bursts; burst_num++) begin
+ for (int packet_num = 0; packet_num < cfg.num_packets; packet_num++) begin
+ axis_pkt_t packet;
+ bit eob;
+ int length;
+
+ // Set EOB and use spp_last for the last packet
+ if (packet_num == cfg.num_packets-1) begin
+ eob = 1;
+ length = (cfg.spp_last > 0) ? cfg.spp_last : cfg.spp;
+ end else begin
+ eob = 0;
+ length = cfg.spp;
+ end
+
+ packet = gen_test_packet(
+ .length (length),
+ .freq (cfg.freq),
+ .amp (cfg.amp),
+ .eob (eob),
+ .timed (cfg.timed));
+ packets.push_back(packet);
+ end
+ end
+
+ return packets;
+ endfunction : generate_bursts
+
+
+ // Write a value to a settings register.
+ //
+ // addr : Address of the register to write to.
+ // value : Value to write to the register.
+ // timestamp : Timestamp to provide with the write. Set to -1 if the write
+ // should not be timed.
+ //
+ task automatic write_reg(
+ bit [SR_AWIDTH-1:0] addr,
+ bit [SR_DWIDTH-1:0] value,
+ longint timestamp = -1
+ );
+ @(posedge clk);
+ set_stb <= 1;
+ set_addr <= addr;
+ set_data <= value;
+ set_time <= (timestamp > 0) ? timestamp : 'X;
+ set_has_time <= (timestamp > 0);
+ @(posedge clk);
+ set_stb <= 0;
+ set_addr <= 'X;
+ set_data <= 'X;
+ set_time <= 'X;
+ set_has_time <= 'X;
+ @(posedge clk);
+ endtask : write_reg
+
+
+ // Write a value to the frequency register.
+ //
+ // freq : Normalized frequency to write to the register. E.g., in the
+ // range [-0.5,0.5) or [0,1). Numerically, either works.
+ // timestamp : Timestamp to provide with the write. Set to -1 if the write
+ // should not be timed.
+ //
+ task automatic write_reg_freq(real freq, longint timestamp = -1);
+ write_reg(SR_FREQ_ADDR, freq * (2.0**PHASE_ACCUM_WIDTH), timestamp);
+ endtask : write_reg_freq
+
+
+ // Write a value to the scale register.
+ //
+ // scale : Scaler to write to the register, in the range [-4,4).
+ // timestamp : Timestamp to provide with the write. Set to -1 if the write
+ // should not be timed.
+ //
+ task automatic write_reg_scale(real scale);
+ // Saturate to the range allowed by the register
+ scale = scale > MAX_SCALE ? MAX_SCALE : scale;
+ scale = scale < MIN_SCALE ? MIN_SCALE : scale;
+ write_reg(SR_SCALE_IQ_ADDR, scale * (2.0**FRAC_W));
+ endtask : write_reg_scale
+
+
+ // Check that the output matches what we would expect.
+ //
+ // cfg: Test configuration
+ // packets: The packets that were input to the DUT
+ //
+ task automatic verify_output(burst_cfg_t cfg, axis_pkt_queue_t packets);
+ axis_pkt_t expected[$];
+
+ expected = generate_expected(cfg, packets);
+
+ foreach(packets[i]) begin
+ axis_pkt_t recvd;
+ string msg;
+ axis_bfm.get(recvd);
+ msg = compare_packets(recvd, expected[i]);
+ `ASSERT_ERROR(msg == "",
+ $sformatf("Error in packet %0d: %s", i, msg));
+ end
+ endtask : verify_output
+
+
+ // Test a burst (i.e., multiple packets ending with EOB) through the DUT
+ // using the provided configuration.
+ task automatic test_bursts(burst_cfg_t cfg);
+ axis_pkt_t packets[$];
+
+ // Are we doing timed packets?
+ cfg.timed = (cfg.timed || cfg.tune_time >= 0);
+
+ // Set the registers
+ write_reg_scale(cfg.scale);
+ write_reg_freq(cfg.freq_shift);
+
+ // Schedule a timed tune, if requested
+ if (cfg.tune_time >= 0) begin
+ write_reg_freq(cfg.tune_freq_shift, cfg.tune_time);
+ end
+
+ // Wait a bit for the register changes to take effect
+ clk_gen.clk_wait_r(10);
+
+
+ // Generate test packets to send
+ packets = generate_bursts(cfg);
+
+ // Send the packets
+ foreach (packets[i]) axis_bfm.put(packets[i]);
+
+ // Check the results
+ verify_output(cfg, packets);
+ endtask : test_bursts
+
+
+ //---------------------------------------------------------------------------
+ // Test Procedures
+ //---------------------------------------------------------------------------
+
+ // This performs a few directed test as a sanity check and to test a few
+ // corner cases.
+ task automatic directed_tests();
+ burst_cfg_t cfg;
+
+ // Iterate over different flow control settings to exercise different
+ // scenarios.
+ for (int bfm_config = 0; bfm_config < 4; bfm_config++) begin
+ case (bfm_config)
+ 0 : begin
+ // No stalls: on input or output to DUT
+ axis_bfm.set_master_stall_prob(0);
+ axis_bfm.set_slave_stall_prob(0);
+ end
+ 1 : begin
+ // Overflow: Input to DUT faster than output
+ axis_bfm.set_master_stall_prob(10);
+ axis_bfm.set_slave_stall_prob(30);
+ end
+ 2 : begin
+ // Underflow: Input to DUT slower than output
+ axis_bfm.set_master_stall_prob(30);
+ axis_bfm.set_slave_stall_prob(10);
+ end
+ 3 : begin
+ // Lots of stalls: Input and output stall frequently
+ axis_bfm.set_master_stall_prob(40);
+ axis_bfm.set_slave_stall_prob(40);
+ end
+ endcase
+
+ //-------------------------------
+ // Test Basic Configurations
+ //-------------------------------
+
+ // Test the default configuration
+ cfg = DEFAULT_BURST_CFG;
+ test.start_test($sformatf("Directed Test: bfm_config: %0d, %p", bfm_config, cfg));
+ test_bursts(cfg);
+ test.end_test();
+
+ // Test a somewhat arbitrary but different configuration
+ cfg = DEFAULT_BURST_CFG;
+ cfg.spp = 97;
+ cfg.spp_last = 33;
+ cfg.num_bursts = 2;
+ cfg.num_packets = 3;
+ cfg.amp = 0.5;
+ cfg.scale = 1.25;
+ cfg.freq = 0.23;
+ cfg.freq_shift = 0.17;
+ test.start_test($sformatf("Directed Test: bfm_config: %0d, %p", bfm_config, cfg));
+ test_bursts(cfg);
+ test.end_test();
+
+ // Repeat with a single-sample packet
+ cfg.spp = 1;
+ cfg.spp_last = 1;
+ test.start_test($sformatf("Directed Test: bfm_config: %0d, %p", bfm_config, cfg));
+ test_bursts(cfg);
+ test.end_test();
+
+ //-------------------------------
+ // Test timed tunes
+ //-------------------------------
+
+ cfg = DEFAULT_BURST_CFG;
+ cfg.spp = 135;
+ cfg.freq = 1.0/32.0;
+ cfg.freq_shift = 0.0;
+ cfg.num_bursts = 1;
+ cfg.num_packets = 3;
+ cfg.scale = 0.75;
+ cfg.freq_shift = 0.0; // Initial frequency shift
+ cfg.tune_freq_shift = 0.13; // New frequency shift
+
+ // Test tuning in the middle of a packet
+ cfg.tune_time = current_time + cfg.num_packets*cfg.spp/2;
+ test.start_test($sformatf("Directed Test: bfm_config: %0d, %p", bfm_config, cfg));
+ test_bursts(cfg);
+ test.end_test();
+
+ // Test tuning at the end of the first packet
+ cfg.tune_time = current_time + cfg.spp-1;
+ test.start_test($sformatf("Directed Test: bfm_config: %0d, %p", bfm_config, cfg));
+ test_bursts(cfg);
+ test.end_test();
+
+ // Test tuning at the beginning of a packet
+ cfg.tune_time = current_time + cfg.spp;
+ test.start_test($sformatf("Directed Test: bfm_config: %0d, %p", bfm_config, cfg));
+ test_bursts(cfg);
+ test.end_test();
+ end
+ endtask : directed_tests
+
+
+ // This generates a randomized configuration exercises the DUT with that
+ // configuration. This is repeated num_tests times, with a unique
+ // configuration each time.
+ task automatic random_tests(int num_tests);
+ burst_cfg_t cfg;
+ int master_stall_prob, slave_stall_prob;
+
+ repeat (num_tests) begin
+ // Choose random values for this run. Round the floating point numbers to
+ // a smaller number of bits to reduce rounding differences between the
+ // testbench and the DUT.
+ cfg = DEFAULT_BURST_CFG;
+ cfg.spp = $urandom_range(1, 64);
+ cfg.spp_last = $urandom_range(1, 64);
+ cfg.num_packets = $urandom_range(1, 3);
+ cfg.num_bursts = $urandom_range(1, 2);
+ cfg.amp = round_bits(frand_range(1.0/16.0, 15.0/16.0), 15);
+ cfg.freq = frand(0.5);
+ cfg.timed = $urandom_range(0, 1);
+ cfg.scale = round_bits(frand_range(-4.0, 4.0), 15);
+ cfg.freq_shift = round_bits(frand(0.5), 32);
+ cfg.tune_freq_shift = round_bits(frand(0.5), 32);
+ if (cfg.timed) begin
+ cfg.tune_time = current_time +
+ $urandom_range(0, (cfg.num_packets-1)*cfg.spp + cfg.spp_last - 1);
+ end
+ master_stall_prob = $urandom_range(0, 50);
+ slave_stall_prob = $urandom_range(0, 50);
+
+ // Run the test
+ test.start_test($sformatf("Random Test: InStall: %0d, OutStall: %0d, %p",
+ master_stall_prob, slave_stall_prob, cfg));
+ axis_bfm.set_master_stall_prob(master_stall_prob);
+ axis_bfm.set_slave_stall_prob(slave_stall_prob);
+ test_bursts(cfg);
+ test.end_test();
+ end
+ endtask : random_tests
+
+
+ //---------------------------------------------------------------------------
+ // Main Test Process
+ //---------------------------------------------------------------------------
+
+ initial begin : main
+ test.start_tb("dds_timed_tb");
+
+ // Start the BFMs running
+ axis_bfm.run();
+
+ // Reset
+ clk_gen.reset();
+ @(negedge rst);
+
+ //-------------------------------
+ // Run Tests
+ //-------------------------------
+
+ directed_tests();
+ random_tests(200);
+
+ test.end_tb();
+ end
+
+endmodule
+
+
+`default_nettype wire