1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 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
|