fpga: rfnoc: Make Replay packet length independent of burst size

Before this change, the packet size output by the Replay block during
playback was limited to length of a full memory burst transaction.
This led to relatively small packets during playback (typically
2 KiB) and had other side effects, such as simultaneous playback from
two different memory locations using different packet sizes because of
differences in memory alignment.

With this change, the configured packet size, as set by the register
REG_PLAY_WORDS_PER_PKT, is used for all packets except the last
packet of playback, which can of course be smaller.


Original-commit: 082a733c326d6332d581d5b7bf9ee86a503ba502
This commit is contained in:
Wade Fife
2022-03-09 07:57:21 -06:00
parent df7fd28589
commit 56e53d271e
2 changed files with 160 additions and 108 deletions
@@ -281,7 +281,7 @@ module axis_replay #(
reg_play_cmd_time <= 0;
reg_play_words_per_pkt <= REG_PLAY_WORDS_PER_PKT_INIT;
reg_item_size <= DEFAULT_ITEM_SIZE;
items_per_word <= 'bX;
items_per_word <= 'bX;
rec_restart <= 0;
play_cmd_stop <= 0;
clear_cmd_fifo <= 0;
@@ -576,7 +576,7 @@ module axis_replay #(
rec_restart_clear <= 1'b0;
// Update wait timer
if (i_tvalid || !rec_fifo_occupied) begin
if ((i_tvalid && i_tready) || !rec_fifo_occupied) begin
// If a new word is presented to the input FIFO, or the FIFO is empty,
// then reset the timer.
rec_wait_timer <= 0;
@@ -943,78 +943,91 @@ module axis_replay #(
//
// This section monitors the signals to/from the memory interface and
// generates the TLAST and sideband signals. We assert TLAST at the end of
// every read transaction and after every reg_play_words_per_pkt words, so
// every reg_play_words_per_pkt words and at the end of the last packet, so
// that no packets are longer than the length indicated by the
// REG_PLAY_WORDS_PER_PKT register.
//
// The sideband signals consist of the timestamp, has_time flag, and eob
// flag. These are generated by the playback logic for each memory
// transaction.
// The sideband signals consist of the timestamp, has-time flag, and EOB (end
// of burst) flag. Timestamp and has_time are set for the first packet of
// each playback. EOB applies to each packet but is only set to 1 for the
// last packet of a playback.
//
// The timing of this block relies on the fact that read_ctrl_ready is not
// reasserted by the memory interface until after TLAST gets asserted.
// The timing of this section relies on the fact axi_dma_master doesn't allow
// overlapping read transactions. This means that the next read_ctrl_ready
// won't be asserted until after previous memory transaction finishes being
// read out.
//
//---------------------------------------------------------------------------
reg [MEM_COUNT_W-1:0] read_counter;
reg [ WPP_W-1:0] length_counter;
reg [ TIME_W-1:0] time_counter;
reg play_fifo_i_tlast;
reg has_time;
reg eob;
reg [MEM_COUNT_W-1:0] read_counter; // Track outstanding words to read
reg [ WPP_W-1:0] length_counter; // Track packet length
reg [ TIME_W-1:0] timestamp; // Timestamp for the current burst
reg has_time; // Is current burst timed?
reg eob; // End of burst
reg play_fifo_i_tlast; // End of packet
always @(posedge clk)
begin
if (rst) begin
play_fifo_i_tlast <= 1'b0;
// Don't care:
read_counter <= {MEM_COUNT_W{1'bX}};
length_counter <= {MEM_COUNT_W+1{1'bX}};
time_counter <= {TIME_W{1'bX}};
has_time <= 1'bX;
eob <= 1'bX;
end else begin
// Check if we're requesting a read transaction
if (read_ctrl_valid && read_ctrl_ready) begin
// Initialize read_counter for new transaction
read_counter <= read_count;
// synthesis translate_off
//
// Check our assumption about non-overlapping read transactions.
if (read_ctrl_ready && play_fifo_i_tvalid) begin
$fatal(1, "New read transaction started before the previous one completed!");
end
// synthesis translate_on
if (read_ctrl_valid && read_ctrl_ready) begin
read_counter <= read_count;
// If read_count is 0, then the next word is also the last word
if (read_count == 0) begin
play_fifo_i_tlast <= 1'b1;
eob <= last_trans;
end
end
if (play_fifo_i_tvalid && play_fifo_i_tready) begin
read_counter <= read_counter - 1;
length_counter <= length_counter - 1;
// Check if the current word is the last of the packet
if (play_fifo_i_tlast) begin
length_counter <= reg_play_words_per_pkt;
time_counter <= cmd_time;
has_time <= cmd_timed;
eob <= last_trans && (read_count < reg_play_words_per_pkt);
// If read_count is 0, then the first word is also the last word
if (read_count == 0) begin
play_fifo_i_tlast <= 1'b1;
// Clear tlast, unless the first word of the next packet is also the
// last word of the next packet.
if (!(last_trans && read_counter == 1)) begin
play_fifo_i_tlast <= 1'b0;
end
// Track the number of words read out by memory interface
end else if (read_data_valid && read_data_ready) begin
read_counter <= read_counter - 1;
length_counter <= length_counter - 1;
time_counter <= time_counter + items_per_word;
// Check if the word currently being output is the last word of a
// packet, which means we need to clear tlast.
if (play_fifo_i_tlast) begin
// But make sure that the next word isn't also the last of a memory
// burst, for which we will need to keep tlast asserted.
if (read_counter != 1) begin
play_fifo_i_tlast <= 1'b0;
end
// Restart length counter
length_counter <= reg_play_words_per_pkt;
// Check if next packet is the end of the burst (EOB)
eob <= last_trans && (read_counter <= reg_play_words_per_pkt);
// Check if the next word to be output should be the last of a packet.
end else if (read_counter == 1 || length_counter == 2) begin
play_fifo_i_tlast <= 1'b1;
end
// The timestamp only applies to the first packet, so disable for
// subsequent packets.
has_time <= 1'b0;
end
// Check if the next word will be the last of the packet.
//
// First, check if the next word is the last word of playback, in which
// case it's both the last word of the packet and the end of the burst.
if (last_trans && read_counter == 1) begin
play_fifo_i_tlast <= 1'b1;
eob <= 1;
// Next, check if this is the last word of the packet according to packet
// length. But note that the next word won't be the last if we're already
// outputting the last word of a burst on the current cycle.
end else if (length_counter == 2 && !(eob && play_fifo_i_tlast)) begin
play_fifo_i_tlast <= 1'b1;
end
end
if (play_state == PLAY_IDLE) begin
// Reset signals for the next playback
length_counter <= reg_play_words_per_pkt;
timestamp <= cmd_time_cf;
has_time <= cmd_timed_cf;
eob <= 0;
play_fifo_i_tlast <= 1'b0;
end
end
@@ -1053,20 +1066,6 @@ module axis_replay #(
.occupied ()
);
reg play_fifo_i_sop = 1'b1;
// Make play_fifo_i_sop true whenever the next play_fifo_i word is the start
// of a packet.
always @(posedge clk) begin
if (rst) begin
play_fifo_i_sop <= 1'b1;
end else begin
if (play_fifo_i_tvalid & play_fifo_i_tready) begin
play_fifo_i_sop <= play_fifo_i_tlast;
end
end
end
//---------------------------------------------------------------------------
// Header Info FIFO
@@ -1080,6 +1079,7 @@ module axis_replay #(
wire [(TIME_W+2)-1:0] hdr_fifo_i_tdata;
wire hdr_fifo_i_tvalid;
wire [(TIME_W+2)-1:0] hdr_fifo_o_tdata;
wire hdr_fifo_o_tvalid;
wire hdr_fifo_o_tready;
wire [15:0] hdr_fifo_space;
@@ -1097,20 +1097,31 @@ module axis_replay #(
.i_tready (),
//
.o_tdata (hdr_fifo_o_tdata),
.o_tvalid (),
.o_tvalid (hdr_fifo_o_tvalid),
.o_tready (hdr_fifo_o_tready),
//
.space (hdr_fifo_space),
.occupied ()
);
assign hdr_fifo_i_tdata = {has_time, eob, time_counter};
// synthesis translate_off
//
// The FIFO code above assumes the header info will always be available when
// the last word of the payload FIFO is read out. Check that assumption here.
always @(posedge clk) begin
if (hdr_fifo_o_tready && !hdr_fifo_o_tvalid) begin
$fatal(1, "Header FIFO read without valid data!");
end
end
// synthesis translate_on
assign hdr_fifo_i_tdata = {has_time, eob, timestamp };
// Pop the timestamp whenever we finish reading out a data packet
assign hdr_fifo_o_tready = o_tvalid & o_tready & o_tlast;
// Write the timestamp at the start of each packet
assign hdr_fifo_i_tvalid = play_fifo_i_tvalid & play_fifo_i_tready & play_fifo_i_sop;
assign hdr_fifo_i_tvalid = play_fifo_i_tvalid & play_fifo_i_tready & play_fifo_i_tlast;
assign { o_thas_time, o_teob, o_ttimestamp } = hdr_fifo_o_tdata;
@@ -46,6 +46,8 @@ module rfnoc_block_replay_tb#(
`include "rfnoc_block_replay_regs.vh"
`define MIN(X,Y) ((X)<(Y)?(X):(Y))
//---------------------------------------------------------------------------
// Testbench Configuration
@@ -535,6 +537,8 @@ module rfnoc_block_replay_tb#(
"Buffer size extends beyond available memory");
`ASSERT_FATAL(spp * ITEM_SIZE % MEM_WORD_SIZE == 0,
"Requested SPP must be a multiple of the memory word size");
`ASSERT_FATAL(spp <= 2**MTU,
"Requested SPP exceeds MTU");
// Update record buffer settings
write_reg_64(port, REG_REC_BASE_ADDR_LO, base_addr);
@@ -583,6 +587,7 @@ module rfnoc_block_replay_tb#(
// exp_items : Queue of the items you expect to receive
// eob : Indicates if we expect EOB to be set for the last item (set
// to 1'bX to skip this check)
// spp : Samples per packet to expect. Set to 0 to skip this check.
// timestamp : The timestamp we expect to receive for the first item (set to
// 'X to skip timestamp checking)
//
@@ -591,6 +596,7 @@ module rfnoc_block_replay_tb#(
output string error_msg,
input item_t exp_items[$],
input logic eob = 1'b1,
input int spp = SPP,
input logic [63:0] timestamp = 64'bX
);
item_t recv_items[$];
@@ -614,9 +620,9 @@ module rfnoc_block_replay_tb#(
// Check the packet length
if (item_count + recv_items.size() > exp_items.size()) begin
$sformat(error_msg,
"On packet %0d, size exceeds expected by %0d items",
packet_count,
(item_count + recv_items.size()) - exp_items.size());
"On packet %0d, size is %0d items, which exceeds expected by %0d items.",
packet_count, recv_items.size(),
(item_count + recv_items.size()) - exp_items.size());
return;
end
@@ -645,8 +651,27 @@ module rfnoc_block_replay_tb#(
end
end
// Check the timestamp
if (timestamp !== 64'bX) begin
// Check the payload length against expected SPP
if (spp > 0) begin
// If this is NOT the last packet, then its length should match the SPP.
// If it is the last packet, then it can be shorter than SPP.
if (!pkt_info.eob) begin
if (recv_items.size() != spp) begin
$sformat(error_msg,
"On packet %0d, expected SPP of %0d but packet has %0d items",
packet_count, spp, recv_items.size());
end
end else begin
if (recv_items.size() > spp) begin
$sformat(error_msg,
"On packet %0d (EOB), expected SPP of %0d or less but packet has %0d items",
packet_count, spp, recv_items.size());
end
end
end
// Check the timestamp (only first packet should have a timestamp)
if (timestamp !== 64'bX && packet_count == 0) begin
if (!pkt_info.timestamp) begin
$sformat(error_msg,
"On packet %0d, timestamp is missing",
@@ -925,37 +950,40 @@ module rfnoc_block_replay_tb#(
// For each test below, we record two memory bursts and playback two memory
// bursts. Each time we change the playback packet size to test boundary
// conditions.
//
// Note that we can't let the SPP exceed the MTU, so if the burst size is
// too large, we won't be testing the full size.
// Test packet size equals burst size
spp = MEM_BURST_LEN * MEM_WORD_SIZE / ITEM_SIZE;
spp = `MIN(MEM_BURST_LEN * MEM_WORD_SIZE / ITEM_SIZE, 2**MTU);
start_replay(port, send_items, buffer_size, num_items, spp);
verify_rx_data(port, error_string, send_items, 1);
verify_rx_data(port, error_string, send_items, 1, spp);
`ASSERT_ERROR(error_string == "", error_string);
// Test packet is one less than burst size
spp = (MEM_BURST_LEN-1) * MEM_WORD_SIZE / ITEM_SIZE;
spp = `MIN((MEM_BURST_LEN-1) * MEM_WORD_SIZE / ITEM_SIZE, 2**MTU-(MEM_WORD_SIZE / ITEM_SIZE));
start_replay(port, send_items, buffer_size, num_items, spp);
verify_rx_data(port, error_string, send_items, 1);
verify_rx_data(port, error_string, send_items, 1, spp);
`ASSERT_ERROR(error_string == "", error_string);
// Test packet is one more than burst size
spp = (MEM_BURST_LEN+1) * MEM_WORD_SIZE / ITEM_SIZE;
spp = `MIN((MEM_BURST_LEN+1) * MEM_WORD_SIZE / ITEM_SIZE, 2**MTU);
start_replay(port, send_items, buffer_size, num_items, spp);
verify_rx_data(port, error_string, send_items, 1);
verify_rx_data(port, error_string, send_items, 1, spp);
`ASSERT_ERROR(error_string == "", error_string);
// For each test below, we record two memory bursts and playback one memory
// burst plus or minus one word, keeping the packet size the same.
spp = MEM_BURST_LEN * MEM_WORD_SIZE / ITEM_SIZE;
spp = `MIN(MEM_BURST_LEN * MEM_WORD_SIZE / ITEM_SIZE, 2**MTU);
// Playback one less than burst/packet size
start_replay(port, send_items, buffer_size, spp-MEM_WORD_SIZE/ITEM_SIZE, spp);
verify_rx_data(port, error_string, send_items[0:spp-1-MEM_WORD_SIZE/ITEM_SIZE], 1);
verify_rx_data(port, error_string, send_items[0:spp-1-MEM_WORD_SIZE/ITEM_SIZE], 1, spp);
`ASSERT_ERROR(error_string == "", error_string);
// Playback one more than burst/packet size
start_replay(port, send_items, buffer_size, spp+MEM_WORD_SIZE/ITEM_SIZE, spp);
verify_rx_data(port, error_string, send_items[0:spp-1+MEM_WORD_SIZE/ITEM_SIZE], 1);
verify_rx_data(port, error_string, send_items[0:spp-1+MEM_WORD_SIZE/ITEM_SIZE], 1, spp);
`ASSERT_ERROR(error_string == "", error_string);
// Make sure there are no more packets
@@ -1016,8 +1044,8 @@ module rfnoc_block_replay_tb#(
// Set number of words to test
buffer_size = (3 * MEM_BURST_LEN) / 2 * MEM_WORD_SIZE; // 1.5 memory bursts in size (in bytes)
num_items_rec = buffer_size / ITEM_SIZE; // 1.5 memory bursts in size (in CHDR words)
num_items_play = 2 * MEM_BURST_LEN * MEM_WORD_SIZE / // 2 memory bursts in size (in CHDR words)
num_items_rec = buffer_size / ITEM_SIZE; // 1.5 memory bursts in size (in samples)
num_items_play = 2 * MEM_BURST_LEN * MEM_WORD_SIZE / // 2 memory bursts in size (in samples)
ITEM_SIZE;
// Start playback of data
@@ -1045,19 +1073,25 @@ module rfnoc_block_replay_tb#(
item_t send_items[$];
string error_string;
logic [31:0] cmd;
int num_items, mem_words;
int num_items, mem_words, spp;
test.start_test("Test continuous mode", TEST_TIMEOUT);
mem_words = 70;
num_items = mem_words * MEM_WORD_SIZE / ITEM_SIZE;
// Make sure the spp evenly divides our test size, or else we'll end a
// check mid packet, which verify_rx_data doesn't support.
spp = num_items / 2;
`ASSERT_ERROR(num_items % spp == 0,
"num_items should be a multiple of spp for this test");
// Update record buffer settings
write_reg_64(port, REG_REC_BASE_ADDR_LO, 0);
write_reg_64(port, REG_REC_BUFFER_SIZE_LO, num_items*ITEM_SIZE);
write_reg_64(port, REG_PLAY_BASE_ADDR_LO, 0);
write_reg_64(port, REG_PLAY_BUFFER_SIZE_LO, num_items*ITEM_SIZE);
write_reg (port, REG_PLAY_WORDS_PER_PKT, SPP * ITEM_SIZE/MEM_WORD_SIZE);
write_reg (port, REG_PLAY_WORDS_PER_PKT, spp * ITEM_SIZE/MEM_WORD_SIZE);
// Restart the record buffer
write_reg(port, REG_REC_RESTART, 0);
@@ -1071,16 +1105,15 @@ module rfnoc_block_replay_tb#(
// Make sure the REG_PLAY_CMD_NUM_WORDS value is ignored by setting it to
// something smaller than what we receive.
write_reg_64(port, REG_PLAY_CMD_NUM_WORDS_LO, mem_words);
write_reg_64(port, REG_PLAY_CMD_NUM_WORDS_LO, 0);
// Send command for continuous playback
cmd = PLAY_CMD_CONTINUOUS;
write_reg_64(port, REG_PLAY_CMD_NUM_WORDS_LO, mem_words);
write_reg(port, REG_PLAY_CMD, cmd);
// Check the output, looking for the full set of data, multiple times
repeat (5) begin
verify_rx_data(port, error_string, send_items, 0);
verify_rx_data(port, error_string, send_items, 0, spp);
`ASSERT_ERROR(error_string == "", error_string);
end
@@ -1153,6 +1186,7 @@ module rfnoc_block_replay_tb#(
// Check the result
verify_rx_data(port, error_string, send_items, 1);
`ASSERT_ERROR(error_string == "", error_string);
// Check the fullness
read_reg_64(port, REG_REC_FULLNESS_LO, val64);
@@ -1264,15 +1298,21 @@ module rfnoc_block_replay_tb#(
item_t send_items[$];
item_t exp_items[$];
string error_string;
int num_items, mem_words;
int num_items, spp;
int num_items_buf;
logic [63:0] val64;
test.start_test("Test overfilled record buffer", TEST_TIMEOUT);
// Choose the sizes we want to use for this test
num_items = 97*MEM_WORD_SIZE/ITEM_SIZE; // Number of items to record
num_items_buf = 43*MEM_WORD_SIZE/ITEM_SIZE; // Size of buffer to use in items
num_items = 98*MEM_WORD_SIZE/ITEM_SIZE; // Number of items to record
num_items_buf = 44*MEM_WORD_SIZE/ITEM_SIZE; // Size of buffer to use in items
// Make sure the spp evenly divides our test size, or else we'll end a
// check mid packet, which verify_rx_data doesn't support.
spp = num_items_buf / 4;
`ASSERT_ERROR(num_items_buf % spp == 0,
"num_items_buf should be a multiple of spp for this test");
// Restart the record buffer
write_reg(port, REG_REC_RESTART, 0);
@@ -1281,17 +1321,17 @@ module rfnoc_block_replay_tb#(
send_items = gen_test_data(num_items);
// Start playback of the larger size
start_replay(port, send_items, num_items_buf*ITEM_SIZE, num_items);
start_replay(port, send_items, num_items_buf*ITEM_SIZE, num_items, spp);
// We should get two frames of num_items_buf, then one smaller frame to
// bring us up to num_items total.
exp_items = send_items[0 : num_items_buf-1];
for (int i = 0; i < 2; i ++) begin
verify_rx_data(port, error_string, exp_items, 0);
verify_rx_data(port, error_string, exp_items, 0, spp);
`ASSERT_ERROR(error_string == "", error_string);
end
exp_items = exp_items[0 : (num_items % num_items_buf)-1];
verify_rx_data(port, error_string, exp_items, 1);
verify_rx_data(port, error_string, exp_items, 1, spp);
`ASSERT_ERROR(error_string == "", error_string);
// Make sure REG_REC_FULLNESS didn't keep increasing
@@ -1447,12 +1487,11 @@ module rfnoc_block_replay_tb#(
send_items = gen_test_data(num_items);
start_replay(port, send_items, buffer_size, num_items, pkt_size_items);
// We should get 8 small packets instead of 2 large ones, with EOB set on
// the last packet.
// We should get 8 small packets with EOB set on the last packet.
for (int k = 0; k < 8; k ++) begin
verify_rx_data(port, error_string,
send_items[pkt_size_items*k : pkt_size_items*(k+1)-1],
(k == 7 ? 1 : 0));
(k == 7 ? 1 : 0), pkt_size_items);
`ASSERT_ERROR(error_string == "", error_string);
end
@@ -1473,7 +1512,7 @@ module rfnoc_block_replay_tb#(
// with EOB set on the last packet.
for (int i=0; i < num_items; i += pkt_size_items) begin
verify_rx_data(port, error_string, send_items[i:i+pkt_size_items-1],
i == num_items-pkt_size_items ? 1 : 0);
i == num_items-pkt_size_items ? 1 : 0, pkt_size_items);
`ASSERT_FATAL(error_string == "", error_string);
end
@@ -1507,7 +1546,7 @@ module rfnoc_block_replay_tb#(
send_items = gen_test_data(num_items);
start_replay(port, send_items, buffer_size, num_items, spp, 0, 0, timestamp);
verify_rx_data(port, error_string, send_items, 1, timestamp);
verify_rx_data(port, error_string, send_items, 1, spp, timestamp);
`ASSERT_ERROR(error_string == "", error_string);
test.end_test();
@@ -1581,6 +1620,8 @@ module rfnoc_block_replay_tb#(
blk_ctrl.send_items(port+1, port1_data);
// Play back on each port
write_reg(port+0, REG_PLAY_WORDS_PER_PKT, SPP*ITEM_SIZE/MEM_WORD_SIZE);
write_reg(port+1, REG_PLAY_WORDS_PER_PKT, SPP*ITEM_SIZE/MEM_WORD_SIZE);
write_reg_64(port+0, REG_PLAY_CMD_NUM_WORDS_LO, mem_words);
write_reg_64(port+1, REG_PLAY_CMD_NUM_WORDS_LO, mem_words);
write_reg(port+0, REG_PLAY_CMD, PLAY_CMD_FINITE);
@@ -1704,7 +1745,7 @@ module rfnoc_block_replay_tb#(
// Generate a string for the name of this instance of the testbench
tb_name = $sformatf( {
"rfnoc_block_replay_tb\n",
"CHDR_W = %03d, ITEM_W = %02d, NUM_PORTS = %d,\n",
"CHDR_W = %03d, ITEM_W = %02d, NUM_PORTS = %0d,\n",
"MEM_DATA_W = %03d, MEM_ADDR_W = %02d,\n",
"TEST_REGS = %03d, TEST_FULL = %02d,\n",
"STALL_PROB = %03d" },