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:
@@ -7,19 +7,19 @@
|
|||||||
//
|
//
|
||||||
// Description:
|
// Description:
|
||||||
//
|
//
|
||||||
// This module contains the core Rx radio acquisition logic. It retrieves
|
// This module contains the core Rx radio acquisition logic. It retrieves
|
||||||
// sample data from the radio interface, as indicated by the radio's strobe
|
// sample data from the radio interface, as indicated by the radio's strobe
|
||||||
// signal, and outputs the data via AXI-Stream.
|
// signal, and outputs the data via AXI-Stream.
|
||||||
//
|
//
|
||||||
// The receiver is operated by writing a time (optionally) to the
|
// 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_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_NUM_WORDS_* registers followed by writing a command word to
|
||||||
// REG_RX_CMD. The command word indicates whether it is a finite ("num samps
|
// 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
|
// 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
|
// 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.
|
// 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.
|
// initialized prior to the first acquisition.
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
@@ -207,7 +207,7 @@ module radio_rx_core #(
|
|||||||
if (s_ctrlport_req_rd) begin
|
if (s_ctrlport_req_rd) begin
|
||||||
case (s_ctrlport_req_addr)
|
case (s_ctrlport_req_addr)
|
||||||
REG_RX_STATUS: begin
|
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];
|
<= cmd_fifo_space[CMD_FIFO_SPACE_LEN-1:0];
|
||||||
s_ctrlport_resp_ack <= 1;
|
s_ctrlport_resp_ack <= 1;
|
||||||
end
|
end
|
||||||
@@ -285,7 +285,8 @@ module radio_rx_core #(
|
|||||||
.clk (radio_clk),
|
.clk (radio_clk),
|
||||||
.reset (radio_rst),
|
.reset (radio_rst),
|
||||||
.clear (clear_fifo),
|
.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_tvalid (reg_cmd_valid),
|
||||||
.i_tready (),
|
.i_tready (),
|
||||||
.o_tdata ({ cmd_time, cmd_timed, cmd_num_words, cmd_continuous }),
|
.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
|
// Receiver State Machine
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
@@ -325,8 +365,12 @@ module radio_rx_core #(
|
|||||||
reg out_fifo_teob;
|
reg out_fifo_teob;
|
||||||
reg out_fifo_almost_full;
|
reg out_fifo_almost_full;
|
||||||
|
|
||||||
reg [63:0] radio_time_low_samp, radio_time_hi_samp;
|
reg time_now; // Indicates when we've reached the requested timestamp
|
||||||
reg time_now, time_past;
|
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
|
// All ctrlport requests have a time
|
||||||
assign m_ctrlport_req_has_time = 1'b1;
|
assign m_ctrlport_req_has_time = 1'b1;
|
||||||
@@ -342,36 +386,80 @@ module radio_rx_core #(
|
|||||||
seq_num <= 'd0;
|
seq_num <= 'd0;
|
||||||
m_ctrlport_req_wr <= 1'b0;
|
m_ctrlport_req_wr <= 1'b0;
|
||||||
first_word <= 1'b1;
|
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
|
end else begin
|
||||||
// Default assignments
|
// Default assignments
|
||||||
out_fifo_tvalid <= 1'b0;
|
out_fifo_tvalid <= 1'b0;
|
||||||
out_fifo_tlast <= 1'b0;
|
out_fifo_tlast <= 1'b0;
|
||||||
out_fifo_teob <= 1'b0;
|
out_fifo_teob <= 1'b0;
|
||||||
m_ctrlport_req_wr <= 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
|
if (radio_rx_stb) begin
|
||||||
// Get the time for the low sample and the high sample of the radio
|
// Register time comparisons so they don't become the critical path.
|
||||||
// word (needed when NISPC > 1). Compensate for the delay required to
|
// Add two to compensate for the pipeline delays of this comparison and
|
||||||
// check the time by adding 3 clock cycles worth of samples.
|
// its propagation through the state machine. This ensures that the
|
||||||
radio_time_low_samp <= (radio_time + 3*NSPC);
|
// timestamp in the packet matches the requested timestamp.
|
||||||
radio_time_hi_samp <= (radio_time + 3*NSPC + (NSPC-1));
|
time_now <= (radio_time[63:SHIFT_W]+2 == cmd_time[63:SHIFT_W]);
|
||||||
|
time_now_p1 <= time_now;
|
||||||
// Register the time comparisons so they don't become the critical path
|
time_past <= (radio_time[63:SHIFT_W] >= cmd_time[63:SHIFT_W]);
|
||||||
time_now <= (cmd_time >= radio_time_low_samp &&
|
|
||||||
cmd_time <= radio_time_hi_samp);
|
|
||||||
time_past <= (cmd_time < radio_time_low_samp);
|
|
||||||
end
|
end
|
||||||
|
|
||||||
case (state)
|
case (state)
|
||||||
ST_IDLE : begin
|
ST_IDLE : begin
|
||||||
// Wait for a new command to arrive and allow a cycle for the time
|
// Wait for a new command to arrive and a radio strobe to update the
|
||||||
// comparisons to update.
|
// time comparisons.
|
||||||
if (cmd_valid && radio_rx_stb) begin
|
if (cmd_valid && radio_rx_stb) begin
|
||||||
state <= ST_TIME_CHECK;
|
state <= ST_TIME_CHECK;
|
||||||
end else if (cmd_stop) begin
|
end else if (cmd_stop) begin
|
||||||
state <= ST_STOP;
|
state <= ST_STOP;
|
||||||
end
|
end
|
||||||
first_word <= 1'b1;
|
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
|
end
|
||||||
|
|
||||||
ST_TIME_CHECK : begin
|
ST_TIME_CHECK : begin
|
||||||
@@ -386,22 +474,26 @@ module radio_rx_core #(
|
|||||||
error_code <= ERR_RX_LATE_CMD;
|
error_code <= ERR_RX_LATE_CMD;
|
||||||
error_time <= radio_time;
|
error_time <= radio_time;
|
||||||
state <= ST_REPORT_ERR;
|
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
|
// Either it's time to run this command or it should run
|
||||||
// immediately.
|
// immediately.
|
||||||
words_left <= cmd_num_words;
|
state <= ST_RUNNING;
|
||||||
words_left_pkt <= reg_max_pkt_len;
|
|
||||||
state <= ST_RUNNING;
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
words_left <= cmd_num_words;
|
||||||
|
words_left_pkt <= reg_max_pkt_len;
|
||||||
end
|
end
|
||||||
|
|
||||||
ST_RUNNING : begin
|
ST_RUNNING : begin
|
||||||
if (radio_rx_stb) begin
|
if (radio_rx_stb) begin
|
||||||
// Output the next word
|
// Output the next word
|
||||||
out_fifo_tvalid <= 1'b1;
|
out_fifo_tvalid <= 1'b1;
|
||||||
out_fifo_tdata <= radio_rx_data;
|
out_fifo_tdata <= aligned_data;
|
||||||
if (first_word) begin
|
if (first_word) begin
|
||||||
out_fifo_timestamp <= radio_time;
|
out_fifo_timestamp <= radio_time - time_shift;
|
||||||
first_word <= 1'b0;
|
first_word <= 1'b0;
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -437,12 +529,11 @@ module radio_rx_core #(
|
|||||||
error_code <= ERR_RX_OVERRUN;
|
error_code <= ERR_RX_OVERRUN;
|
||||||
state <= ST_REPORT_ERR;
|
state <= ST_REPORT_ERR;
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ST_STOP : begin
|
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.
|
// and for the command FIFO to be popped.
|
||||||
state <= ST_IDLE;
|
state <= ST_IDLE;
|
||||||
end
|
end
|
||||||
@@ -472,7 +563,7 @@ module radio_rx_core #(
|
|||||||
|
|
||||||
assign radio_rx_running = (state == ST_RUNNING); // We're actively acquiring
|
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.
|
// are only used for error reporting.
|
||||||
assign m_ctrlport_req_portid = reg_error_portid;
|
assign m_ctrlport_req_portid = reg_error_portid;
|
||||||
assign m_ctrlport_req_rem_epid = reg_error_rem_epid;
|
assign m_ctrlport_req_rem_epid = reg_error_rem_epid;
|
||||||
@@ -514,7 +605,6 @@ module radio_rx_core #(
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,20 +7,20 @@
|
|||||||
//
|
//
|
||||||
// Description:
|
// Description:
|
||||||
//
|
//
|
||||||
// This module contains the core Tx radio data-path logic. It receives samples
|
// 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
|
// over AXI-Stream that it then sends to the radio interface coincident with a
|
||||||
// strobe signal that must be provided by the radio interface.
|
// strobe signal that must be provided by the radio interface.
|
||||||
//
|
//
|
||||||
// There are no registers for starting or stopping the transmitter. It is
|
// There are no registers for starting or stopping the transmitter. It is
|
||||||
// operated simply by providing data packets via its AXI-Stream data interface.
|
// 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
|
// 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
|
// allowed to stop transmitting. Packet timestamps can be used to indicate when
|
||||||
// transmission should start.
|
// transmission should start.
|
||||||
//
|
//
|
||||||
// Care must be taken to provide data to the transmitter at a rate that is
|
// 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,
|
// 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
|
// 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
|
// arrives late, then it will be dropped and the error will be reported via the
|
||||||
// CTRL port interface.
|
// CTRL port interface.
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
@@ -187,6 +187,44 @@ module radio_tx_core #(
|
|||||||
end
|
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
|
// Transmitter State Machine
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
@@ -195,9 +233,11 @@ module radio_tx_core #(
|
|||||||
localparam ST_IDLE = 0;
|
localparam ST_IDLE = 0;
|
||||||
localparam ST_TIME_CHECK = 1;
|
localparam ST_TIME_CHECK = 1;
|
||||||
localparam ST_TRANSMIT = 2;
|
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
|
reg sop = 1'b1; // Start of packet
|
||||||
|
|
||||||
@@ -205,20 +245,50 @@ module radio_tx_core #(
|
|||||||
reg [ 63:0] new_error_time;
|
reg [ 63:0] new_error_time;
|
||||||
reg new_error_valid = 1'b0;
|
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
|
always @(posedge radio_clk) begin
|
||||||
if (radio_rst) begin
|
if (radio_rst) begin
|
||||||
state <= ST_IDLE;
|
state <= ST_IDLE;
|
||||||
sop <= 1'b1;
|
sop <= 1'b1;
|
||||||
new_error_valid <= 1'b0;
|
new_error_valid <= 1'b0;
|
||||||
end else begin
|
time_shift <= 0;
|
||||||
new_error_valid <= 1'b0;
|
align_cfg_en <= 1'b0;
|
||||||
|
|
||||||
// Register time comparisons so they don't become the critical path
|
// Registers for which we don't care if they have a reset or not because
|
||||||
time_now <= (radio_time == s_axis_ttimestamp);
|
// they're set during state machine execution.
|
||||||
time_past <= (radio_time > s_axis_ttimestamp);
|
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)
|
// Track if the next word will be the start of a packet (sop)
|
||||||
if (s_axis_tvalid && s_axis_tready) begin
|
if (s_axis_tvalid && s_axis_tready) begin
|
||||||
@@ -227,15 +297,39 @@ module radio_tx_core #(
|
|||||||
|
|
||||||
case (state)
|
case (state)
|
||||||
ST_IDLE : begin
|
ST_IDLE : begin
|
||||||
// Wait for a new packet to arrive and allow a cycle for the time
|
// Wait for a new packet to arrive and a radio strobe to update the
|
||||||
// comparisons to update.
|
// time comparisons.
|
||||||
if (s_axis_tvalid) begin
|
if (s_axis_tvalid && radio_tx_stb) begin
|
||||||
state <= ST_TIME_CHECK;
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
ST_TIME_CHECK : begin
|
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
|
// We have a new packet without a timestamp, or a new packet
|
||||||
// whose time has arrived.
|
// whose time has arrived.
|
||||||
state <= ST_TRANSMIT;
|
state <= ST_TRANSMIT;
|
||||||
@@ -268,11 +362,29 @@ module radio_tx_core #(
|
|||||||
new_error_code <= ERR_TX_EOB_ACK;
|
new_error_code <= ERR_TX_EOB_ACK;
|
||||||
new_error_time <= radio_time;
|
new_error_time <= radio_time;
|
||||||
new_error_valid <= 1'b1;
|
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
|
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
|
ST_POLICY_WAIT : begin
|
||||||
// If we came here from ST_TIME_CHECK or ST_TRANSMIT and we're in the
|
// 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
|
// 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
|
// Output the current sample whenever we're transmitting and the sample is
|
||||||
// valid. Otherwise, output the idle value.
|
// 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 :
|
s_axis_tdata :
|
||||||
{NSPC{reg_idle_value[SAMP_W-1:0]}};
|
{NSPC{reg_idle_value[SAMP_W-1:0]}};
|
||||||
|
|
||||||
// Read packet in the transmit state or dump it in the error state
|
// Read packet in the transmit state or dump it in the error state
|
||||||
assign s_axis_tready = (radio_tx_stb && (state == ST_TRANSMIT)) ||
|
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_IDLE = 0;
|
||||||
localparam ST_ERR_CODE = 1;
|
localparam ST_ERR_CODE = 1;
|
||||||
|
|
||||||
reg [0:0] err_state = ST_ERR_IDLE;
|
reg [0:0] err_state = ST_ERR_IDLE;
|
||||||
|
|
||||||
@@ -378,19 +490,19 @@ module radio_tx_core #(
|
|||||||
err_state <= ST_ERR_IDLE;
|
err_state <= ST_ERR_IDLE;
|
||||||
next_error_ready <= 1'b0;
|
next_error_ready <= 1'b0;
|
||||||
end else begin
|
end else begin
|
||||||
m_ctrlport_req_wr <= 1'b0;
|
m_ctrlport_req_wr <= 1'b0;
|
||||||
next_error_ready <= 1'b0;
|
next_error_ready <= 1'b0;
|
||||||
|
|
||||||
case (err_state)
|
case (err_state)
|
||||||
ST_ERR_IDLE : begin
|
ST_ERR_IDLE : begin
|
||||||
if (next_error_valid) begin
|
if (next_error_valid) begin
|
||||||
// Setup write of error code
|
// Setup write of error code
|
||||||
m_ctrlport_req_wr <= 1'b1;
|
m_ctrlport_req_wr <= 1'b1;
|
||||||
m_ctrlport_req_addr <= reg_error_addr;
|
m_ctrlport_req_addr <= reg_error_addr;
|
||||||
m_ctrlport_req_data <= {{(32-ERR_TX_CODE_W){1'b0}}, next_error_code};
|
m_ctrlport_req_data <= {{(32-ERR_TX_CODE_W){1'b0}}, next_error_code};
|
||||||
m_ctrlport_req_time <= next_error_time;
|
m_ctrlport_req_time <= next_error_time;
|
||||||
next_error_ready <= 1'b1;
|
next_error_ready <= 1'b1;
|
||||||
err_state <= ST_ERR_CODE;
|
err_state <= ST_ERR_CODE;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -407,7 +519,7 @@ module radio_tx_core #(
|
|||||||
end
|
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.
|
// only used for error reporting.
|
||||||
assign m_ctrlport_req_portid = reg_error_portid;
|
assign m_ctrlport_req_portid = reg_error_portid;
|
||||||
assign m_ctrlport_req_rem_epid = reg_error_rem_epid;
|
assign m_ctrlport_req_rem_epid = reg_error_rem_epid;
|
||||||
|
|||||||
@@ -5,64 +5,26 @@
|
|||||||
//
|
//
|
||||||
// Module: rfnoc_block_radio_all_tb
|
// 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.
|
// several variations of rfnoc_block_radio_tb to test different configurations.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
module rfnoc_block_radio_all_tb;
|
module rfnoc_block_radio_all_tb;
|
||||||
|
|
||||||
timeunit 1ns;
|
|
||||||
timeprecision 1ps;
|
|
||||||
|
|
||||||
import PkgTestExec::*;
|
|
||||||
|
|
||||||
|
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
// Test Definitions
|
// Test Configurations
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
typedef struct {
|
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 ();
|
||||||
int CHDR_W;
|
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 ();
|
||||||
int ITEM_W;
|
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 ();
|
||||||
int NIPC;
|
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 ();
|
||||||
int NUM_PORTS;
|
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 ();
|
||||||
int STALL_PROB;
|
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 ();
|
||||||
int STB_PROB;
|
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 ();
|
||||||
bit TEST_REGS;
|
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 ();
|
||||||
} test_config_t;
|
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 ();
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
endmodule : rfnoc_block_radio_all_tb
|
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 CTRL_CLK_PER = 25; // rfnoc_ctrl_clk period in ns
|
||||||
localparam int RADIO_CLK_PER = 10; // radio_clk_per 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
|
// 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;
|
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 [15:0] TX_ERR_REM_DST_EPID = 16'hA18E;
|
||||||
localparam bit [19:0] TX_ERR_ADDRESS = 20'hA31D3;
|
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());
|
rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst());
|
||||||
sim_clock_gen #(.PERIOD(CTRL_CLK_PER), .AUTOSTART(0))
|
sim_clock_gen #(.PERIOD(CTRL_CLK_PER), .AUTOSTART(0))
|
||||||
rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst());
|
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());
|
radio_clk_gen (.clk(radio_clk), .rst());
|
||||||
|
|
||||||
|
|
||||||
@@ -110,11 +129,10 @@ module rfnoc_block_radio_tb #(
|
|||||||
// Radio Data Model
|
// Radio Data Model
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
bit [NUM_PORTS*RADIO_W-1:0] radio_rx_data;
|
logic [NUM_PORTS*RADIO_W-1:0] radio_rx_data;
|
||||||
bit [ NUM_PORTS-1:0] radio_rx_stb;
|
logic [ NUM_PORTS-1:0] radio_rx_stb;
|
||||||
|
|
||||||
bit [63:0] radio_time;
|
logic [63:0] radio_time;
|
||||||
bit radio_pps;
|
|
||||||
|
|
||||||
// Radio data generation
|
// Radio data generation
|
||||||
sim_radio_gen #(
|
sim_radio_gen #(
|
||||||
@@ -122,15 +140,13 @@ module rfnoc_block_radio_tb #(
|
|||||||
.SAMP_W (ITEM_W),
|
.SAMP_W (ITEM_W),
|
||||||
.NUM_CHANNELS (NUM_PORTS),
|
.NUM_CHANNELS (NUM_PORTS),
|
||||||
.STB_PROB (STB_PROB),
|
.STB_PROB (STB_PROB),
|
||||||
.INCREMENT (NIPC),
|
.INCREMENT (NIPC)
|
||||||
.PPS_PERIOD (NIPC * 250)
|
|
||||||
) radio_gen (
|
) radio_gen (
|
||||||
.radio_clk (radio_clk),
|
.radio_clk (radio_clk),
|
||||||
.radio_rst (1'b0),
|
.radio_rst (1'b0),
|
||||||
.radio_rx_data (radio_rx_data),
|
.radio_rx_data (radio_rx_data),
|
||||||
.radio_rx_stb (radio_rx_stb),
|
.radio_rx_stb (radio_rx_stb),
|
||||||
.radio_time (radio_time),
|
.radio_time (radio_time)
|
||||||
.radio_pps (radio_pps)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
@@ -275,11 +291,11 @@ module rfnoc_block_radio_tb #(
|
|||||||
|
|
||||||
if (num_words == 0) begin
|
if (num_words == 0) begin
|
||||||
// Do a continuous acquisition
|
// 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;
|
cmd = RX_CMD_CONTINUOUS;
|
||||||
end else begin
|
end else begin
|
||||||
// Do a finite acquisition (num samps and done)
|
// 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);
|
write_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, num_words);
|
||||||
cmd = RX_CMD_FINITE;
|
cmd = RX_CMD_FINITE;
|
||||||
end
|
end
|
||||||
@@ -292,18 +308,18 @@ module rfnoc_block_radio_tb #(
|
|||||||
// Start an Rx acquisition at a specific time
|
// Start an Rx acquisition at a specific time
|
||||||
task automatic start_rx_timed (
|
task automatic start_rx_timed (
|
||||||
int radio_num, // Radio channel to use
|
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
|
bit [63:0] start_time
|
||||||
);
|
);
|
||||||
logic [31:0] cmd;
|
logic [31:0] cmd;
|
||||||
|
|
||||||
if (num_words == 0) begin
|
if (num_words == 0) begin
|
||||||
// Do a continuous acquisition
|
// 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;
|
cmd = RX_CMD_CONTINUOUS;
|
||||||
end else begin
|
end else begin
|
||||||
// Do a finite acquisition (num samps and done)
|
// 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);
|
write_radio_64(radio_num, REG_RX_CMD_NUM_WORDS_LO, num_words);
|
||||||
cmd = RX_CMD_FINITE;
|
cmd = RX_CMD_FINITE;
|
||||||
end
|
end
|
||||||
@@ -321,12 +337,12 @@ module rfnoc_block_radio_tb #(
|
|||||||
|
|
||||||
// Send the Rx stop command to the indicated radio channel
|
// Send the Rx stop command to the indicated radio channel
|
||||||
task automatic stop_rx(int radio_num);
|
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);
|
write_radio(radio_num, REG_RX_CMD, RX_CMD_STOP);
|
||||||
endtask : stop_rx
|
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.
|
// sequential and contiguous data aligned on packet boundaries.
|
||||||
task automatic check_rx(
|
task automatic check_rx(
|
||||||
int radio_num, // Radio to receive from and check
|
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
|
// Check that the length matches our expectation
|
||||||
`ASSERT_ERROR(
|
`ASSERT_ERROR(
|
||||||
byte_length == expected_length,
|
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
|
// 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
|
chdr_word_t chdr_word; // Next word to send to BFM
|
||||||
packet_info_t pkt_info = 0; // Flags/timestamp for next packet
|
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;
|
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
|
bit [ITEM_W-1:0] start_val = 1 // Initial sample value
|
||||||
);
|
);
|
||||||
int sample_val; // Expected value of next sample
|
int sample_val; // Expected value of next sample
|
||||||
|
bit found = 0;
|
||||||
|
int offset = 0;
|
||||||
|
int num_samps;
|
||||||
|
|
||||||
sample_val = 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.
|
||||||
// Wait for the packet to start
|
while (!found) begin
|
||||||
wait(radio_tx_data[radio_num*RADIO_W +: ITEM_W] == start_val);
|
@(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
|
// Check the time
|
||||||
if (!$isunknown(start_time)) begin
|
if (!$isunknown(start_time)) begin
|
||||||
`ASSERT_ERROR(
|
`ASSERT_ERROR(
|
||||||
radio_time - start_time <= NIPC*2,
|
radio_time+offset == start_time + RADIO_TX_LATENCY*NIPC,
|
||||||
$sformatf("Packet transmitted at radio time 0x%0X but expected 0x%0X", radio_time, start_time)
|
$sformatf("First sample transmitted at radio time 0x%0X but expected 0x%0X",
|
||||||
|
radio_time+offset, start_time + RADIO_TX_LATENCY*NIPC)
|
||||||
);
|
);
|
||||||
end
|
end
|
||||||
|
|
||||||
// Verify output one word at a time
|
// Verify output one word at a time
|
||||||
for (int word_count = 0; word_count < num_words; word_count++) begin
|
sample_val = start_val;
|
||||||
// Wait for the next radio word to be output
|
num_samps = num_words * NIPC;
|
||||||
do begin
|
for (int samp_count = 0; samp_count < num_samps; samp_count++) begin
|
||||||
@(posedge radio_clk);
|
`ASSERT_ERROR(
|
||||||
end while (radio_tx_stb[radio_num] == 0);
|
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
|
if (offset == NIPC) begin
|
||||||
for (int sub_sample = 0; sub_sample < NIPC; sub_sample++) begin
|
offset = 0;
|
||||||
`ASSERT_ERROR(
|
// Wait for the next radio word to be output
|
||||||
radio_tx_data[radio_num*RADIO_W + ITEM_W*sub_sample +: ITEM_W] == sample_val,
|
do begin
|
||||||
"Radio output doesn't match expected value"
|
@(posedge radio_clk);
|
||||||
);
|
end while (!radio_tx_stb[radio_num]);
|
||||||
sample_val++;
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
endtask : check_tx_timed
|
endtask : check_tx_timed
|
||||||
@@ -545,6 +582,23 @@ module rfnoc_block_radio_tb #(
|
|||||||
endtask : check_error
|
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
|
// Test Procedures
|
||||||
@@ -579,13 +633,18 @@ module rfnoc_block_radio_tb #(
|
|||||||
},
|
},
|
||||||
"REG_COMPAT_NUM didn't read correctly"
|
"REG_COMPAT_NUM didn't read correctly"
|
||||||
);
|
);
|
||||||
read_shared(REG_TIME_LO, time1[31:0]);
|
do begin
|
||||||
read_shared(REG_TIME_HI, time1[63:32]);
|
read_shared(REG_TIME_LO, time1[31:0]);
|
||||||
read_shared(REG_TIME_LO, time2[31:0]);
|
read_shared(REG_TIME_HI, time1[63:32]);
|
||||||
read_shared(REG_TIME_HI, time2[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(
|
`ASSERT_ERROR(
|
||||||
time2 > time1,
|
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();
|
test.end_test();
|
||||||
endtask : test_shared_registers
|
endtask : test_shared_registers
|
||||||
@@ -615,7 +674,7 @@ module rfnoc_block_radio_tb #(
|
|||||||
|
|
||||||
|
|
||||||
task test_rx_registers(int radio_num);
|
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;
|
localparam int num_words_len = RX_CMD_NUM_WORDS_LEN;
|
||||||
|
|
||||||
test.start_test("Rx Registers", 50us);
|
test.start_test("Rx Registers", 50us);
|
||||||
@@ -625,7 +684,7 @@ module rfnoc_block_radio_tb #(
|
|||||||
read_radio(radio_num, REG_RX_STATUS, val);
|
read_radio(radio_num, REG_RX_STATUS, val);
|
||||||
`ASSERT_ERROR(val == expected, "REG_RX_STATUS not initially CMD_FIFO_SPACE_MAX");
|
`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.
|
// read/write of the register.
|
||||||
expected = 0;
|
expected = 0;
|
||||||
expected[RX_CMD_POS +: RX_CMD_LEN] = RX_CMD_STOP;
|
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");
|
`ASSERT_ERROR(val == expected, "REG_RX_ERR_ADDR didn't update correctly");
|
||||||
|
|
||||||
// REG_RX_DATA (read-only)
|
// REG_RX_DATA (read-only)
|
||||||
temp = radio_tx_data[RADIO_W*radio_num +: RADIO_W];
|
do begin
|
||||||
read_radio(radio_num, REG_RX_DATA, val);
|
// 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(
|
`ASSERT_ERROR(
|
||||||
radio_rx_data[RADIO_W*radio_num +: RADIO_W] >= val && val >= temp,
|
radio_val_0 < val && val < radio_val_1,
|
||||||
"REG_RX_DATA wasn't in the expected range");
|
$sformatf({"REG_RX_DATA wasn't in the expected range\n",
|
||||||
read_radio(radio_num, REG_RX_DATA, temp);
|
"Radio Value 0: 0x%X, Reg Value: 0x%X, Radio Value 1: 0x%X"},
|
||||||
`ASSERT_ERROR(temp != val, "REG_RX_DATA didn't update");
|
radio_val_0, val, radio_val_1)
|
||||||
|
);
|
||||||
|
|
||||||
test.end_test();
|
test.end_test();
|
||||||
endtask : test_rx_registers
|
endtask : test_rx_registers
|
||||||
@@ -772,15 +838,15 @@ module rfnoc_block_radio_tb #(
|
|||||||
|
|
||||||
task automatic test_rx(int radio_num);
|
task automatic test_rx(int radio_num);
|
||||||
|
|
||||||
|
// Set default packet length
|
||||||
|
write_radio(radio_num, REG_RX_MAX_WORDS_PER_PKT, WPP);
|
||||||
|
|
||||||
//---------------------
|
//---------------------
|
||||||
// Finite Acquisitions
|
// Finite Acquisitions
|
||||||
//---------------------
|
//---------------------
|
||||||
|
|
||||||
test.start_test("Rx (finite)", 50us);
|
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
|
// Grab and verify a partial packet
|
||||||
start_rx(radio_num, WPP/2);
|
start_rx(radio_num, WPP/2);
|
||||||
check_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);
|
start_rx(radio_num, WPP*15/2);
|
||||||
check_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.
|
// receive anything. That is, make sure Rx actually stopped.
|
||||||
#MAX_PKT_WAIT;
|
#MAX_PKT_WAIT;
|
||||||
`ASSERT_ERROR(
|
`ASSERT_ERROR(
|
||||||
@@ -834,19 +900,36 @@ module rfnoc_block_radio_tb #(
|
|||||||
|
|
||||||
begin
|
begin
|
||||||
ChdrPacket #(CHDR_W) chdr_packet;
|
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);
|
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
|
// 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);
|
start_rx_timed(radio_num, WPP, expected_time);
|
||||||
|
|
||||||
// Take a peak at the timestamp in the received packet to check it
|
// Take a peak at the timestamp in the received packet to check it
|
||||||
blk_ctrl.peek_chdr(radio_num, chdr_packet);
|
blk_ctrl.peek_chdr(radio_num, chdr_packet);
|
||||||
`ASSERT_ERROR(
|
`ASSERT_ERROR(
|
||||||
chdr_packet.timestamp == expected_time,
|
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
|
// Verify the packet data
|
||||||
@@ -861,12 +944,21 @@ module rfnoc_block_radio_tb #(
|
|||||||
|
|
||||||
begin
|
begin
|
||||||
ChdrPacket #(CHDR_W) chdr_packet;
|
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);
|
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
|
// 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);
|
start_rx_timed(radio_num, 0, expected_time);
|
||||||
|
|
||||||
// Take a peak at the timestamp in the received packet to check it
|
// 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,
|
chdr_packet.timestamp == expected_time,
|
||||||
"Received packet didn't have expected timestamp"
|
"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
|
// Verify a few packets
|
||||||
check_rx(radio_num, WPP*3);
|
check_rx(radio_num, WPP*3);
|
||||||
@@ -893,9 +992,81 @@ module rfnoc_block_radio_tb #(
|
|||||||
end
|
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
|
// Rx Overflow
|
||||||
//-------------
|
//-------------
|
||||||
|
|
||||||
begin
|
begin
|
||||||
logic [31:0] val;
|
logic [31:0] val;
|
||||||
|
|
||||||
@@ -933,7 +1104,7 @@ module rfnoc_block_radio_tb #(
|
|||||||
"Rx radio reports that it is still busy after overflow"
|
"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.
|
// automatically after an overflow.
|
||||||
do begin
|
do begin
|
||||||
while (blk_ctrl.num_received(radio_num) != 0) 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;
|
expected = CMD_FIFO_SPACE_MAX-1;
|
||||||
read_radio(radio_num, REG_RX_STATUS, val);
|
read_radio(radio_num, REG_RX_STATUS, val);
|
||||||
`ASSERT_ERROR(
|
`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"
|
"CMD_FIFO_SPACE did not decrement"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -997,7 +1168,7 @@ module rfnoc_block_radio_tb #(
|
|||||||
expected = 0;
|
expected = 0;
|
||||||
read_radio(radio_num, REG_RX_STATUS, val);
|
read_radio(radio_num, REG_RX_STATUS, val);
|
||||||
`ASSERT_ERROR(
|
`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"
|
"CMD_FIFO_SPACE did not reach 0"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1006,7 +1177,7 @@ module rfnoc_block_radio_tb #(
|
|||||||
expected = CMD_FIFO_SPACE_MAX;
|
expected = CMD_FIFO_SPACE_MAX;
|
||||||
read_radio(radio_num, REG_RX_STATUS, val);
|
read_radio(radio_num, REG_RX_STATUS, val);
|
||||||
`ASSERT_ERROR(
|
`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"
|
"CMD_FIFO_SPACE did not return to max"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1019,8 +1190,8 @@ module rfnoc_block_radio_tb #(
|
|||||||
#MAX_PKT_WAIT;
|
#MAX_PKT_WAIT;
|
||||||
end while (blk_ctrl.num_received(radio_num) != 0);
|
end while (blk_ctrl.num_received(radio_num) != 0);
|
||||||
|
|
||||||
// Queue several long commands back-to-back and make sure they all
|
// Queue several long commands back-to-back and make sure they all
|
||||||
// complete. The lengths are unique to ensure we execute the right
|
// complete. The lengths are unique to ensure we execute the right
|
||||||
// commands in the expected order.
|
// 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++) start_rx(radio_num, WPP*20+i);
|
||||||
for (int i = 0; i < 3; i++) check_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);
|
test.start_test("Tx (now, underflow)", 50us);
|
||||||
|
|
||||||
|
write_radio(radio_num, REG_TX_ERROR_POLICY, TX_ERR_POLICY_PACKET);
|
||||||
|
|
||||||
// Send some bursts without EOB
|
// Send some bursts without EOB
|
||||||
start_tx(radio_num, WPP*3/4, 1, 0); // Skip EOB
|
start_tx(radio_num, WPP*3/4, 1, 0); // Skip EOB
|
||||||
check_tx(radio_num, WPP*3/4);
|
check_tx(radio_num, WPP*3/4);
|
||||||
@@ -1108,36 +1281,88 @@ module rfnoc_block_radio_tb #(
|
|||||||
test.start_test("Tx (timed)", 50us);
|
test.start_test("Tx (timed)", 50us);
|
||||||
|
|
||||||
// Grab and verify a partial packet
|
// Grab and verify a partial packet
|
||||||
start_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 + 200);
|
check_tx_timed(radio_num, WPP*3/4, radio_time + TX_CMD_DELAY);
|
||||||
check_error(ERR_TX_EOB_ACK);
|
check_error(ERR_TX_EOB_ACK);
|
||||||
|
|
||||||
// Grab and verify whole packets
|
// Grab and verify whole packets
|
||||||
start_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 + 200);
|
check_tx_timed(radio_num, WPP*2, radio_time + TX_CMD_DELAY);
|
||||||
check_error(ERR_TX_EOB_ACK);
|
check_error(ERR_TX_EOB_ACK);
|
||||||
|
|
||||||
test.end_test();
|
test.end_test();
|
||||||
|
|
||||||
|
|
||||||
//-----------------
|
//----------------------------
|
||||||
// Test Tx (timed, underflow)
|
// Test Tx (timed, underflow)
|
||||||
//-----------------
|
//----------------------------
|
||||||
|
|
||||||
test.start_test("Tx (timed, underflow)", 50us);
|
test.start_test("Tx (timed, underflow)", 50us);
|
||||||
|
|
||||||
// Send some bursts without EOB
|
// Send some bursts without EOB
|
||||||
start_tx_timed(radio_num, WPP*3/4, radio_time + 200, 1, 0);
|
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 + 200);
|
check_tx_timed(radio_num, WPP*3/4, radio_time + TX_CMD_DELAY);
|
||||||
check_error(ERR_TX_UNDERRUN);
|
check_error(ERR_TX_UNDERRUN);
|
||||||
|
|
||||||
start_tx_timed(radio_num, WPP*2, radio_time + 200, 1, 0);
|
start_tx_timed(radio_num, WPP*2, radio_time + TX_CMD_DELAY, 1, 0);
|
||||||
check_tx_timed(radio_num, WPP*2, radio_time + 200);
|
check_tx_timed(radio_num, WPP*2, radio_time + TX_CMD_DELAY);
|
||||||
check_error(ERR_TX_UNDERRUN);
|
check_error(ERR_TX_UNDERRUN);
|
||||||
|
|
||||||
test.end_test();
|
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)
|
// 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);
|
write_radio(radio_num, REG_TX_ERROR_POLICY, TX_ERR_POLICY_BURST);
|
||||||
end
|
end
|
||||||
|
|
||||||
// Commenting out the fork code for now due to Vivado 2018.3 bug.
|
radio_data = radio_tx_data[radio_num];
|
||||||
// radio_data = radio_tx_data[radio_num];
|
fork : tx_fork
|
||||||
// fork : tx_fork
|
|
||||||
// In this branch of the fork, we send the packets
|
// In this branch of the fork, we send the packets
|
||||||
repeat (2) begin
|
repeat (2) begin
|
||||||
// Send late packets with random start value
|
// Send late packets with random start value
|
||||||
@@ -1171,29 +1395,31 @@ module rfnoc_block_radio_tb #(
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
// // The packets sent in the above branch of the fork should be
|
// 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
|
// dropped. In this branch of the fork we make sure that the Tx
|
||||||
// // output doesn't change.
|
// output doesn't change.
|
||||||
// begin
|
begin
|
||||||
// forever begin
|
forever begin
|
||||||
// @(posedge radio_clk)
|
@(posedge radio_clk)
|
||||||
// `ASSERT_ERROR(
|
if (radio_tx_stb[radio_num]) begin
|
||||||
// radio_data === radio_tx_data[radio_num],
|
`ASSERT_ERROR(
|
||||||
// "Radio Tx output changed when late Tx packet should have been ignored"
|
radio_data === radio_tx_data[radio_num],
|
||||||
// );
|
"Radio Tx output changed when late Tx packet should have been ignored"
|
||||||
// end
|
);
|
||||||
// end
|
end
|
||||||
// join_any
|
end
|
||||||
//
|
end
|
||||||
// // Stop checking the output
|
join_any
|
||||||
// disable tx_fork;
|
|
||||||
|
// Stop checking the output
|
||||||
|
disable tx_fork;
|
||||||
|
|
||||||
policy = policy.next();
|
policy = policy.next();
|
||||||
end while (policy != policy.first());
|
end while (policy != policy.first());
|
||||||
|
|
||||||
// Make sure good transmissions can go through now.
|
// Make sure good transmissions can go through now.
|
||||||
start_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 + 200);
|
check_tx_timed(radio_num, WPP, radio_time + TX_CMD_DELAY);
|
||||||
check_error(ERR_TX_EOB_ACK);
|
check_error(ERR_TX_EOB_ACK);
|
||||||
|
|
||||||
test.end_test();
|
test.end_test();
|
||||||
@@ -1217,8 +1443,8 @@ module rfnoc_block_radio_tb #(
|
|||||||
// Turn on loopback
|
// Turn on loopback
|
||||||
write_radio(radio_num, REG_LOOPBACK_EN, 1);
|
write_radio(radio_num, REG_LOOPBACK_EN, 1);
|
||||||
|
|
||||||
// This test ensures we get the Tx output on Rx and not the TB's simulated
|
// 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
|
// radio data. It also tests updating the idle value. Run the test twice to
|
||||||
// make sure the IDLE value updates.
|
// make sure the IDLE value updates.
|
||||||
repeat (2) begin
|
repeat (2) begin
|
||||||
// Set idle value
|
// Set idle value
|
||||||
@@ -1226,12 +1452,12 @@ module rfnoc_block_radio_tb #(
|
|||||||
write_radio(radio_num, REG_TX_IDLE_VALUE, idle);
|
write_radio(radio_num, REG_TX_IDLE_VALUE, idle);
|
||||||
|
|
||||||
// Grab a radio word and check that it equals the IDLE value
|
// 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);
|
write_radio(radio_num, REG_RX_CMD, RX_CMD_FINITE);
|
||||||
blk_ctrl.recv(radio_num, data, byte_length);
|
blk_ctrl.recv(radio_num, data, byte_length);
|
||||||
|
|
||||||
// Check the 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
|
// Check the payload
|
||||||
foreach (data[i]) begin
|
foreach (data[i]) begin
|
||||||
@@ -1265,12 +1491,12 @@ module rfnoc_block_radio_tb #(
|
|||||||
// Set packet length
|
// Set packet length
|
||||||
write_radio(radio_num, REG_RX_MAX_WORDS_PER_PKT, WPP);
|
write_radio(radio_num, REG_RX_MAX_WORDS_PER_PKT, WPP);
|
||||||
|
|
||||||
// Loopback a few packets, back-to-back. This code has a race condition
|
// 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
|
// 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
|
// 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
|
// lot more packets than we receive to ensure we're still transmitting by
|
||||||
// the time we receive.
|
// the time we receive.
|
||||||
start_tx(radio_num, WPP*16);
|
start_tx(radio_num, WPP*20);
|
||||||
start_rx(radio_num, WPP*2);
|
start_rx(radio_num, WPP*2);
|
||||||
|
|
||||||
// Check the results
|
// Check the results
|
||||||
@@ -1300,11 +1526,12 @@ module rfnoc_block_radio_tb #(
|
|||||||
|
|
||||||
// Generate a string for the name of this instance of the testbench
|
// Generate a string for the name of this instance of the testbench
|
||||||
tb_name = $sformatf(
|
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
|
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
|
// 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
|
// the clocks aren't toggling while other instances of this testbench are
|
||||||
@@ -1330,7 +1557,7 @@ module rfnoc_block_radio_tb #(
|
|||||||
// Test Sequences
|
// 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.
|
// correct.
|
||||||
|
|
||||||
test_block_info();
|
test_block_info();
|
||||||
@@ -1345,9 +1572,11 @@ module rfnoc_block_radio_tb #(
|
|||||||
test_rx_registers(radio_num);
|
test_rx_registers(radio_num);
|
||||||
test_tx_registers(radio_num);
|
test_tx_registers(radio_num);
|
||||||
end
|
end
|
||||||
test_rx(radio_num);
|
repeat (NUM_TESTS) begin
|
||||||
test_tx(radio_num);
|
test_rx(radio_num);
|
||||||
test_loopback_and_idle(radio_num);
|
test_tx(radio_num);
|
||||||
|
test_loopback_and_idle(radio_num);
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -1355,7 +1584,7 @@ module rfnoc_block_radio_tb #(
|
|||||||
// Finish
|
// 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.
|
// instances of this testbench that may be running.
|
||||||
test.end_tb(0);
|
test.end_tb(0);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user