diff --git a/lib/rfnoc/blocks/rfnoc_block_radio/radio_rx_core.v b/lib/rfnoc/blocks/rfnoc_block_radio/radio_rx_core.v index ee7774f..9a7b4a1 100644 --- a/lib/rfnoc/blocks/rfnoc_block_radio/radio_rx_core.v +++ b/lib/rfnoc/blocks/rfnoc_block_radio/radio_rx_core.v @@ -7,19 +7,19 @@ // // Description: // -// This module contains the core Rx radio acquisition logic. It retrieves -// sample data from the radio interface, as indicated by the radio's strobe +// This module contains the core Rx radio acquisition logic. It retrieves +// sample data from the radio interface, as indicated by the radio's strobe // signal, and outputs the data via AXI-Stream. // -// The receiver is operated by writing a time (optionally) to the -// REG_RX_CMD_TIME_* registers and a number of words (optionally) to -// REG_RX_CMD_NUM_WORDS_* registers followed by writing a command word to -// REG_RX_CMD. The command word indicates whether it is a finite ("num samps -// and done") or continuous acquisition and whether or not the acquisition -// should start at the time indicated byREG_RX_CMD_TIME_*. A stop command will +// The receiver is operated by writing a time (optionally) to the +// REG_RX_CMD_TIME_* registers and a number of words (optionally) to +// REG_RX_CMD_NUM_WORDS_* registers followed by writing a command word to +// REG_RX_CMD. The command word indicates whether it is a finite ("num samps +// and done") or continuous acquisition and whether or not the acquisition +// should start at the time indicated byREG_RX_CMD_TIME_*. A stop command will // stop any acquisition that's waiting to start or is in progress. // -// The REG_RX_MAX_WORDS_PER_PKT and REG_RX_ERR_* registers should be +// The REG_RX_MAX_WORDS_PER_PKT and REG_RX_ERR_* registers should be // initialized prior to the first acquisition. // // Parameters: @@ -207,7 +207,7 @@ module radio_rx_core #( if (s_ctrlport_req_rd) begin case (s_ctrlport_req_addr) REG_RX_STATUS: begin - s_ctrlport_resp_data[CMD_FIFO_SPACE_POS+:CMD_FIFO_SPACE_LEN] + s_ctrlport_resp_data[CMD_FIFO_SPACE_POS+:CMD_FIFO_SPACE_LEN] <= cmd_fifo_space[CMD_FIFO_SPACE_LEN-1:0]; s_ctrlport_resp_ack <= 1; end @@ -285,7 +285,8 @@ module radio_rx_core #( .clk (radio_clk), .reset (radio_rst), .clear (clear_fifo), - .i_tdata ({ reg_cmd_time, reg_cmd_timed, reg_cmd_num_words, (reg_cmd_word == RX_CMD_CONTINUOUS) }), + .i_tdata ({ reg_cmd_time, reg_cmd_timed, reg_cmd_num_words, + (reg_cmd_word == RX_CMD_CONTINUOUS) }), .i_tvalid (reg_cmd_valid), .i_tready (), .o_tdata ({ cmd_time, cmd_timed, cmd_num_words, cmd_continuous }), @@ -296,6 +297,45 @@ module radio_rx_core #( ); + //--------------------------------------------------------------------------- + // Sample Alignment + //--------------------------------------------------------------------------- + // + // Shift the incoming radio data to align the requested sample with the start + // of the data word. This ensures that the first sample received matches the + // timestamp requested. + // + //--------------------------------------------------------------------------- + + localparam SHIFT_W = $clog2(NSPC); + + reg [SHIFT_W-1:0] time_shift = 0; + reg align_cfg_en = 0; + wire [SAMP_W*NSPC-1:0] aligned_data; + + if (NSPC > 1) begin : gen_time_alignment + align_samples #( + .SAMP_W (SAMP_W), + .SPC (NSPC ), + .USER_W (1 ), + .PIPE_IN (1 ), + .PIPE_OUT(1 ) + ) shifter_i ( + .clk (radio_clk ), + .i_data (radio_rx_data), + .i_push (radio_rx_stb ), + .i_user (1'b0 ), + .i_dir (1'b0 ), + .i_shift (time_shift ), + .i_cfg_en(align_cfg_en ), + .o_data (aligned_data ), + .o_user ( ) + ); + end else begin : gen_no_time_alignment + assign aligned_data = radio_rx_data; + end + + //--------------------------------------------------------------------------- // Receiver State Machine //--------------------------------------------------------------------------- @@ -325,8 +365,12 @@ module radio_rx_core #( reg out_fifo_teob; reg out_fifo_almost_full; - reg [63:0] radio_time_low_samp, radio_time_hi_samp; - reg time_now, time_past; + reg time_now; // Indicates when we've reached the requested timestamp + reg time_now_p1; // Indicates we've reached the requested timestamp plus 1 + reg time_past; // Indicates when we've passed the requested timestamp + + reg [SHIFT_W-1:0] radio_offset; + reg align_delay; // All ctrlport requests have a time assign m_ctrlport_req_has_time = 1'b1; @@ -342,36 +386,80 @@ module radio_rx_core #( seq_num <= 'd0; m_ctrlport_req_wr <= 1'b0; first_word <= 1'b1; + time_shift <= 0; + align_cfg_en <= 1'b0; + + // Registers for which we don't care if they have a reset or not because + // they're set during state machine execution. + radio_offset <= 'bX; + align_delay <= 'bX; + error_code <= 'bX; + error_time <= 'bX; + m_ctrlport_req_addr <= 'bX; + m_ctrlport_req_data <= 'bX; + m_ctrlport_req_time <= 'bX; + time_now <= 'bX; + time_now_p1 <= 'bX; + time_past <= 'bX; + words_left <= 'bX; + words_left_pkt <= 'bX; end else begin // Default assignments - out_fifo_tvalid <= 1'b0; - out_fifo_tlast <= 1'b0; - out_fifo_teob <= 1'b0; - m_ctrlport_req_wr <= 1'b0; + out_fifo_tvalid <= 1'b0; + out_fifo_tlast <= 1'b0; + out_fifo_teob <= 1'b0; + m_ctrlport_req_wr <= 1'b0; + align_cfg_en <= 1'b0; + + if (NSPC > 1) begin + if (radio_rx_stb) begin + radio_offset <= radio_time[0+:SHIFT_W]; + end + end else begin + radio_offset <= 0; + end if (radio_rx_stb) begin - // Get the time for the low sample and the high sample of the radio - // word (needed when NISPC > 1). Compensate for the delay required to - // check the time by adding 3 clock cycles worth of samples. - radio_time_low_samp <= (radio_time + 3*NSPC); - radio_time_hi_samp <= (radio_time + 3*NSPC + (NSPC-1)); - - // Register the time comparisons so they don't become the critical path - time_now <= (cmd_time >= radio_time_low_samp && - cmd_time <= radio_time_hi_samp); - time_past <= (cmd_time < radio_time_low_samp); + // Register time comparisons so they don't become the critical path. + // Add two to compensate for the pipeline delays of this comparison and + // its propagation through the state machine. This ensures that the + // timestamp in the packet matches the requested timestamp. + time_now <= (radio_time[63:SHIFT_W]+2 == cmd_time[63:SHIFT_W]); + time_now_p1 <= time_now; + time_past <= (radio_time[63:SHIFT_W] >= cmd_time[63:SHIFT_W]); end case (state) ST_IDLE : begin - // Wait for a new command to arrive and allow a cycle for the time - // comparisons to update. + // Wait for a new command to arrive and a radio strobe to update the + // time comparisons. if (cmd_valid && radio_rx_stb) begin state <= ST_TIME_CHECK; end else if (cmd_stop) begin state <= ST_STOP; end first_word <= 1'b1; + + // Calculate the time shift, in samples, needed to left-shift the + // first sample requested into the least-significant position. + // "align_delay" means that the requested sample will arrive one + // radio word later than the requested timestamp due to being shifted + // into the next word by the alignment. + if (NSPC > 1) begin + align_cfg_en <= 1'b1; + if (cmd_timed) begin + if (radio_offset < cmd_time[0+:SHIFT_W]) begin + time_shift <= NSPC - (cmd_time[0+:SHIFT_W] - radio_offset); + align_delay <= 1'b1; + end else begin + time_shift <= radio_offset - cmd_time[0+:SHIFT_W]; + align_delay <= 1'b0; + end + end else begin + time_shift <= 0; + align_delay <= 1'b0; + end + end end ST_TIME_CHECK : begin @@ -386,22 +474,26 @@ module radio_rx_core #( error_code <= ERR_RX_LATE_CMD; error_time <= radio_time; state <= ST_REPORT_ERR; - end else if (!cmd_timed || (time_now && radio_rx_stb)) begin + end else if (!cmd_timed || + (radio_rx_stb && time_now && (!align_delay || NSPC == 1)) || + (radio_rx_stb && time_now_p1 && ( align_delay && NSPC > 1)) + ) begin // Either it's time to run this command or it should run // immediately. - words_left <= cmd_num_words; - words_left_pkt <= reg_max_pkt_len; - state <= ST_RUNNING; + state <= ST_RUNNING; end + + words_left <= cmd_num_words; + words_left_pkt <= reg_max_pkt_len; end ST_RUNNING : begin if (radio_rx_stb) begin // Output the next word - out_fifo_tvalid <= 1'b1; - out_fifo_tdata <= radio_rx_data; + out_fifo_tvalid <= 1'b1; + out_fifo_tdata <= aligned_data; if (first_word) begin - out_fifo_timestamp <= radio_time; + out_fifo_timestamp <= radio_time - time_shift; first_word <= 1'b0; end @@ -437,12 +529,11 @@ module radio_rx_core #( error_code <= ERR_RX_OVERRUN; state <= ST_REPORT_ERR; end - end end ST_STOP : begin - // This single-cycle state allows time for STOP to be acknowledged + // This single-cycle state allows time for STOP to be acknowledged // and for the command FIFO to be popped. state <= ST_IDLE; end @@ -472,7 +563,7 @@ module radio_rx_core #( assign radio_rx_running = (state == ST_RUNNING); // We're actively acquiring - // Directly connect the port ID, remote port ID, and remote EPID since they + // Directly connect the port ID, remote port ID, and remote EPID since they // are only used for error reporting. assign m_ctrlport_req_portid = reg_error_portid; assign m_ctrlport_req_rem_epid = reg_error_rem_epid; @@ -514,7 +605,6 @@ module radio_rx_core #( end end - endmodule diff --git a/lib/rfnoc/blocks/rfnoc_block_radio/radio_tx_core.v b/lib/rfnoc/blocks/rfnoc_block_radio/radio_tx_core.v index d40db51..075c4e9 100644 --- a/lib/rfnoc/blocks/rfnoc_block_radio/radio_tx_core.v +++ b/lib/rfnoc/blocks/rfnoc_block_radio/radio_tx_core.v @@ -7,20 +7,20 @@ // // Description: // -// This module contains the core Tx radio data-path logic. It receives samples -// over AXI-Stream that it then sends to the radio interface coincident with a +// This module contains the core Tx radio data-path logic. It receives samples +// over AXI-Stream that it then sends to the radio interface coincident with a // strobe signal that must be provided by the radio interface. // -// There are no registers for starting or stopping the transmitter. It is -// operated simply by providing data packets via its AXI-Stream data interface. -// The end-of-burst (EOB) signal is used to indicate when the transmitter is -// allowed to stop transmitting. Packet timestamps can be used to indicate when +// There are no registers for starting or stopping the transmitter. It is +// operated simply by providing data packets via its AXI-Stream data interface. +// The end-of-burst (EOB) signal is used to indicate when the transmitter is +// allowed to stop transmitting. Packet timestamps can be used to indicate when // transmission should start. // -// Care must be taken to provide data to the transmitter at a rate that is -// faster than the radio needs it so that underflows do not occur. Similarly, -// timed packets must be delivered before the timestamp expires. If a packet -// arrives late, then it will be dropped and the error will be reported via the +// Care must be taken to provide data to the transmitter at a rate that is +// faster than the radio needs it so that underflows do not occur. Similarly, +// timed packets must be delivered before the timestamp expires. If a packet +// arrives late, then it will be dropped and the error will be reported via the // CTRL port interface. // // Parameters: @@ -187,6 +187,44 @@ module radio_tx_core #( end + //--------------------------------------------------------------------------- + // Sample Alignment + //--------------------------------------------------------------------------- + // + // Shift the outgoing data to align the first sample with sample position + // corresponding to the requested timestamp. + // + //--------------------------------------------------------------------------- + + localparam SHIFT_W = $clog2(NSPC); + + reg [SHIFT_W-1:0] time_shift = 0; + reg align_cfg_en = 0; + wire [SAMP_W*NSPC-1:0] unaligned_data; + + if (NSPC > 1) begin : gen_time_alignment + align_samples #( + .SAMP_W (SAMP_W), + .SPC (NSPC ), + .USER_W (1 ), + .PIPE_IN (1 ), + .PIPE_OUT(1 ) + ) align_samples_i ( + .clk (radio_clk ), + .i_data (unaligned_data), + .i_push (radio_tx_stb ), + .i_user (1'b0 ), + .i_dir (1'b0 ), + .i_shift (time_shift ), + .i_cfg_en(align_cfg_en ), + .o_data (radio_tx_data ), + .o_user ( ) + ); + end else begin : gen_no_time_alignment + assign radio_tx_data = unaligned_data; + end + + //--------------------------------------------------------------------------- // Transmitter State Machine //--------------------------------------------------------------------------- @@ -195,9 +233,11 @@ module radio_tx_core #( localparam ST_IDLE = 0; localparam ST_TIME_CHECK = 1; localparam ST_TRANSMIT = 2; - localparam ST_POLICY_WAIT = 3; + localparam ST_WAIT_ALIGN0 = 3; + localparam ST_WAIT_ALIGN1 = 4; + localparam ST_POLICY_WAIT = 5; - reg [1:0] state = ST_IDLE; + reg [2:0] state = ST_IDLE; reg sop = 1'b1; // Start of packet @@ -205,20 +245,50 @@ module radio_tx_core #( reg [ 63:0] new_error_time; reg new_error_valid = 1'b0; - reg time_now, time_past; + reg time_now; // Indicates when we've reached the requested timestamp + reg time_now_m1; // Indicates we've reached the requested timestamp minus 1 + reg time_past; // Indicates when we've passed the requested timestamp + reg [SHIFT_W-1:0] radio_offset = 0; + reg send_early; always @(posedge radio_clk) begin if (radio_rst) begin state <= ST_IDLE; sop <= 1'b1; new_error_valid <= 1'b0; - end else begin - new_error_valid <= 1'b0; + time_shift <= 0; + align_cfg_en <= 1'b0; - // Register time comparisons so they don't become the critical path - time_now <= (radio_time == s_axis_ttimestamp); - time_past <= (radio_time > s_axis_ttimestamp); + // Registers for which we don't care if they have a reset or not because + // they're set during state machine execution. + radio_offset <= 'bX; + send_early <= 'bX; + new_error_code <= 'bX; + new_error_time <= 'bX; + new_error_valid <= 'bX; + time_now <= 'bX; + time_now_m1 <= 'bX; + time_past <= 'bX; + end else begin + // Default assignments + new_error_valid <= 1'b0; + align_cfg_en <= 1'b0; + + if (radio_tx_stb) begin + // Register time comparisons so they don't become the critical path + time_now_m1 <= (radio_time[63:SHIFT_W]+1 == s_axis_ttimestamp[63:SHIFT_W]); + time_now <= time_now_m1; + time_past <= (radio_time[63:SHIFT_W] > s_axis_ttimestamp[63:SHIFT_W]); + end + + if (NSPC > 1) begin + if (radio_tx_stb) begin + radio_offset <= radio_time[0+:SHIFT_W]; + end + end else begin + radio_offset <= 0; + end // Track if the next word will be the start of a packet (sop) if (s_axis_tvalid && s_axis_tready) begin @@ -227,15 +297,39 @@ module radio_tx_core #( case (state) ST_IDLE : begin - // Wait for a new packet to arrive and allow a cycle for the time - // comparisons to update. - if (s_axis_tvalid) begin - state <= ST_TIME_CHECK; + // Wait for a new packet to arrive and a radio strobe to update the + // time comparisons. + if (s_axis_tvalid && radio_tx_stb) begin + align_cfg_en <= 1'b1; + state <= ST_TIME_CHECK; + end + + // Calculate the time shift, in samples, needed to left-shift the + // first sample to be transmitted into the time slot indicated by the + // requested timestamp. "send_early" means that the requested + // timestamp is actually one word earlier than the word with the + // matching timestamp because of the way the radio_time is aligned. + if (NSPC > 1) begin + if (s_axis_thas_time) begin + if (radio_offset > s_axis_ttimestamp[0+:SHIFT_W]) begin + time_shift <= NSPC - (radio_offset - s_axis_ttimestamp[0+:SHIFT_W]); + send_early <= 1'b1; + end else begin + time_shift <= s_axis_ttimestamp[0+:SHIFT_W] - radio_offset; + send_early <= 1'b0; + end + end else begin + time_shift <= 0; + send_early <= 1'b0; + end end end ST_TIME_CHECK : begin - if (!s_axis_thas_time || time_now) begin + if (!s_axis_thas_time || + (radio_tx_stb && time_now_m1 && ( send_early && NSPC > 1)) || + (radio_tx_stb && time_now && (!send_early || NSPC == 1)) + ) begin // We have a new packet without a timestamp, or a new packet // whose time has arrived. state <= ST_TRANSMIT; @@ -268,11 +362,29 @@ module radio_tx_core #( new_error_code <= ERR_TX_EOB_ACK; new_error_time <= radio_time; new_error_valid <= 1'b1; - state <= ST_IDLE; + if (NSPC > 1) begin + state <= ST_WAIT_ALIGN0; + end else begin + state <= ST_IDLE; + end end end end + ST_WAIT_ALIGN0 : begin + // Add extra radio word delays to ensure we don't update the time + // alignment until the last word is strobed out. + if (radio_tx_stb) begin + state <= ST_WAIT_ALIGN1; + end + end + + ST_WAIT_ALIGN1 : begin + if (radio_tx_stb) begin + state <= ST_IDLE; + end + end + ST_POLICY_WAIT : begin // If we came here from ST_TIME_CHECK or ST_TRANSMIT and we're in the // middle of a packet then we just wait until we reach the end of the @@ -299,9 +411,9 @@ module radio_tx_core #( // Output the current sample whenever we're transmitting and the sample is // valid. Otherwise, output the idle value. - assign radio_tx_data = (s_axis_tvalid && state == ST_TRANSMIT) ? - s_axis_tdata : - {NSPC{reg_idle_value[SAMP_W-1:0]}}; + assign unaligned_data = (s_axis_tvalid && state == ST_TRANSMIT) ? + s_axis_tdata : + {NSPC{reg_idle_value[SAMP_W-1:0]}}; // Read packet in the transmit state or dump it in the error state assign s_axis_tready = (radio_tx_stb && (state == ST_TRANSMIT)) || @@ -364,8 +476,8 @@ module radio_tx_core #( // //--------------------------------------------------------------------------- - localparam ST_ERR_IDLE = 0; - localparam ST_ERR_CODE = 1; + localparam ST_ERR_IDLE = 0; + localparam ST_ERR_CODE = 1; reg [0:0] err_state = ST_ERR_IDLE; @@ -378,19 +490,19 @@ module radio_tx_core #( err_state <= ST_ERR_IDLE; next_error_ready <= 1'b0; end else begin - m_ctrlport_req_wr <= 1'b0; - next_error_ready <= 1'b0; + m_ctrlport_req_wr <= 1'b0; + next_error_ready <= 1'b0; case (err_state) ST_ERR_IDLE : begin if (next_error_valid) begin // Setup write of error code - m_ctrlport_req_wr <= 1'b1; - m_ctrlport_req_addr <= reg_error_addr; - m_ctrlport_req_data <= {{(32-ERR_TX_CODE_W){1'b0}}, next_error_code}; - m_ctrlport_req_time <= next_error_time; - next_error_ready <= 1'b1; - err_state <= ST_ERR_CODE; + m_ctrlport_req_wr <= 1'b1; + m_ctrlport_req_addr <= reg_error_addr; + m_ctrlport_req_data <= {{(32-ERR_TX_CODE_W){1'b0}}, next_error_code}; + m_ctrlport_req_time <= next_error_time; + next_error_ready <= 1'b1; + err_state <= ST_ERR_CODE; end end @@ -407,7 +519,7 @@ module radio_tx_core #( end - // Directly connect the port ID, remote port ID, remote EPID since they are + // Directly connect the port ID, remote port ID, remote EPID since they are // only used for error reporting. assign m_ctrlport_req_portid = reg_error_portid; assign m_ctrlport_req_rem_epid = reg_error_rem_epid; diff --git a/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_all_tb.sv b/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_all_tb.sv index ea99692..953c3b9 100644 --- a/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_all_tb.sv +++ b/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_all_tb.sv @@ -5,64 +5,26 @@ // // Module: rfnoc_block_radio_all_tb // -// Description: This is the testbench for rfnoc_block_radio that instantiates +// Description: This is the testbench for rfnoc_block_radio that instantiates // several variations of rfnoc_block_radio_tb to test different configurations. // module rfnoc_block_radio_all_tb; - timeunit 1ns; - timeprecision 1ps; - - import PkgTestExec::*; - - //--------------------------------------------------------------------------- - // Test Definitions + // Test Configurations //--------------------------------------------------------------------------- - typedef struct { - int CHDR_W; - int ITEM_W; - int NIPC; - int NUM_PORTS; - int STALL_PROB; - int STB_PROB; - bit TEST_REGS; - } test_config_t; - - localparam NUM_TESTS = 9; - - localparam test_config_t test[NUM_TESTS] = '{ - '{CHDR_W: 64, ITEM_W: 16, NIPC: 1, NUM_PORTS: 3, STALL_PROB: 10, STB_PROB: 100, TEST_REGS: 1 }, - '{CHDR_W: 64, ITEM_W: 16, NIPC: 1, NUM_PORTS: 2, STALL_PROB: 25, STB_PROB: 80, TEST_REGS: 1 }, - '{CHDR_W: 64, ITEM_W: 16, NIPC: 2, NUM_PORTS: 1, STALL_PROB: 25, STB_PROB: 80, TEST_REGS: 0 }, - '{CHDR_W: 64, ITEM_W: 32, NIPC: 1, NUM_PORTS: 1, STALL_PROB: 25, STB_PROB: 80, TEST_REGS: 0 }, - '{CHDR_W: 64, ITEM_W: 32, NIPC: 2, NUM_PORTS: 1, STALL_PROB: 10, STB_PROB: 80, TEST_REGS: 0 }, - '{CHDR_W: 128, ITEM_W: 32, NIPC: 1, NUM_PORTS: 3, STALL_PROB: 10, STB_PROB: 100, TEST_REGS: 1 }, - '{CHDR_W: 128, ITEM_W: 32, NIPC: 1, NUM_PORTS: 2, STALL_PROB: 25, STB_PROB: 80, TEST_REGS: 0 }, - '{CHDR_W: 128, ITEM_W: 32, NIPC: 2, NUM_PORTS: 1, STALL_PROB: 25, STB_PROB: 80, TEST_REGS: 0 }, - '{CHDR_W: 128, ITEM_W: 32, NIPC: 4, NUM_PORTS: 1, STALL_PROB: 10, STB_PROB: 80, TEST_REGS: 0 } - }; - - - //--------------------------------------------------------------------------- - // DUT Instances - //--------------------------------------------------------------------------- - - genvar i; - for (i = 0; i < NUM_TESTS; i++) begin : gen_test_config - rfnoc_block_radio_tb #( - .CHDR_W (test[i].CHDR_W ), - .ITEM_W (test[i].ITEM_W ), - .NIPC (test[i].NIPC ), - .NUM_PORTS (test[i].NUM_PORTS ), - .STALL_PROB (test[i].STALL_PROB), - .STB_PROB (test[i].STB_PROB ), - .TEST_REGS (test[i].TEST_REGS ) - ) rfnoc_block_radio_tb_i (); - end : gen_test_config - + rfnoc_block_radio_tb #(.CHDR_W( 64), .ITEM_W(16), .NIPC(1), .NUM_PORTS(3), .STALL_PROB(10), .STB_PROB(100), .TEST_REGS(1)) tb_0 (); + rfnoc_block_radio_tb #(.CHDR_W( 64), .ITEM_W(16), .NIPC(1), .NUM_PORTS(2), .STALL_PROB(25), .STB_PROB( 80), .TEST_REGS(1)) tb_1 (); + rfnoc_block_radio_tb #(.CHDR_W( 64), .ITEM_W(16), .NIPC(2), .NUM_PORTS(1), .STALL_PROB(25), .STB_PROB( 80), .TEST_REGS(0)) tb_2 (); + rfnoc_block_radio_tb #(.CHDR_W( 64), .ITEM_W(32), .NIPC(1), .NUM_PORTS(1), .STALL_PROB(25), .STB_PROB( 80), .TEST_REGS(0)) tb_3 (); + rfnoc_block_radio_tb #(.CHDR_W( 64), .ITEM_W(32), .NIPC(2), .NUM_PORTS(1), .STALL_PROB(10), .STB_PROB( 80), .TEST_REGS(0)) tb_4 (); + rfnoc_block_radio_tb #(.CHDR_W(128), .ITEM_W(32), .NIPC(1), .NUM_PORTS(3), .STALL_PROB(10), .STB_PROB(100), .TEST_REGS(1)) tb_5 (); + rfnoc_block_radio_tb #(.CHDR_W(128), .ITEM_W(32), .NIPC(1), .NUM_PORTS(2), .STALL_PROB(25), .STB_PROB( 80), .TEST_REGS(0)) tb_6 (); + rfnoc_block_radio_tb #(.CHDR_W(128), .ITEM_W(32), .NIPC(2), .NUM_PORTS(1), .STALL_PROB(25), .STB_PROB( 80), .TEST_REGS(0)) tb_7 (); + rfnoc_block_radio_tb #(.CHDR_W(128), .ITEM_W(32), .NIPC(4), .NUM_PORTS(1), .STALL_PROB(10), .STB_PROB( 80), .TEST_REGS(0)) tb_8 (); + rfnoc_block_radio_tb #(.CHDR_W(512), .ITEM_W(32), .NIPC(8), .NUM_PORTS(1), .STALL_PROB(10), .STB_PROB( 80), .TEST_REGS(0)) tb_9 (); endmodule : rfnoc_block_radio_all_tb diff --git a/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_tb.sv b/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_tb.sv index 3e318a1..604797b 100644 --- a/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_tb.sv +++ b/lib/rfnoc/blocks/rfnoc_block_radio/rfnoc_block_radio_tb.sv @@ -44,6 +44,9 @@ module rfnoc_block_radio_tb #( localparam int CTRL_CLK_PER = 25; // rfnoc_ctrl_clk period in ns localparam int RADIO_CLK_PER = 10; // radio_clk_per period in ns + localparam int NUM_TESTS = 2; // Number of times to run each test + localparam bit VERBOSE = 0; // Display lots of output, or not + // Amount of time to wait for a packet to be fully acquired localparam realtime MAX_PKT_WAIT = 4*WPP*(RADIO_CLK_PER+CTRL_CLK_PER)*1ns; @@ -53,6 +56,22 @@ module rfnoc_block_radio_tb #( localparam bit [15:0] TX_ERR_REM_DST_EPID = 16'hA18E; localparam bit [19:0] TX_ERR_ADDRESS = 20'hA31D3; + // Radio latency expected in due to time alignment. There is a fixed amount + // of latency between the radio_time and when the corresponding sample is + // strobed in or out. We need to make sure this latency is constant. The + // actual amount of latency is not critical since there's always an unknown + // but constant amount of latency in the RF front end. + localparam int RADIO_TX_LATENCY = (NIPC > 1) ? 4 : 2; + localparam int RADIO_RX_LATENCY = (NIPC > 1) ? 2 : 0; + + // Calculate an appropriate delay to use for future timed TX/RX tests in + // terms of the radio time ticks. RX takes a lot longer because you have to + // queue a command using a register write. The higher the NIPC, the faster + // the tick rate for the same clock. We want it to be short-ish to reduce + // simulation time. + localparam int TX_CMD_DELAY = 200; + localparam int RX_CMD_DELAY = 500*NIPC; + //--------------------------------------------------------------------------- @@ -70,7 +89,7 @@ module rfnoc_block_radio_tb #( rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst()); sim_clock_gen #(.PERIOD(CTRL_CLK_PER), .AUTOSTART(0)) rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst()); - sim_clock_gen #(.PERIOD(RADIO_CLK_PER), .AUTOSTART(0)) + sim_clock_gen #(.PERIOD(RADIO_CLK_PER), .AUTOSTART(0)) radio_clk_gen (.clk(radio_clk), .rst()); @@ -110,11 +129,10 @@ module rfnoc_block_radio_tb #( // Radio Data Model //--------------------------------------------------------------------------- - bit [NUM_PORTS*RADIO_W-1:0] radio_rx_data; - bit [ NUM_PORTS-1:0] radio_rx_stb; + logic [NUM_PORTS*RADIO_W-1:0] radio_rx_data; + logic [ NUM_PORTS-1:0] radio_rx_stb; - bit [63:0] radio_time; - bit radio_pps; + logic [63:0] radio_time; // Radio data generation sim_radio_gen #( @@ -122,15 +140,13 @@ module rfnoc_block_radio_tb #( .SAMP_W (ITEM_W), .NUM_CHANNELS (NUM_PORTS), .STB_PROB (STB_PROB), - .INCREMENT (NIPC), - .PPS_PERIOD (NIPC * 250) + .INCREMENT (NIPC) ) radio_gen ( .radio_clk (radio_clk), .radio_rst (1'b0), .radio_rx_data (radio_rx_data), .radio_rx_stb (radio_rx_stb), - .radio_time (radio_time), - .radio_pps (radio_pps) + .radio_time (radio_time) ); @@ -275,11 +291,11 @@ module rfnoc_block_radio_tb #( if (num_words == 0) begin // Do a continuous acquisition - $display("Radio %0d: Start RX, continuous receive", radio_num); + if (VERBOSE) $display("Radio %0d: Start RX, continuous receive", radio_num); cmd = RX_CMD_CONTINUOUS; end else begin // Do a finite acquisition (num samps and done) - $display("Radio %0d: Start RX, receive %0d words", radio_num, num_words); + if (VERBOSE) $display("Radio %0d: Start RX, receive %0d words", radio_num, num_words); write_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, num_words); cmd = RX_CMD_FINITE; end @@ -292,18 +308,18 @@ module rfnoc_block_radio_tb #( // Start an Rx acquisition at a specific time task automatic start_rx_timed ( int radio_num, // Radio channel to use - bit [63:0] num_words = 0, // Number of radio words + bit [63:0] num_words = 0, // Number of radio words (0 means continuous) bit [63:0] start_time ); logic [31:0] cmd; if (num_words == 0) begin // Do a continuous acquisition - $display("Radio %0d: Start RX, continuous receive (timed)", radio_num); + if (VERBOSE) $display("Radio %0d: Start RX, continuous receive (timed)", radio_num); cmd = RX_CMD_CONTINUOUS; end else begin // Do a finite acquisition (num samps and done) - $display("Radio %0d: Start RX, receive %0d words (timed)", radio_num, num_words); + if (VERBOSE) $display("Radio %0d: Start RX, receive %0d words (timed)", radio_num, num_words); write_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, num_words); cmd = RX_CMD_FINITE; end @@ -321,12 +337,12 @@ module rfnoc_block_radio_tb #( // Send the Rx stop command to the indicated radio channel task automatic stop_rx(int radio_num); - $display("Radio %0d: Stop RX", radio_num); + if (VERBOSE) $display("Radio %0d: Stop RX", radio_num); write_radio(radio_num, REG_RX_CMD, RX_CMD_STOP); endtask : stop_rx - // Receive num_words from the indicated radio channel and verify that it's + // Receive num_words from the indicated radio channel and verify that it's // sequential and contiguous data aligned on packet boundaries. task automatic check_rx( int radio_num, // Radio to receive from and check @@ -364,7 +380,8 @@ module rfnoc_block_radio_tb #( // Check that the length matches our expectation `ASSERT_ERROR( byte_length == expected_length, - "Received packet didn't have expected length." + $sformatf({"Received packet didn't have expected length.\n", + "Expected 0x%X, Received 0x%X"}, expected_length, byte_length) ); // Loop over the packet, one chdr_word_t at a time @@ -408,7 +425,7 @@ module rfnoc_block_radio_tb #( chdr_word_t chdr_word; // Next word to send to BFM packet_info_t pkt_info = 0; // Flags/timestamp for next packet - $display("Radio %0d: Start TX, send %0d words", radio_num, num_words); + if (VERBOSE) $display("Radio %0d: Start TX, send %0d words", radio_num, num_words); num_samples = num_words * NIPC; @@ -474,34 +491,54 @@ module rfnoc_block_radio_tb #( bit [ITEM_W-1:0] start_val = 1 // Initial sample value ); int sample_val; // Expected value of next sample + bit found = 0; + int offset = 0; + int num_samps; - sample_val = start_val; - - // Wait for the packet to start - wait(radio_tx_data[radio_num*RADIO_W +: ITEM_W] == start_val); + // Wait for the expected packet to start. Look for the start value in any + // sample position. Save the sample offset so we can verify it's correct. + while (!found) begin + @(posedge radio_clk); + if (radio_tx_stb[radio_num]) begin + for (int samp_i = 0; samp_i < NIPC; samp_i++) begin + if (radio_tx_data[radio_num*RADIO_W + samp_i*ITEM_W +: ITEM_W] == start_val) begin + found = 1; + offset = samp_i; + break; + end + end + end + end // Check the time if (!$isunknown(start_time)) begin `ASSERT_ERROR( - radio_time - start_time <= NIPC*2, - $sformatf("Packet transmitted at radio time 0x%0X but expected 0x%0X", radio_time, start_time) + radio_time+offset == start_time + RADIO_TX_LATENCY*NIPC, + $sformatf("First sample transmitted at radio time 0x%0X but expected 0x%0X", + radio_time+offset, start_time + RADIO_TX_LATENCY*NIPC) ); end // Verify output one word at a time - for (int word_count = 0; word_count < num_words; word_count++) begin - // Wait for the next radio word to be output - do begin - @(posedge radio_clk); - end while (radio_tx_stb[radio_num] == 0); + sample_val = start_val; + num_samps = num_words * NIPC; + for (int samp_count = 0; samp_count < num_samps; samp_count++) begin + `ASSERT_ERROR( + radio_tx_data[radio_num*RADIO_W + offset*ITEM_W +: ITEM_W] == sample_val, + $sformatf({"Radio output doesn't match expected value\n", + "Expected 0x%X but found 0x%X at sample %0d (word offset %0d)."}, + sample_val, radio_tx_data[radio_num*RADIO_W + ITEM_W*offset +: ITEM_W], + samp_count, offset) + ); + sample_val++; + offset++; - // Check each sample of the radio word - for (int sub_sample = 0; sub_sample < NIPC; sub_sample++) begin - `ASSERT_ERROR( - radio_tx_data[radio_num*RADIO_W + ITEM_W*sub_sample +: ITEM_W] == sample_val, - "Radio output doesn't match expected value" - ); - sample_val++; + if (offset == NIPC) begin + offset = 0; + // Wait for the next radio word to be output + do begin + @(posedge radio_clk); + end while (!radio_tx_stb[radio_num]); end end endtask : check_tx_timed @@ -545,6 +582,23 @@ module rfnoc_block_radio_tb #( endtask : check_error + // This function is a copy of the one in sim_radio_gen, but due to a Vivado + // 2021.1 bug, we need to copy it here. + typedef sample_t [ NIPC-1:0] radio_t; // Radio output word + typedef radio_t [NUM_PORTS-1:0] data_t; // Radio output for all channels + function radio_t radio_init( + sample_t first_sample = '0 + ); + radio_t ret_val; + + for (int samp_i = 0; samp_i < NIPC; samp_i++) begin + ret_val[samp_i] = first_sample + samp_i; + end + + return ret_val; + endfunction : radio_init + + //--------------------------------------------------------------------------- // Test Procedures @@ -579,13 +633,18 @@ module rfnoc_block_radio_tb #( }, "REG_COMPAT_NUM didn't read correctly" ); - read_shared(REG_TIME_LO, time1[31:0]); - read_shared(REG_TIME_HI, time1[63:32]); - read_shared(REG_TIME_LO, time2[31:0]); - read_shared(REG_TIME_HI, time2[63:32]); + do begin + read_shared(REG_TIME_LO, time1[31:0]); + read_shared(REG_TIME_HI, time1[63:32]); + end while ($isunknown(time1)); + do begin + read_shared(REG_TIME_LO, time2[31:0]); + read_shared(REG_TIME_HI, time2[63:32]); + end while ($isunknown(time2)); `ASSERT_ERROR( time2 > time1, - "Time did not increment in REG_TIME_HI and REG_TIME_LO" + $sformatf({"Time did not increment in REG_TIME_HI and REG_TIME_LO", + "Time1: 0x%X, Time2: 0x%X"}, time1, time2) ); test.end_test(); endtask : test_shared_registers @@ -615,7 +674,7 @@ module rfnoc_block_radio_tb #( task test_rx_registers(int radio_num); - logic [63:0] val, temp, expected; + logic [63:0] val, expected, radio_val_0, radio_val_1; localparam int num_words_len = RX_CMD_NUM_WORDS_LEN; test.start_test("Rx Registers", 50us); @@ -625,7 +684,7 @@ module rfnoc_block_radio_tb #( read_radio(radio_num, REG_RX_STATUS, val); `ASSERT_ERROR(val == expected, "REG_RX_STATUS not initially CMD_FIFO_SPACE_MAX"); - // REG_RX_CMD (read/write). Test a bogus timed stop command just to check + // REG_RX_CMD (read/write). Test a bogus timed stop command just to check // read/write of the register. expected = 0; expected[RX_CMD_POS +: RX_CMD_LEN] = RX_CMD_STOP; @@ -694,13 +753,20 @@ module rfnoc_block_radio_tb #( `ASSERT_ERROR(val == expected, "REG_RX_ERR_ADDR didn't update correctly"); // REG_RX_DATA (read-only) - temp = radio_tx_data[RADIO_W*radio_num +: RADIO_W]; - read_radio(radio_num, REG_RX_DATA, val); + do begin + // Loop until we get a valid sample from the register (not X's) + do @(posedge radio_clk); while (!radio_rx_stb[radio_num]); + radio_val_0 = radio_rx_data[RADIO_W*radio_num +: RADIO_W] & {32{1'b1}}; + read_radio(radio_num, REG_RX_DATA, val); + do @(posedge radio_clk); while (!radio_rx_stb[radio_num]); + radio_val_1 = radio_rx_data[RADIO_W*radio_num +: RADIO_W] & {32{1'b1}}; + end while ($isunknown(val)); `ASSERT_ERROR( - radio_rx_data[RADIO_W*radio_num +: RADIO_W] >= val && val >= temp, - "REG_RX_DATA wasn't in the expected range"); - read_radio(radio_num, REG_RX_DATA, temp); - `ASSERT_ERROR(temp != val, "REG_RX_DATA didn't update"); + radio_val_0 < val && val < radio_val_1, + $sformatf({"REG_RX_DATA wasn't in the expected range\n", + "Radio Value 0: 0x%X, Reg Value: 0x%X, Radio Value 1: 0x%X"}, + radio_val_0, val, radio_val_1) + ); test.end_test(); endtask : test_rx_registers @@ -772,15 +838,15 @@ module rfnoc_block_radio_tb #( task automatic test_rx(int radio_num); + // Set default packet length + write_radio(radio_num, REG_RX_MAX_WORDS_PER_PKT, WPP); + //--------------------- // Finite Acquisitions //--------------------- test.start_test("Rx (finite)", 50us); - // Set packet length - write_radio(radio_num, REG_RX_MAX_WORDS_PER_PKT, WPP); - // Grab and verify a partial packet start_rx(radio_num, WPP/2); check_rx(radio_num, WPP/2); @@ -793,7 +859,7 @@ module rfnoc_block_radio_tb #( start_rx(radio_num, WPP*15/2); check_rx(radio_num, WPP*15/2); - // Wait long enough to receive another packet and then make sure we didn't + // Wait long enough to receive another packet and then make sure we didn't // receive anything. That is, make sure Rx actually stopped. #MAX_PKT_WAIT; `ASSERT_ERROR( @@ -834,19 +900,36 @@ module rfnoc_block_radio_tb #( begin ChdrPacket #(CHDR_W) chdr_packet; - chdr_word_t expected_time; + bit [ITEM_W-1:0] expected_samp; + bit [ 63:0] new_time; + bit [ 63:0] expected_time; test.start_test("Rx (finite, timed)", 100us); + // Set radio time and data so we know which sample value to expect + radio_clk_gen.clk_wait_f(); + new_time = radio_time; + radio_gen.set_time(new_time); + radio_gen.set_data(radio_num, radio_init(new_time)); + radio_clk_gen.clk_wait_f(); + // Send Rx command with time in the future - expected_time = radio_time + 2000; + expected_time = new_time + RX_CMD_DELAY; start_rx_timed(radio_num, WPP, expected_time); // Take a peak at the timestamp in the received packet to check it blk_ctrl.peek_chdr(radio_num, chdr_packet); `ASSERT_ERROR( chdr_packet.timestamp == expected_time, - "Received packet didn't have expected timestamp" + $sformatf({"Received packet didn't have expected timestamp.\n", + "Expected 0x%X, Received 0x%X"}, expected_time, chdr_packet.timestamp) + ); + expected_samp = expected_time[ITEM_W-1:0] - RADIO_RX_LATENCY*NIPC; + `ASSERT_ERROR( + chdr_packet.data[0][0+:ITEM_W] == expected_samp, + $sformatf({"Received packet didn't have expected start value.\n", + "Expected 0x%X, Received 0x%X"}, expected_samp, + chdr_packet.data[0][0+:ITEM_W]) ); // Verify the packet data @@ -861,12 +944,21 @@ module rfnoc_block_radio_tb #( begin ChdrPacket #(CHDR_W) chdr_packet; - chdr_word_t expected_time; + bit [ITEM_W-1:0] expected_samp; + bit [ 63:0] new_time; + bit [ 63:0] expected_time; test.start_test("Rx (continuous, timed)", 100us); + // Set radio time and data so we know which sample value to expect + radio_clk_gen.clk_wait_f(); + new_time = radio_time; + radio_gen.set_time(new_time); + radio_gen.set_data(radio_num, radio_init(new_time)); + radio_clk_gen.clk_wait_f(); + // Send Rx command with time in the future - expected_time = radio_time + 2000; + expected_time = new_time + RX_CMD_DELAY; start_rx_timed(radio_num, 0, expected_time); // Take a peak at the timestamp in the received packet to check it @@ -875,6 +967,13 @@ module rfnoc_block_radio_tb #( chdr_packet.timestamp == expected_time, "Received packet didn't have expected timestamp" ); + expected_samp = expected_time[ITEM_W-1:0] - RADIO_RX_LATENCY*NIPC; + `ASSERT_ERROR( + chdr_packet.data[0][0+:ITEM_W] == expected_samp, + $sformatf({"Received packet didn't have expected start value.\n", + "Expected 0x%X, Received 0x%X"}, expected_samp, + chdr_packet.data[0][0+:ITEM_W]) + ); // Verify a few packets check_rx(radio_num, WPP*3); @@ -893,9 +992,81 @@ module rfnoc_block_radio_tb #( end + //-------------------------- + // RX Sample Time Alignment + //-------------------------- + + if (NIPC > 1) begin + ChdrPacket #(CHDR_W) chdr_packet; + localparam int ALIGN_W = (NIPC > 1) ? $clog2(NIPC) : 1; + localparam int NUM_WORDS = 4; + bit [ALIGN_W-1:0] radio_align; // Radio alignment + bit [ALIGN_W-1:0] req_align; // Request alignment + bit [ 63:0] new_time; + bit [ 63:0] expected_time; + bit [ ITEM_W-1:0] expected_samp; + + test.start_test("Rx (time alignment)", NIPC*NIPC*10us); + + // Iterate over all possible alignments + repeat(2**ALIGN_W) begin + repeat(2**ALIGN_W) begin + if (VERBOSE) $display("Testing Rx alignment radio: %0d, request: %0d", + radio_align, req_align); + + // Set radio alignment and set the data to be the same as the time to + // make it easier to validate. + radio_clk_gen.clk_wait_f(); + new_time = (radio_time & ('1 << ALIGN_W)) | radio_align; + radio_gen.set_time(new_time); + radio_gen.set_data(radio_num, radio_init(new_time)); + radio_clk_gen.clk_wait_f(); + + // Create future time that's aligned for our request + expected_time = new_time + RX_CMD_DELAY; + expected_time = (expected_time & ('1 << ALIGN_W)) | req_align; + + // Send Rx command with time in the future + start_rx_timed(radio_num, NUM_WORDS, expected_time); + blk_ctrl.peek_chdr(radio_num, chdr_packet); + + // Check the timestamp in the received packet + `ASSERT_ERROR( + chdr_packet.timestamp == expected_time, + $sformatf({"Received packet didn't have expected timestamp.\n", + "Expected 0x%X, Received 0x%X"}, expected_time, + chdr_packet.timestamp) + ); + // Check the first sample to make sure it matches the sample for the + // requested time. + expected_samp = expected_time[ITEM_W-1:0] - RADIO_RX_LATENCY*NIPC; + `ASSERT_ERROR( + chdr_packet.data[0][0+:ITEM_W] == expected_samp, + $sformatf({"Received packet didn't have expected start value.\n", + "Expected 0x%X, Received 0x%X"}, expected_samp, + chdr_packet.data[0][0+:ITEM_W]) + ); + + // Verify the rest of the packet + check_rx(radio_num, NUM_WORDS); + req_align++; + end + radio_align++; + end + + // Reset the radio time and outputs + radio_gen.set_time(0); + radio_gen.set_data_all(radio_gen.radio_init_all(0)); + radio_clk_gen.clk_wait_r(2); + + test.end_test(); + end + + //------------- // Rx Overflow //------------- + begin logic [31:0] val; @@ -933,7 +1104,7 @@ module rfnoc_block_radio_tb #( "Rx radio reports that it is still busy after overflow" ); - // Discard any packets we received. Rx should eventually stop + // Discard any packets we received. Rx should eventually stop // automatically after an overflow. do begin while (blk_ctrl.num_received(radio_num) != 0) begin @@ -986,7 +1157,7 @@ module rfnoc_block_radio_tb #( expected = CMD_FIFO_SPACE_MAX-1; read_radio(radio_num, REG_RX_STATUS, val); `ASSERT_ERROR( - val[CMD_FIFO_SPACE_POS+:CMD_FIFO_SPACE_LEN] == expected, + val[CMD_FIFO_SPACE_POS+:CMD_FIFO_SPACE_LEN] == expected, "CMD_FIFO_SPACE did not decrement" ); @@ -997,7 +1168,7 @@ module rfnoc_block_radio_tb #( expected = 0; read_radio(radio_num, REG_RX_STATUS, val); `ASSERT_ERROR( - val[CMD_FIFO_SPACE_POS+:CMD_FIFO_SPACE_LEN] == expected, + val[CMD_FIFO_SPACE_POS+:CMD_FIFO_SPACE_LEN] == expected, "CMD_FIFO_SPACE did not reach 0" ); @@ -1006,7 +1177,7 @@ module rfnoc_block_radio_tb #( expected = CMD_FIFO_SPACE_MAX; read_radio(radio_num, REG_RX_STATUS, val); `ASSERT_ERROR( - val[CMD_FIFO_SPACE_POS+:CMD_FIFO_SPACE_LEN] == expected, + val[CMD_FIFO_SPACE_POS+:CMD_FIFO_SPACE_LEN] == expected, "CMD_FIFO_SPACE did not return to max" ); @@ -1019,8 +1190,8 @@ module rfnoc_block_radio_tb #( #MAX_PKT_WAIT; end while (blk_ctrl.num_received(radio_num) != 0); - // Queue several long commands back-to-back and make sure they all - // complete. The lengths are unique to ensure we execute the right + // Queue several long commands back-to-back and make sure they all + // complete. The lengths are unique to ensure we execute the right // commands in the expected order. for (int i = 0; i < 3; i++) start_rx(radio_num, WPP*20+i); for (int i = 0; i < 3; i++) check_rx(radio_num, WPP*20+i); @@ -1089,6 +1260,8 @@ module rfnoc_block_radio_tb #( test.start_test("Tx (now, underflow)", 50us); + write_radio(radio_num, REG_TX_ERROR_POLICY, TX_ERR_POLICY_PACKET); + // Send some bursts without EOB start_tx(radio_num, WPP*3/4, 1, 0); // Skip EOB check_tx(radio_num, WPP*3/4); @@ -1108,36 +1281,88 @@ module rfnoc_block_radio_tb #( test.start_test("Tx (timed)", 50us); // Grab and verify a partial packet - start_tx_timed(radio_num, WPP*3/4, radio_time + 200); - check_tx_timed(radio_num, WPP*3/4, radio_time + 200); + start_tx_timed(radio_num, WPP*3/4, radio_time + TX_CMD_DELAY); + check_tx_timed(radio_num, WPP*3/4, radio_time + TX_CMD_DELAY); check_error(ERR_TX_EOB_ACK); // Grab and verify whole packets - start_tx_timed(radio_num, WPP*2, radio_time + 200); - check_tx_timed(radio_num, WPP*2, radio_time + 200); + start_tx_timed(radio_num, WPP*2, radio_time + TX_CMD_DELAY); + check_tx_timed(radio_num, WPP*2, radio_time + TX_CMD_DELAY); check_error(ERR_TX_EOB_ACK); test.end_test(); - //----------------- + //---------------------------- // Test Tx (timed, underflow) - //----------------- + //---------------------------- test.start_test("Tx (timed, underflow)", 50us); // Send some bursts without EOB - start_tx_timed(radio_num, WPP*3/4, radio_time + 200, 1, 0); - check_tx_timed(radio_num, WPP*3/4, radio_time + 200); + start_tx_timed(radio_num, WPP*3/4, radio_time + TX_CMD_DELAY, 1, 0); + check_tx_timed(radio_num, WPP*3/4, radio_time + TX_CMD_DELAY); check_error(ERR_TX_UNDERRUN); - start_tx_timed(radio_num, WPP*2, radio_time + 200, 1, 0); - check_tx_timed(radio_num, WPP*2, radio_time + 200); + start_tx_timed(radio_num, WPP*2, radio_time + TX_CMD_DELAY, 1, 0); + check_tx_timed(radio_num, WPP*2, radio_time + TX_CMD_DELAY); check_error(ERR_TX_UNDERRUN); test.end_test(); + //------------------------------- + // Test Tx Sample Time Alignment + //------------------------------- + + if (NIPC > 1) begin + ChdrPacket #(CHDR_W) chdr_packet; + localparam int ALIGN_W = (NIPC > 1) ? $clog2(NIPC) : 1; + localparam int NUM_WORDS = 4; + bit [ALIGN_W-1:0] radio_align; // Radio alignment + bit [ALIGN_W-1:0] req_align; // Request alignment + bit [ 63:0] new_time; + bit [ 63:0] expected_time; + bit [ ITEM_W-1:0] expected_samp; + + + test.start_test("Tx (time alignment)", 200us); + + // Iterate over all possible alignments + repeat(2**ALIGN_W) begin + repeat(2**ALIGN_W) begin + if (VERBOSE) $display("Testing Tx alignment radio: %0d, request: %0d", + radio_align, req_align); + + // Set radio alignment + radio_clk_gen.clk_wait_f(); + new_time = (radio_time & ('1 << ALIGN_W)) | radio_align; + radio_gen.set_time(new_time); + radio_clk_gen.clk_wait_f(); + + // Create future time that's aligned for our request + expected_time = new_time + TX_CMD_DELAY; + expected_time = (expected_time & ('1 << ALIGN_W)) | req_align; + + // Transmit and verify the output + start_tx_timed(radio_num, NUM_WORDS, expected_time); + check_tx_timed(radio_num, NUM_WORDS, expected_time); + check_error(ERR_TX_EOB_ACK); + + req_align++; + end + radio_align++; + end + + // Reset the radio time and outputs + radio_gen.set_time(0); + radio_gen.set_data_all(radio_gen.radio_init_all(0)); + radio_clk_gen.clk_wait_r(2); + + test.end_test(); + end + + //--------------------------- // Test Tx (timed, late) //--------------------------- @@ -1154,9 +1379,8 @@ module rfnoc_block_radio_tb #( write_radio(radio_num, REG_TX_ERROR_POLICY, TX_ERR_POLICY_BURST); end -// Commenting out the fork code for now due to Vivado 2018.3 bug. -// radio_data = radio_tx_data[radio_num]; -// fork : tx_fork + radio_data = radio_tx_data[radio_num]; + fork : tx_fork // In this branch of the fork, we send the packets repeat (2) begin // Send late packets with random start value @@ -1171,29 +1395,31 @@ module rfnoc_block_radio_tb #( end end -// // The packets sent in the above branch of the fork should be -// // dropped. In this branch of the fork we make sure that the Tx -// // output doesn't change. -// begin -// forever begin -// @(posedge radio_clk) -// `ASSERT_ERROR( -// radio_data === radio_tx_data[radio_num], -// "Radio Tx output changed when late Tx packet should have been ignored" -// ); -// end -// end -// join_any -// -// // Stop checking the output -// disable tx_fork; + // The packets sent in the above branch of the fork should be + // dropped. In this branch of the fork we make sure that the Tx + // output doesn't change. + begin + forever begin + @(posedge radio_clk) + if (radio_tx_stb[radio_num]) begin + `ASSERT_ERROR( + radio_data === radio_tx_data[radio_num], + "Radio Tx output changed when late Tx packet should have been ignored" + ); + end + end + end + join_any + + // Stop checking the output + disable tx_fork; policy = policy.next(); end while (policy != policy.first()); // Make sure good transmissions can go through now. - start_tx_timed(radio_num, WPP, radio_time + 200); - check_tx_timed(radio_num, WPP, radio_time + 200); + start_tx_timed(radio_num, WPP, radio_time + TX_CMD_DELAY); + check_tx_timed(radio_num, WPP, radio_time + TX_CMD_DELAY); check_error(ERR_TX_EOB_ACK); test.end_test(); @@ -1217,8 +1443,8 @@ module rfnoc_block_radio_tb #( // Turn on loopback write_radio(radio_num, REG_LOOPBACK_EN, 1); - // This test ensures we get the Tx output on Rx and not the TB's simulated - // radio data. It also tests updating the idle value. Run the test twice to + // This test ensures we get the Tx output on Rx and not the TB's simulated + // radio data. It also tests updating the idle value. Run the test twice to // make sure the IDLE value updates. repeat (2) begin // Set idle value @@ -1226,12 +1452,12 @@ module rfnoc_block_radio_tb #( write_radio(radio_num, REG_TX_IDLE_VALUE, idle); // Grab a radio word and check that it equals the IDLE value - write_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, 1); + write_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, WPP); write_radio(radio_num, REG_RX_CMD, RX_CMD_FINITE); blk_ctrl.recv(radio_num, data, byte_length); // Check the length - `ASSERT_ERROR(byte_length == RADIO_W/8, "Didn't receive expected length"); + `ASSERT_ERROR(byte_length == WPP*RADIO_W/8, "Didn't receive expected length"); // Check the payload foreach (data[i]) begin @@ -1265,12 +1491,12 @@ module rfnoc_block_radio_tb #( // Set packet length write_radio(radio_num, REG_RX_MAX_WORDS_PER_PKT, WPP); - // Loopback a few packets, back-to-back. This code has a race condition - // since there's a delay between when we start Tx and when Rx starts, due - // to how long it takes to write the Rx registers. Therefore, we transmit a - // lot more packets than we receive to ensure we're still transmitting by + // Loopback a few packets, back-to-back. This code has a race condition + // since there's a delay between when we start Tx and when Rx starts, due + // to how long it takes to write the Rx registers. Therefore, we transmit a + // lot more packets than we receive to ensure we're still transmitting by // the time we receive. - start_tx(radio_num, WPP*16); + start_tx(radio_num, WPP*20); start_rx(radio_num, WPP*2); // Check the results @@ -1300,11 +1526,12 @@ module rfnoc_block_radio_tb #( // Generate a string for the name of this instance of the testbench tb_name = $sformatf( - "rfnoc_block_radio_tb\nCHDR_W = %0D, ITEM_W = %0D, NIPC = %0D, NUM_PORTS = %0D, STALL_PROB = %0D, STB_PROB = %0D, TEST_REGS = %0D", + {"rfnoc_block_radio_tb\nCHDR_W = %0D, ITEM_W = %0D, NIPC = %0D, ", + "NUM_PORTS = %0D, STALL_PROB = %0D, STB_PROB = %0D, TEST_REGS = %0D"}, CHDR_W, ITEM_W, NIPC, NUM_PORTS, STALL_PROB, STB_PROB, TEST_REGS ); - test.start_tb(tb_name); + test.start_tb(tb_name, NUM_TESTS*5ms); // Don't start the clocks until after start_tb() returns. This ensures that // the clocks aren't toggling while other instances of this testbench are @@ -1330,7 +1557,7 @@ module rfnoc_block_radio_tb #( // Test Sequences //------------------------------------------------------------------------- - // Run register tests first, since they check that initial values are + // Run register tests first, since they check that initial values are // correct. test_block_info(); @@ -1345,9 +1572,11 @@ module rfnoc_block_radio_tb #( test_rx_registers(radio_num); test_tx_registers(radio_num); end - test_rx(radio_num); - test_tx(radio_num); - test_loopback_and_idle(radio_num); + repeat (NUM_TESTS) begin + test_rx(radio_num); + test_tx(radio_num); + test_loopback_and_idle(radio_num); + end end @@ -1355,7 +1584,7 @@ module rfnoc_block_radio_tb #( // Finish //------------------------------------------------------------------------- - // End the TB, but don't $finish, since we don't want to kill other + // End the TB, but don't $finish, since we don't want to kill other // instances of this testbench that may be running. test.end_tb(0);