fpga: rfnoc: Add timed sample alignment to radio

This adds time alignment to the radio, so that the first transmitted or
received sample gets shifted into the position corresponding to its
timestamp. The applies only to configurations in which SPC > 1.

For example, if the radio time is incrementing by 4 samples per cycle
(SPC = 4) starting at time 0, and a packet is supposed to be
transmitted at time 201, then the data must be left-shifted by one
sample (201 % 4 = 1), so that the first sample of the packet correctly
aligns with the radio sample corresponding to time 201. Things get more
complicated if the radio time is not a multiple of the SPC (i.e., the
radio time started at time 1, 2, or 3 instead of 0).


Original-commit: 9afa6be7b270047cb03cbd5a16482e5d19edc3dd
This commit is contained in:
Wade Fife
2022-12-19 08:57:18 -06:00
parent a40550497a
commit e81f89128f
4 changed files with 632 additions and 239 deletions
@@ -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;
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;
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_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,7 +529,6 @@ module radio_rx_core #(
error_code <= ERR_RX_OVERRUN;
state <= ST_REPORT_ERR;
end
end
end
@@ -514,7 +605,6 @@ module radio_rx_core #(
end
end
endmodule
@@ -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;
// 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 <= (radio_time == s_axis_ttimestamp);
time_past <= (radio_time > s_axis_ttimestamp);
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
// 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,10 +362,28 @@ module radio_tx_core #(
new_error_code <= ERR_TX_EOB_ACK;
new_error_time <= radio_time;
new_error_valid <= 1'b1;
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
@@ -299,7 +411,7 @@ 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) ?
assign unaligned_data = (s_axis_tvalid && state == ST_TRANSMIT) ?
s_axis_tdata :
{NSPC{reg_idle_value[SAMP_W-1:0]}};
@@ -12,57 +12,19 @@
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
@@ -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;
//---------------------------------------------------------------------------
@@ -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,7 +337,7 @@ 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
@@ -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
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++;
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] == 0);
// 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++;
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"
);
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);
@@ -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];
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);
@@ -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;
@@ -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();
@@ -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
@@ -1270,7 +1496,7 @@ module rfnoc_block_radio_tb #(
// 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
@@ -1345,10 +1572,12 @@ module rfnoc_block_radio_tb #(
test_rx_registers(radio_num);
test_tx_registers(radio_num);
end
repeat (NUM_TESTS) begin
test_rx(radio_num);
test_tx(radio_num);
test_loopback_and_idle(radio_num);
end
end
//-------------------------------------------------------------------------