aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/rfnoc/sim
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/sim')
-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
2 files changed, 954 insertions, 0 deletions
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