diff options
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/sim')
-rw-r--r-- | fpga/usrp3/lib/rfnoc/sim/dds_timed_tb/Makefile | 68 | ||||
-rw-r--r-- | fpga/usrp3/lib/rfnoc/sim/dds_timed_tb/dds_timed_tb.sv | 886 |
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 |