aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3
diff options
context:
space:
mode:
authorWade Fife <wade.fife@ettus.com>2020-04-06 16:19:50 -0500
committerAaron Rossetto <aaron.rossetto@ni.com>2020-08-04 15:40:08 -0500
commit6d92a1828121ca4b57d496bbf522820f961244b9 (patch)
treef0659ab6d1a6e1f8801db356e2237388d90724f2 /fpga/usrp3
parent24f8bb39fd2769ff93d11b21152a834500152de4 (diff)
downloaduhd-6d92a1828121ca4b57d496bbf522820f961244b9.tar.gz
uhd-6d92a1828121ca4b57d496bbf522820f961244b9.tar.bz2
uhd-6d92a1828121ca4b57d496bbf522820f961244b9.zip
fpga: rfnoc: Add RFNoC Replay block
Diffstat (limited to 'fpga/usrp3')
-rw-r--r--fpga/usrp3/lib/axi/Makefile.srcs1
-rw-r--r--fpga/usrp3/lib/axi/axi_replay.v867
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/Makefile45
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/Makefile.srcs24
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/axis_replay.v1146
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/noc_shell_replay.v306
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/rfnoc_block_replay.v518
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/rfnoc_block_replay_all_tb.sv82
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/rfnoc_block_replay_regs.vh205
-rw-r--r--fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/rfnoc_block_replay_tb.sv1775
-rw-r--r--fpga/usrp3/lib/rfnoc/core/axis_data_to_chdr.v7
11 files changed, 4101 insertions, 875 deletions
diff --git a/fpga/usrp3/lib/axi/Makefile.srcs b/fpga/usrp3/lib/axi/Makefile.srcs
index 598af9ef3..c56244987 100644
--- a/fpga/usrp3/lib/axi/Makefile.srcs
+++ b/fpga/usrp3/lib/axi/Makefile.srcs
@@ -14,7 +14,6 @@ axi_chdr_test_pattern.v \
axi_defs.v \
axi_dma_fifo.v \
axi_dma_master.v \
-axi_replay.v \
axi_embed_tlast.v \
axi_extract_tlast.v \
axi_fast_extract_tlast.v \
diff --git a/fpga/usrp3/lib/axi/axi_replay.v b/fpga/usrp3/lib/axi/axi_replay.v
deleted file mode 100644
index 49e4318c5..000000000
--- a/fpga/usrp3/lib/axi/axi_replay.v
+++ /dev/null
@@ -1,867 +0,0 @@
-//
-// Copyright 2017 Ettus Research, A National Instruments Company
-//
-// SPDX-License-Identifier: LGPL-3.0
-//
-// Module: axi_replay.v
-// Description:
-//
-// This block implements the state machine and control logic for recording and
-// playback of AXI-Stream data, using a DMA-accessible memory as a buffer.
-
-
-module axi_replay #(
- parameter DATA_WIDTH = 64,
- parameter ADDR_WIDTH = 32, // Byte address width used by DMA master
- parameter COUNT_WIDTH = 8 // Length of counters used to connect to the DMA
- // master's read and write interfaces.
-) (
- input wire clk,
- input wire rst, // Synchronous to clk
-
- //---------------------------------------------------------------------------
- // Settings Bus
- //---------------------------------------------------------------------------
-
- input wire set_stb,
- input wire [ 7:0] set_addr,
- input wire [31:0] set_data,
- output reg [31:0] rb_data,
- input wire [ 7:0] rb_addr,
-
- //---------------------------------------------------------------------------
- // AXI Stream Interface
- //---------------------------------------------------------------------------
-
- // Input
- input wire [DATA_WIDTH-1:0] i_tdata,
- input wire i_tvalid,
- input wire i_tlast,
- output wire i_tready,
-
- // Output
- output wire [DATA_WIDTH-1:0] o_tdata,
- output wire o_tvalid,
- output wire o_tlast,
- input wire o_tready,
-
- //---------------------------------------------------------------------------
- // DMA Interface
- //---------------------------------------------------------------------------
-
- // Write interface
- output reg [ ADDR_WIDTH-1:0] write_addr, // Byte address for start of write
- // transaction (64-bit aligned).
- output reg [COUNT_WIDTH-1:0] write_count, // Count of 64-bit words to write, minus 1.
- output reg write_ctrl_valid,
- input wire write_ctrl_ready,
- output wire [ DATA_WIDTH-1:0] write_data,
- output wire write_data_valid,
- input wire write_data_ready,
-
- // Read interface
- output reg [ ADDR_WIDTH-1:0] read_addr, // Byte address for start of read
- // transaction (64-bit aligned).
- output reg [COUNT_WIDTH-1:0] read_count, // Count of 64-bit words to read, minus 1.
- output reg read_ctrl_valid,
- input wire read_ctrl_ready,
- input wire [ DATA_WIDTH-1:0] read_data,
- input wire read_data_valid,
- output wire read_data_ready
-);
-
- //---------------------------------------------------------------------------
- // Constants
- //---------------------------------------------------------------------------
-
- // Size constants
- localparam CMD_WIDTH = 32; // Command width
- localparam LINES_WIDTH = 28; // Width of cmd_num_lines
- localparam WORD_SIZE = DATA_WIDTH/8; // Size of DATA_WIDTH in bytes
-
- // Register offsets
- localparam [7:0] SR_REC_BASE_ADDR = 128;
- localparam [7:0] SR_REC_BUFFER_SIZE = 129;
- localparam [7:0] SR_REC_RESTART = 130;
- localparam [7:0] SR_REC_FULLNESS = 131;
- localparam [7:0] SR_PLAY_BASE_ADDR = 132;
- localparam [7:0] SR_PLAY_BUFFER_SIZE = 133;
- localparam [7:0] SR_RX_CTRL_COMMAND = 152; // Same offset as radio
- localparam [7:0] SR_RX_CTRL_HALT = 155; // Same offset as radio
- localparam [7:0] SR_RX_CTRL_MAXLEN = 156; // Same offset as radio
-
-
- // Memory buffering parameters:
- //
- // Log base 2 of the depth of the input and output FIFOs to use. The FIFOs
- // should be large enough to store more than a complete burst
- // (MEM_BURST_SIZE). A size of 9 (512 64-bit words) is one 36-kbit BRAM.
- localparam REC_FIFO_ADDR_WIDTH = 9; // Log2 of input/record FIFO size
- localparam PLAY_FIFO_ADDR_WIDTH = 9; // Log2 of output/playback FIFO size
- //
- // Amount of data to buffer before writing to RAM. This should be a power of
- // two so that it evenly divides the AXI_ALIGNMENT requirement. It also must
- // not exceed 2**COUNT_WIDTH (the maximum count allowed by DMA master).
- localparam MEM_BURST_SIZE = 2**COUNT_WIDTH; // Size in DATA_WIDTH-sized words
- //
- // AXI alignment requirement (4096 bytes) in DATA_WIDTH-bit words
- localparam AXI_ALIGNMENT = 4096 / WORD_SIZE;
- //
- // Clock cycles to wait before writing something less than MEM_BURST_SIZE
- // to memory.
- localparam DATA_WAIT_TIMEOUT = 31;
-
-
- //---------------------------------------------------------------------------
- // Signals
- //---------------------------------------------------------------------------
-
- // Command wires
- wire cmd_send_imm_cf, cmd_chain_cf, cmd_reload_cf, cmd_stop_cf;
- wire [LINES_WIDTH-1:0] cmd_num_lines_cf;
-
- // Settings registers signals
- wire [ ADDR_WIDTH-1:0] rec_base_addr_sr; // Byte address
- wire [ ADDR_WIDTH-1:0] rec_buffer_size_sr; // Size in bytes
- wire [ ADDR_WIDTH-1:0] play_base_addr_sr; // Byte address
- wire [ ADDR_WIDTH-1:0] play_buffer_size_sr; // Size in bytes
- reg rec_restart;
- reg rec_restart_clear;
- wire [ CMD_WIDTH-1:0] command;
- wire command_valid;
- reg play_halt;
- reg play_halt_clear;
- wire [COUNT_WIDTH:0] play_max_len_sr;
-
- // Command FIFO
- wire cmd_fifo_valid;
- reg cmd_fifo_ready;
-
- // Record Data FIFO (Input)
- wire [DATA_WIDTH-1:0] rec_fifo_o_tdata;
- wire rec_fifo_o_tvalid;
- wire rec_fifo_o_tready;
- wire [ 15:0] rec_fifo_occupied;
-
- // Playback Data FIFO (Output)
- wire [DATA_WIDTH-1:0] play_fifo_i_tdata;
- wire play_fifo_i_tvalid;
- wire play_fifo_i_tready;
- wire [ 15:0] play_fifo_space; // Free space in play_axi_fifo
-
- // Buffer usage registers
- reg [ADDR_WIDTH-1:0] rec_buffer_avail; // Amount of free buffer space in words
- reg [ADDR_WIDTH-1:0] rec_buffer_used; // Amount of occupied buffer space in words
-
-
- //---------------------------------------------------------------------------
- // Registers
- //---------------------------------------------------------------------------
-
- // Record Base Address Register. Address is a byte address. This must be a
- // multiple of 8 bytes.
- setting_reg #(
- .my_addr (SR_REC_BASE_ADDR),
- .width (ADDR_WIDTH)
- ) sr_rec_base_addr (
- .clk (clk),
- .rst (rst),
- .strobe (set_stb),
- .addr (set_addr),
- .in (set_data),
- .out (rec_base_addr_sr),
- .changed ()
- );
-
-
- // Record Buffer Size Register. This indicates the portion of the RAM
- // allocated to the record buffer, in bytes. This should be a multiple of 8
- // bytes.
- setting_reg #(
- .my_addr (SR_REC_BUFFER_SIZE),
- .width (ADDR_WIDTH)
- ) sr_rec_buffer_size (
- .clk (clk),
- .rst (rst),
- .strobe (set_stb),
- .addr (set_addr),
- .in (set_data),
- .out (rec_buffer_size_sr),
- .changed ()
- );
-
-
- // Playback Base Address Register. Address is a byte address. This must be a
- // multiple of the 8 bytes.
- setting_reg #(
- .my_addr (SR_PLAY_BASE_ADDR),
- .width (ADDR_WIDTH)
- ) sr_play_base_addr (
- .clk (clk),
- .rst (rst),
- .strobe (set_stb),
- .addr (set_addr),
- .in (set_data),
- .out (play_base_addr_sr),
- .changed ()
- );
-
-
- // Playback Buffer Size Register. This indicates the portion of the RAM
- // allocated to the record buffer, in bytes. This should be a multiple of 8
- // bytes.
- setting_reg #(
- .my_addr (SR_PLAY_BUFFER_SIZE),
- .width (ADDR_WIDTH)
- ) sr_play_buffer_size (
- .clk (clk),
- .rst (rst),
- .strobe (set_stb),
- .addr (set_addr),
- .in (set_data),
- .out (play_buffer_size_sr),
- .changed ()
- );
-
-
- // Record Buffer Restart Register. Software must write to this register after
- // updating the base address or buffer size. A write to this register means
- // we need to stop any recording in progress and reset the record buffers
- // according to the current buffer base address and size registers.
- always @(posedge clk)
- begin : sr_restart
- if(rst) begin
- rec_restart <= 1'b0;
- end else begin
- if(set_stb & (set_addr == SR_REC_RESTART)) begin
- rec_restart <= 1'b1;
- end else if (rec_restart_clear) begin
- rec_restart <= 1'b0;
- end
- end
- end
-
-
- // Halt Register. A write to this register stops any replay operation as soon
- // as the current DRAM transaction completes.
- always @(posedge clk)
- begin : sr_halt
- if(rst) begin
- play_halt <= 1'b0;
- end else begin
- if(set_stb & (set_addr == SR_RX_CTRL_HALT)) begin
- play_halt <= 1'b1;
- end else if (play_halt_clear) begin
- play_halt <= 1'b0;
- end
- end
- end
-
-
- // Play Command Register
- //
- // This register mirrors the behavior of the RFNoC RX radio block. All
- // commands are queued up in the replay command FIFO. The fields are as
- // follows.
- //
- // send_imm [31] Send command immediately (don't use time).
- //
- // chain [30] When done with num_lines, immediately run next command.
- //
- // reload [29] When done with num_lines, rerun the same command if
- // cmd_chain is set and no new command is available.
- //
- // stop [28] When done with num_lines, stop transferring if
- // cmd_chain is set.
- //
- // num_lines [27:0] Number of samples to transfer to/from block.
- //
- setting_reg #(
- .my_addr (SR_RX_CTRL_COMMAND),
- .width (CMD_WIDTH)
- ) sr_command (
- .clk (clk),
- .rst (rst),
- .strobe (set_stb),
- .addr (set_addr),
- .in (set_data),
- .out (command),
- .changed (command_valid)
- );
-
-
- // Max Length Register. This register sets the number of words for the
- // maximum packet size.
- setting_reg #(
- .my_addr (SR_RX_CTRL_MAXLEN),
- .width (COUNT_WIDTH+1),
- .at_reset({1'b1, {COUNT_WIDTH{1'b0}}})
- ) sr_max_len (
- .clk (clk),
- .rst (rst),
- .strobe (set_stb),
- .addr (set_addr),
- .in (set_data),
- .out (play_max_len_sr),
- .changed ()
- );
-
-
- // Implement register read
- always @(*) begin
- case (rb_addr)
- SR_REC_BASE_ADDR : rb_data = rec_base_addr_sr;
- SR_REC_BUFFER_SIZE : rb_data = rec_buffer_size_sr;
- SR_REC_FULLNESS : rb_data = rec_buffer_used * WORD_SIZE;
- SR_PLAY_BASE_ADDR : rb_data = play_base_addr_sr;
- SR_PLAY_BUFFER_SIZE : rb_data = play_buffer_size_sr;
- SR_RX_CTRL_MAXLEN : rb_data = play_max_len_sr;
- default : rb_data = 32'h0;
- endcase
- end
-
-
- //---------------------------------------------------------------------------
- // Playback Command FIFO
- //---------------------------------------------------------------------------
- //
- // This block queues up commands for playback control.
- //
- //---------------------------------------------------------------------------
-
- axi_fifo_short #(
- .WIDTH (CMD_WIDTH)
- ) command_fifo (
- .clk (clk),
- .reset (rst),
- .clear (play_halt_clear),
- .i_tdata (command),
- .i_tvalid (command_valid),
- .i_tready (),
- .o_tdata ({cmd_send_imm_cf, cmd_chain_cf, cmd_reload_cf, cmd_stop_cf, cmd_num_lines_cf}),
- .o_tvalid (cmd_fifo_valid),
- .o_tready (cmd_fifo_ready),
- .occupied (),
- .space ()
- );
-
-
- //---------------------------------------------------------------------------
- // Record Input Data FIFO
- //---------------------------------------------------------------------------
- //
- // This FIFO stores data to be recording into the RAM buffer.
- //
- //---------------------------------------------------------------------------
-
- axi_fifo #(
- .WIDTH (DATA_WIDTH),
- .SIZE (REC_FIFO_ADDR_WIDTH)
- ) rec_axi_fifo (
- .clk (clk),
- .reset (rst),
- .clear (1'b0),
- //
- .i_tdata (i_tdata),
- .i_tvalid (i_tvalid),
- .i_tready (i_tready),
- //
- .o_tdata (rec_fifo_o_tdata),
- .o_tvalid (rec_fifo_o_tvalid),
- .o_tready (rec_fifo_o_tready),
- //
- .space (),
- .occupied (rec_fifo_occupied)
- );
-
-
- //---------------------------------------------------------------------------
- // Record State Machine
- //---------------------------------------------------------------------------
-
- // FSM States
- localparam REC_WAIT_FIFO = 0;
- localparam REC_CHECK_ALIGN = 1;
- localparam REC_DMA_REQ = 2;
- localparam REC_WAIT_DMA_START = 3;
- localparam REC_WAIT_DMA_COMMIT = 4;
-
- // State Signals
- reg [2:0] rec_state;
-
- // Registers
- reg [ADDR_WIDTH-1:0] rec_base_addr; // Last base address pulled from settings register
- reg [ADDR_WIDTH-1:0] rec_buffer_size; // Last buffer size pulled from settings register
- reg [ADDR_WIDTH-1:0] rec_addr; // Current offset into record buffer
- reg [ADDR_WIDTH-1:0] rec_size; // Number of words to transfer next
- reg [ADDR_WIDTH-1:0] rec_size_0; // Pipeline stage for computation of rec_size
-
- reg signed [ADDR_WIDTH:0] rec_size_aligned; // rec_size reduced to not cross 4k boundary
-
- // Timer to count how many cycles we've been waiting for new data
- reg [$clog2(DATA_WAIT_TIMEOUT+1)-1:0] rec_wait_timer;
- reg rec_wait_timeout;
-
- always @(posedge clk) begin
- if (rst) begin
- rec_state <= REC_WAIT_FIFO;
- rec_addr <= 0;
- write_ctrl_valid <= 1'b0;
-
- rec_buffer_avail <= 0;
- rec_buffer_used <= 0;
- rec_wait_timer <= 0;
- rec_wait_timeout <= 0;
-
- end else begin
-
- // Default assignments
- rec_restart_clear <= 1'b0;
-
- // Update wait timer
- if (i_tvalid || !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;
- rec_wait_timeout <= 1'b0;
- end else if (rec_fifo_occupied) begin
- // If no new word is written, but there's data in the FIFO, update the
- // timer. Latch timeout condition when we reach out limit.
- rec_wait_timer <= rec_wait_timer + 1;
-
- if (rec_wait_timer == DATA_WAIT_TIMEOUT) begin
- rec_wait_timeout <= 1'b1;
- end
- end
-
- // Pre-calculate the aligned size
- rec_size_aligned <= $signed(AXI_ALIGNMENT) - $signed(rec_addr & (AXI_ALIGNMENT-1));
-
- //
- // State logic
- //
- case (rec_state)
-
- REC_WAIT_FIFO : begin
- // Wait until there's enough data to initiate a transfer from the
- // FIFO to the RAM.
-
- // Check if a restart was requested on the record interface
- if (rec_restart) begin
- rec_restart_clear <= 1'b1;
-
- // Latch the new register values. We don't want them to change
- // while we're running.
- rec_base_addr <= rec_base_addr_sr;
- rec_buffer_size <= rec_buffer_size_sr / WORD_SIZE; // Store size in words
-
- // Reset counters and address any time we update the buffer size or
- // base address.
- rec_buffer_avail <= rec_buffer_size_sr / WORD_SIZE; // Store size in words
- rec_buffer_used <= 0;
- rec_addr <= rec_base_addr_sr;
-
- // Check if there's room left in the record RAM buffer
- end else if (rec_buffer_used < rec_buffer_size) begin
- // See if we can transfer a full burst
- if (rec_fifo_occupied >= MEM_BURST_SIZE && rec_buffer_avail >= MEM_BURST_SIZE) begin
- rec_size_0 <= MEM_BURST_SIZE;
- rec_state <= REC_CHECK_ALIGN;
-
- // Otherwise, if we've been waiting a long time, see if we can
- // transfer less than a burst.
- end else if (rec_fifo_occupied > 0 && rec_wait_timeout) begin
- rec_size_0 <= (rec_fifo_occupied <= rec_buffer_avail) ?
- rec_fifo_occupied : rec_buffer_avail;
- rec_state <= REC_CHECK_ALIGN;
- end
- end
- end
-
- REC_CHECK_ALIGN : begin
- // Check the address alignment, since AXI requires that an access not
- // cross 4k boundaries (boo), and the axi_dma_master doesn't handle
- // this automatically (boo again).
- rec_size <= ($signed({1'b0,rec_size_0}) > rec_size_aligned) ?
- rec_size_aligned : rec_size_0;
-
- // DMA interface is ready, so transaction will begin
- rec_state <= REC_DMA_REQ;
- end
-
- REC_DMA_REQ : begin
- // The write count written to the DMA engine should be 1 less than
- // the number of words you want to write (not the number of bytes).
- write_count <= rec_size - 1;
-
- // Create the physical RAM byte address by combining the address and
- // base address.
- write_addr <= rec_addr;
-
- // Once the interface is ready, make the DMA request
- if (write_ctrl_ready) begin
- // Request the write transaction
- write_ctrl_valid <= 1'b1;
- rec_state <= REC_WAIT_DMA_START;
- end
- end
-
- REC_WAIT_DMA_START : begin
- // Wait until DMA interface deasserts ready, indicating it has
- // started on the request.
- write_ctrl_valid <= 1'b0;
- if (!write_ctrl_ready) begin
- rec_state <= REC_WAIT_DMA_COMMIT;
- end
- end
-
- REC_WAIT_DMA_COMMIT : begin
- // Wait for the DMA interface to reassert write_ctrl_ready, which
- // signals that the DMA engine has received a response for the whole
- // write transaction and (we assume) it has been committed to RAM.
- // After this, we can update the write address and start the next
- // transaction.
- if (write_ctrl_ready) begin
- rec_addr <= rec_addr + (rec_size * WORD_SIZE);
- rec_buffer_used <= rec_buffer_used + rec_size;
- rec_buffer_avail <= rec_buffer_avail - rec_size;
- rec_state <= REC_WAIT_FIFO;
- end
- end
-
- default : begin
- rec_state <= REC_WAIT_FIFO;
- end
-
- endcase
- end
- end
-
- // Connect output of record FIFO to input of DMA write interface
- assign write_data = rec_fifo_o_tdata;
- assign write_data_valid = rec_fifo_o_tvalid;
- assign rec_fifo_o_tready = write_data_ready;
-
-
- //---------------------------------------------------------------------------
- // Playback State Machine
- //---------------------------------------------------------------------------
-
- // FSM States
- localparam PLAY_IDLE = 0;
- localparam PLAY_WAIT_DATA_READY = 1;
- localparam PLAY_SIZE_CALC = 2;
- localparam PLAY_DMA_REQ = 3;
- localparam PLAY_WAIT_DMA_START = 4;
- localparam PLAY_WAIT_DMA_COMMIT = 5;
- localparam PLAY_DONE_CHECK = 6;
-
- // State Signals
- reg [2:0] play_state;
-
- // Registers
- reg [ADDR_WIDTH-1:0] play_base_addr; // Last base address pulled from settings register
- reg [ADDR_WIDTH-1:0] play_buffer_size; // Last buffer size pulled from settings register
- reg [ADDR_WIDTH-1:0] play_addr; // Current byte offset into record buffer
- reg [ADDR_WIDTH-1:0] play_addr_0; // Pipeline stage for computing play_addr
- reg [ADDR_WIDTH-1:0] play_addr_1; // Pipeline stage for computing play_addr
- reg [ADDR_WIDTH-1:0] play_buffer_end; // Address of location after end of buffer
- reg [ADDR_WIDTH-1:0] max_dma_size; // Maximum size of next transfer, in words
- //
- reg [LINES_WIDTH-1:0] cmd_num_lines; // Copy of cmd_num_lines from last command
- reg [LINES_WIDTH-1:0] play_words_remaining; // Number of lines left to read for command
- reg cmd_chain; // Copy of cmd_chain from last command
- reg cmd_reload; // Copy of cmd_reload from last command
-
- reg play_full_burst_avail; // True if we there's a full burst to read
- reg play_buffer_avail_nonzero; // True if > 0
- reg cmd_num_lines_cf_nonzero; // True if > 0
- reg max_dma_size_ok; // True if it's OK to read max_dma_size
-
- reg [ADDR_WIDTH-1:0] max_dma_size_m1; // max_dma_size - 1
- reg [ADDR_WIDTH-1:0] play_words_remaining_m1; // play_words_remaining - 1
-
- reg [ADDR_WIDTH-1:0] play_buffer_avail; // Number of words left to read in record buffer
- reg [ADDR_WIDTH-1:0] play_buffer_avail_0; // Pipeline stage for computing play_buffer_avail
-
- always @(posedge clk)
- begin
- if (rst) begin
- play_state <= PLAY_IDLE;
- cmd_fifo_ready <= 1'b0;
-
- end else begin
-
- // Calculate how many words are left to read from the record buffer
- play_full_burst_avail <= (play_buffer_avail >= MEM_BURST_SIZE);
- play_buffer_avail_nonzero <= (play_buffer_avail > 0);
- cmd_num_lines_cf_nonzero <= (cmd_num_lines_cf > 0);
- play_buffer_end <= play_base_addr_sr + play_buffer_size_sr;
-
- // Default values
- cmd_fifo_ready <= 1'b0;
- read_ctrl_valid <= 1'b0;
- play_halt_clear <= 1'b0;
-
- //
- // State logic
- //
- case (play_state)
- PLAY_IDLE : begin
- // Always start reading at the start of the record buffer
- play_addr <= play_base_addr_sr;
-
- // Save off command info, in case we need to repeat the command
- cmd_num_lines <= cmd_num_lines_cf;
- cmd_reload <= cmd_reload_cf;
- cmd_chain <= cmd_chain_cf;
-
- // Save the buffer info so it doesn't update during playback
- play_base_addr <= play_base_addr_sr;
- play_buffer_size <= play_buffer_size_sr;
- play_buffer_avail <= play_buffer_size_sr / WORD_SIZE;
-
- // Wait until we receive a command and we have enough data recorded
- // to honor it.
- if (cmd_fifo_valid && ~play_halt_clear) begin
- // Load the number of word remaining to complete this command
- play_words_remaining <= cmd_num_lines_cf;
-
- // We don't support time yet, so we require send_imm to do
- // anything. Also, we can't do anything until we have data recorded.
- if (cmd_stop_cf) begin
- // Do nothing, except clear command from the FIFO
- cmd_fifo_ready <= 1'b1;
- end else if (cmd_send_imm_cf
- && play_buffer_avail_nonzero
- && cmd_num_lines_cf_nonzero) begin
- // Dequeue the command from the FIFO
- cmd_fifo_ready <= 1'b1;
-
- play_state <= PLAY_WAIT_DATA_READY;
- end
- end else if (play_halt) begin
- // In case we get a HALT after a command has finished
- play_halt_clear <= 1'b1;
- end
- end
-
- PLAY_WAIT_DATA_READY : begin
- // Save the maximum size we can read from RAM
- max_dma_size <= play_full_burst_avail ? MEM_BURST_SIZE : play_buffer_avail;
-
- // Check if we got a halt command while waiting
- if (play_halt) begin
- play_halt_clear <= 1'b1;
- play_state <= PLAY_IDLE;
-
- // Wait for output FIFO to empty sufficiently so we can read an
- // entire burst at once. This may be more space than needed, but we
- // won't know the exact size until the next state.
- end else if (play_fifo_space >= MEM_BURST_SIZE) begin
- play_state <= PLAY_SIZE_CALC;
- end
- end
-
- PLAY_SIZE_CALC : begin
- // Do some intermediate calculations to determine what the read_count
- // should be.
- play_words_remaining_m1 <= play_words_remaining-1;
- max_dma_size_m1 <= max_dma_size-1;
- max_dma_size_ok <= play_words_remaining >= max_dma_size;
- play_state <= PLAY_DMA_REQ;
- end
-
- PLAY_DMA_REQ : begin
- // Load the size of the next read into a register. We try to read the
- // max amount available (up to the burst size) or however many words
- // are needed to reach the end of the RAM buffer.
- //
- // The read count written to the DMA engine should be 1 less than the
- // number of words you want to read (not the number of bytes).
- read_count <= max_dma_size_ok ? max_dma_size_m1 : play_words_remaining_m1;
-
- // Load the address to read. Note that we don't do an alignment check
- // since we assume that multiples of MEM_BURST_SIZE meet the
- // AXI_ALIGNMENT requirement.
- read_addr <= play_addr;
-
- // Request the read transaction as soon as DMA interface is ready
- if (read_ctrl_ready) begin
- read_ctrl_valid <= 1'b1;
- play_state <= PLAY_WAIT_DMA_START;
- end
- end
-
- PLAY_WAIT_DMA_START : begin
- // Wait until DMA interface deasserts ready, indicating it has
- // started on the request.
- read_ctrl_valid <= 1'b0;
- if (!read_ctrl_ready) begin
- // Update values for next transaction
- play_addr_0 <= play_addr + ({{(ADDR_WIDTH-COUNT_WIDTH){1'b0}}, read_count} + 1) * WORD_SIZE;
- play_words_remaining <= play_words_remaining - ({1'b0, read_count} + 1);
- play_buffer_avail_0 <= play_buffer_avail - ({1'b0, read_count} + 1);
-
- play_state <= PLAY_WAIT_DMA_COMMIT;
- end
- end
-
- PLAY_WAIT_DMA_COMMIT : begin
- // Wait for the DMA interface to reassert read_ctrl_ready, which
- // signals that the DMA engine has received a response for the whole
- // read transaction.
- if (read_ctrl_ready) begin
- // Check if we need to wrap the address for the next transaction
- if (play_addr_0 >= play_buffer_end) begin
- play_addr_1 <= play_base_addr_sr;
- play_buffer_avail <= play_buffer_size_sr / WORD_SIZE;
- end else begin
- play_addr_1 <= play_addr_0;
- play_buffer_avail <= play_buffer_avail_0;
- end
-
- play_state <= PLAY_DONE_CHECK;
- end
- end
-
- PLAY_DONE_CHECK : begin
- play_addr <= play_addr_1;
-
- // Check if we have more data to transfer for this command
- if (play_words_remaining) begin
- play_state <= PLAY_WAIT_DATA_READY;
-
- // Check if we're chaining
- end else if (cmd_chain) begin
- // Check if there's a new command waiting
- if (cmd_fifo_valid) begin
- // Load the next command. Note that we don't reset the playback
- // address when commands are chained together.
- play_words_remaining <= cmd_num_lines_cf;
- cmd_num_lines <= cmd_num_lines_cf;
- cmd_reload <= cmd_reload_cf;
- cmd_chain <= cmd_chain_cf;
-
- // Dequeue the command from the FIFO
- cmd_fifo_ready <= 1'b1;
-
- // Stop if it's a stop command, otherwise restart
- if (cmd_stop_cf) begin
- play_state <= PLAY_IDLE;
- end else begin
- play_state <= PLAY_WAIT_DATA_READY;
- end
-
- // Check if we need to restart the previous command
- end else if (cmd_reload) begin
- play_words_remaining <= cmd_num_lines;
- play_state <= PLAY_WAIT_DATA_READY;
- end
- // Nothing left to do
- end else begin
- play_state <= PLAY_IDLE;
- end
- end
- endcase
-
- end
- end
-
- // Connect output of DMA master to playback data FIFO
- assign play_fifo_i_tdata = read_data;
- assign play_fifo_i_tvalid = read_data_valid;
- assign read_data_ready = play_fifo_i_tready;
-
-
- //---------------------------------------------------------------------------
- // TLAST Generation
- //---------------------------------------------------------------------------
- //
- // This block monitors the signals to/from the DMA master and generates the
- // TLAST signal. We assert TLAST at the end of every read transaction and
- // after every play_max_len_sr words, so that no packets are longer than the
- // length indicated by the max_len register.
- //
- // The timing of this block relies on the fact that read_ctrl_ready is not
- // reasserted by the DMA master until after TLAST gets asserted.
- //
- //---------------------------------------------------------------------------
-
- reg [COUNT_WIDTH-1:0] read_counter;
- reg [COUNT_WIDTH-1:0] length_counter;
- reg play_fifo_i_tlast;
-
- always @(posedge clk)
- begin
- if (rst) begin
- play_fifo_i_tlast <= 1'b0;
- 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;
- length_counter <= play_max_len_sr;
-
- // 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;
- end
-
- // Track the number of words read out by DMA master
- end else if (read_data_valid && read_data_ready) begin
- read_counter <= read_counter - 1;
- length_counter <= length_counter - 1;
-
- // 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 DMA
- // 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 <= play_max_len_sr;
-
- // 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
- end
-
- end
- end
-
-
- //---------------------------------------------------------------------------
- // Playback Output Data FIFO
- //---------------------------------------------------------------------------
- //
- // This FIFO buffers data that has been read out of RAM as part of a playback
- // operation.
- //
- //---------------------------------------------------------------------------
-
- axi_fifo #(
- .WIDTH (DATA_WIDTH+1),
- .SIZE (PLAY_FIFO_ADDR_WIDTH)
- ) play_axi_fifo (
- .clk (clk),
- .reset (rst),
- .clear (1'b0),
- //
- .i_tdata ({play_fifo_i_tlast, play_fifo_i_tdata}),
- .i_tvalid (play_fifo_i_tvalid),
- .i_tready (play_fifo_i_tready),
- //
- .o_tdata ({o_tlast, o_tdata}),
- .o_tvalid (o_tvalid),
- .o_tready (o_tready),
- //
- .space (play_fifo_space),
- .occupied ()
- );
-
-endmodule \ No newline at end of file
diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/Makefile b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/Makefile
new file mode 100644
index 000000000..21d42d71d
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/Makefile
@@ -0,0 +1,45 @@
+#
+# Copyright 2020 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: LGPL-3.0-or-later
+#
+
+#-------------------------------------------------
+# Top-of-Makefile
+#-------------------------------------------------
+# Define BASE_DIR to point to the "top" dir
+BASE_DIR = $(abspath ../../../../top)
+# Include viv_sim_preamble after defining BASE_DIR
+include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak
+
+#-------------------------------------------------
+# Design Specific
+#-------------------------------------------------
+# Include makefiles and sources for the DUT and its
+# dependencies.
+include $(BASE_DIR)/../lib/rfnoc/core/Makefile.srcs
+include $(BASE_DIR)/../lib/rfnoc/utils/Makefile.srcs
+include Makefile.srcs
+
+DESIGN_SRCS += $(abspath \
+$(RFNOC_CORE_SRCS) \
+$(RFNOC_UTIL_SRCS) \
+$(RFNOC_OOT_SRCS) \
+)
+
+#-------------------------------------------------
+# Testbench Specific
+#-------------------------------------------------
+SIM_TOP = rfnoc_block_replay_all_tb
+SIM_SRCS = \
+$(abspath ../rfnoc_block_axi_ram_fifo/sim_axi_ram.sv) \
+$(abspath rfnoc_block_replay_tb.sv) \
+$(abspath rfnoc_block_replay_all_tb.sv) \
+
+#-------------------------------------------------
+# Bottom-of-Makefile
+#-------------------------------------------------
+# Include all simulator specific makefiles here
+# Each should define a unique target to simulate
+# e.g. xsim, vsim, etc and a common "clean" target
+include $(BASE_DIR)/../tools/make/viv_simulator.mak
diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/Makefile.srcs b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/Makefile.srcs
new file mode 100644
index 000000000..e968005b5
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/Makefile.srcs
@@ -0,0 +1,24 @@
+#
+# Copyright 2020 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: LGPL-3.0-or-later
+#
+
+##################################################
+# RFNoC Block Sources
+##################################################
+# Here, list all the files that are necessary to synthesize this block. Don't
+# include testbenches!
+# Make sure that the source files are nicely detectable by a regex. Best to put
+# one on each line.
+# The first argument to addprefix is the current path to this Makefile, so the
+# path list is always absolute, regardless of from where we're including or
+# calling this file. RFNOC_OOT_SRCS needs to be a simply expanded variable
+# (not a recursively expanded variable), and we take care of that in the build
+# infrastructure.
+RFNOC_OOT_SRCS += $(addprefix $(dir $(abspath $(lastword $(MAKEFILE_LIST)))), \
+rfnoc_block_replay_regs.vh \
+axis_replay.v \
+noc_shell_replay.v \
+rfnoc_block_replay.v \
+)
diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/axis_replay.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/axis_replay.v
new file mode 100644
index 000000000..04a3adf3d
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/axis_replay.v
@@ -0,0 +1,1146 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: axis_replay.v
+//
+// Description:
+//
+// This block implements the registers, state machines, and control logic for
+// recording and playback of AXI-Stream data using an attached memory as a
+// buffer. It has a set of registers for controlling recording and a set of
+// registers for controlling playback. See rfnoc_replay_regs.vh for a
+// description of the registers.
+//
+// RECORDING
+//
+// The AXI-Stream data received on the input port is written to the attached
+// memory into a buffer space configured by the record registers. The
+// REG_REC_BASE_ADDR register indicates the starting address for the record
+// buffer and REG_REC_BUFFER_SIZE indicates how much memory to allocate for
+// recording. REG_REC_FULLNESS can be used to determine how much data has
+// been buffered. Once the configured buffer size has filled, the block stops
+// accepting data. That is, it will deassert i_tready to stall any input
+// data. Recording can be restarted (REG_REC_RESTART) to accept the remaining
+// data and write it at the beginning of the configured buffer.
+//
+// PLAYBACK
+//
+// Playback is completely independent of recording. The playback buffer is
+// configured similarly using its own registers. Playback is started by
+// writing a command to the REG_PLAY_CMD register. The play command indicates
+// if it should play a fixed number of words then stop (PLAY_CMD_FINITE),
+// playback forever (PLAY_CMD_CONTINUOUS), or stop playback (PLAY_CMD_STOP).
+// The number of words to play back with PLAY_CMD_FINITE is set by first
+// writing to REG_PLAY_CMD_NUM_WORDS.
+//
+// The length of the packets generated during playback is configured by the
+// REG_PLAY_WORDS_PER_PKT register.
+//
+// A timestamp for playback can also be specified by setting
+// REG_PLAY_CMD_TIME and setting the REG_PLAY_TIMED_POS bit as part of the
+// command write. The timestamp will then be included in all output packets,
+// starting with the provided timestamp value and auto-incrementing by one
+// for every REG_ITEM_SIZE bytes of data in each packet.
+//
+// When playback reaches the end of the configured playback buffer, if more
+// words were requested, it will loop back to the beginning of the buffer to
+// continue playing data. The last packet of playback will always have the
+// EOB flag set (e.g., after REG_PLAY_CMD_NUM_WORDS have been played back or
+// after PLAY_CMD_STOP has been issued).
+//
+// MEMORY SHARING
+//
+// Because the record and playback logic share the same memory and can
+// operate independently, care must be taken to manage the record and
+// playback buffers. You should ensure that recording is complete before
+// trying to play back the recorded data. Simultaneous recording and playing
+// back is allowed, but is only recommended when the recording and playback
+// are to different sections of memory, such that unintended overlap of the
+// write/read pointers will never occur.
+//
+// Furthermore, if multiple replay modules are instantiated and share the
+// same external memory, care must be taken to not unintentionally affect the
+// contents of neighboring buffers.
+//
+// MEMORY WORD SIZE
+//
+// The address and size registers are in terms of bytes. But playback and
+// recording length and fullness are in terms of memory words (MEM_DATA_W
+// bits wide). The current implementation can't read/write to the memory in
+// units other than the memory word size. So care must be taken to ensure
+// that REG_PLAY_CMD_NUM_WORDS and REG_PLAY_WORDS_PER_PKT always indicate the
+// number of memory words intended. The number of samples to playback or
+// record must always represent an amount of data that is a multiple of the
+// memory word size.
+//
+
+`default_nettype none
+
+
+module axis_replay #(
+ parameter MEM_DATA_W = 64,
+ parameter MEM_ADDR_W = 34, // Byte address width used by memory controller
+ parameter MEM_COUNT_W = 8 // Length of counters used to connect to the
+ // memory interface's read and write ports.
+) (
+ input wire clk,
+ input wire rst, // Synchronous to clk
+
+ //---------------------------------------------------------------------------
+ // Settings Bus
+ //---------------------------------------------------------------------------
+
+ input wire s_ctrlport_req_wr,
+ input wire s_ctrlport_req_rd,
+ input wire [19:0] s_ctrlport_req_addr,
+ input wire [31:0] s_ctrlport_req_data,
+ output reg s_ctrlport_resp_ack,
+ output reg [31:0] s_ctrlport_resp_data,
+
+ //---------------------------------------------------------------------------
+ // AXI Stream Interface
+ //---------------------------------------------------------------------------
+
+ // Input
+ input wire [MEM_DATA_W-1:0] i_tdata,
+ input wire i_tvalid,
+ input wire i_tlast,
+ output wire i_tready,
+
+ // Output
+ output wire [MEM_DATA_W-1:0] o_tdata,
+ output wire [ 63:0] o_ttimestamp,
+ output wire o_thas_time,
+ output wire o_teob,
+ output wire o_tvalid,
+ output wire o_tlast,
+ input wire o_tready,
+
+ //---------------------------------------------------------------------------
+ // Memory Interface
+ //---------------------------------------------------------------------------
+
+ // Write interface
+ output reg [ MEM_ADDR_W-1:0] write_addr, // Byte address for start of write
+ // transaction (64-bit aligned).
+ output reg [MEM_COUNT_W-1:0] write_count, // Count of 64-bit words to write, minus 1.
+ output reg write_ctrl_valid,
+ input wire write_ctrl_ready,
+ output wire [ MEM_DATA_W-1:0] write_data,
+ output wire write_data_valid,
+ input wire write_data_ready,
+
+ // Read interface
+ output reg [ MEM_ADDR_W-1:0] read_addr, // Byte address for start of read
+ // transaction (64-bit aligned).
+ output reg [MEM_COUNT_W-1:0] read_count, // Count of 64-bit words to read, minus 1.
+ output reg read_ctrl_valid,
+ input wire read_ctrl_ready,
+ input wire [ MEM_DATA_W-1:0] read_data,
+ input wire read_data_valid,
+ output wire read_data_ready
+);
+
+ `include "rfnoc_block_replay_regs.vh"
+
+ //---------------------------------------------------------------------------
+ // Constants
+ //---------------------------------------------------------------------------
+
+ localparam [REG_MAJOR_LEN-1:0] COMPAT_MAJOR = 1;
+ localparam [REG_MINOR_LEN-1:0] COMPAT_MINOR = 0;
+
+ localparam [REG_ITEM_SIZE_LEN-1:0] DEFAULT_ITEM_SIZE = 4; // 4 bytes for sc16
+
+ localparam NUM_WORDS_W = REG_CMD_NUM_WORDS_LEN;
+ localparam TIME_W = REG_CMD_TIME_LEN;
+ localparam CMD_W = REG_PLAY_CMD_LEN;
+ localparam WPP_W = REG_PLAY_WORDS_PER_PKT_LEN;
+ localparam MEM_SIZE_W = MEM_ADDR_W + 1; // Number of bits needed to
+ // represent memory size in bytes.
+
+ // Memory Alignment
+ //
+ // Size of DATA_WIDTH in bytes
+ localparam BYTES_PER_WORD = MEM_DATA_W/8;
+ //
+ // The lower MEM_ALIGN bits for all memory byte addresses should be 0.
+ localparam MEM_ALIGN = $clog2(MEM_DATA_W / 8);
+ //
+ // AXI alignment requirement (4096 bytes) in MEM_DATA_W-bit words
+ localparam AXI_ALIGNMENT = 4096 / BYTES_PER_WORD;
+
+ // Memory Buffering Parameters
+ //
+ // Log base 2 of the depth of the input and output FIFOs to use. The FIFOs
+ // should be large enough to store more than a complete burst
+ // (MEM_BURST_LEN). A size of 9 (512 64-bit words) is one 36-kbit BRAM.
+ localparam REC_FIFO_ADDR_WIDTH = 9; // Log2 of input/record FIFO size
+ localparam PLAY_FIFO_ADDR_WIDTH = 9; // Log2 of output/playback FIFO size
+ localparam HDR_FIFO_ADDR_WIDTH = 5; // Log2 of output/time FIFO size
+ //
+ // Amount of data to buffer before writing to RAM. It must not exceed
+ // 2**MEM_COUNT_W (the maximum count allowed by an AXI master).
+ localparam MEM_BURST_LEN = 2**MEM_COUNT_W; // Size in MEM_DATA_W-sized words
+ //
+ // Clock cycles to wait before writing something less than MEM_BURST_LEN
+ // to memory.
+ localparam DATA_WAIT_TIMEOUT = 31;
+
+
+ //---------------------------------------------------------------------------
+ // Functions
+ //---------------------------------------------------------------------------
+
+ function integer max(input integer a, b);
+ begin
+ if (a > b) max = a;
+ else max = b;
+ end
+ endfunction
+
+ function integer min(input integer a, b);
+ begin
+ if (a < b) min = a;
+ else min = b;
+ end
+ endfunction
+
+ // This zeros the lower MEM_ALIGN bits of the input address.
+ function [MEM_SIZE_W-1:0] mem_align(input [MEM_SIZE_W-1:0] addr);
+ begin
+ mem_align = { addr[MEM_SIZE_W-1 : MEM_ALIGN], {MEM_ALIGN{1'b0}} };
+ end
+ endfunction
+
+
+ //---------------------------------------------------------------------------
+ // Data FIFO Signals
+ //---------------------------------------------------------------------------
+
+ // Record Data FIFO (Input)
+ wire [MEM_DATA_W-1:0] rec_fifo_o_tdata;
+ wire rec_fifo_o_tvalid;
+ wire rec_fifo_o_tready;
+ wire [ 15:0] rec_fifo_occupied;
+
+ // Playback Data FIFO (Output)
+ wire [MEM_DATA_W-1:0] play_fifo_i_tdata;
+ wire play_fifo_i_tvalid;
+ wire play_fifo_i_tready;
+ wire [ 15:0] play_fifo_space;
+
+
+ //---------------------------------------------------------------------------
+ // Registers
+ //---------------------------------------------------------------------------
+
+ reg [MEM_ADDR_W-1:0] reg_rec_base_addr;
+ reg [MEM_SIZE_W-1:0] reg_rec_buffer_size;
+ reg [31:0] reg_rec_fullness_hi;
+ reg rec_restart;
+ reg [MEM_ADDR_W-1:0] reg_play_base_addr;
+ reg [MEM_SIZE_W-1:0] reg_play_buffer_size;
+ reg [NUM_WORDS_W-1:0] reg_play_cmd_num_words;
+ reg [TIME_W-1:0] reg_play_cmd_time;
+ reg [CMD_W-1:0] reg_play_cmd;
+ reg reg_play_cmd_timed;
+ reg reg_play_cmd_valid;
+ reg play_cmd_stop;
+ reg clear_cmd_fifo;
+ reg [WPP_W-1:0] reg_play_words_per_pkt = REG_PLAY_WORDS_PER_PKT_INIT;
+ reg [REG_ITEM_SIZE_LEN-1:0] reg_item_size = DEFAULT_ITEM_SIZE;
+
+ wire [63:0] reg_rec_fullness;
+ reg rec_restart_clear;
+ reg play_cmd_stop_ack;
+
+ reg [REG_ITEM_SIZE_LEN-1:0] items_per_word;
+
+ // Create aligned versions of the settings registers
+ wire [MEM_ADDR_W-1:0] rec_base_addr_sr; // Byte address
+ wire [MEM_SIZE_W-1:0] rec_buffer_size_sr; // Size in bytes
+ wire [MEM_ADDR_W-1:0] play_base_addr_sr; // Byte address
+ wire [MEM_SIZE_W-1:0] play_buffer_size_sr; // Size in bytes
+
+ assign rec_base_addr_sr = mem_align(reg_rec_base_addr);
+ assign rec_buffer_size_sr = mem_align(reg_rec_buffer_size);
+ assign play_base_addr_sr = mem_align(reg_play_base_addr);
+ assign play_buffer_size_sr = mem_align(reg_play_buffer_size);
+
+ always @(posedge clk) begin
+ if (rst) begin
+ reg_rec_base_addr <= 0;
+ reg_rec_buffer_size <= 0;
+ reg_rec_fullness_hi <= 'bX;
+ reg_play_base_addr <= 0;
+ reg_play_buffer_size <= 0;
+ reg_play_cmd_num_words <= 0;
+ 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;
+ rec_restart <= 0;
+ play_cmd_stop <= 0;
+ clear_cmd_fifo <= 0;
+ reg_play_cmd <= 'bX;
+ reg_play_cmd_timed <= 'bX;
+ reg_play_cmd_valid <= 0;
+ s_ctrlport_resp_data <= 'bX;
+ s_ctrlport_resp_ack <= 0;
+ end else begin
+ // Default assignments
+ s_ctrlport_resp_data <= 0;
+ s_ctrlport_resp_ack <= 0;
+ reg_play_cmd_valid <= 0;
+ clear_cmd_fifo <= 0;
+
+ if (rec_restart_clear) begin
+ rec_restart <= 0;
+ end
+
+ if (play_cmd_stop_ack) begin
+ play_cmd_stop <= 0;
+ end
+
+ //-----------------------------------------
+ // Register Reads
+ //-----------------------------------------
+
+ if (s_ctrlport_req_rd) begin
+ s_ctrlport_resp_ack <= 1;
+ case (s_ctrlport_req_addr)
+ REG_COMPAT : begin
+ s_ctrlport_resp_data[REG_MAJOR_POS+:REG_MAJOR_LEN]
+ <= COMPAT_MAJOR;
+ s_ctrlport_resp_data[REG_MINOR_POS+:REG_MINOR_LEN]
+ <= COMPAT_MINOR;
+ end
+ REG_MEM_SIZE : begin
+ s_ctrlport_resp_data[REG_DATA_SIZE_POS+:REG_DATA_SIZE_LEN]
+ <= MEM_DATA_W;
+ s_ctrlport_resp_data[REG_ADDR_SIZE_POS+:REG_ADDR_SIZE_LEN]
+ <= MEM_ADDR_W;
+ end
+ REG_REC_BASE_ADDR_LO :
+ s_ctrlport_resp_data[min(32, MEM_ADDR_W)-1:0]
+ <= reg_rec_base_addr[min(32, MEM_ADDR_W)-1:0];
+ REG_REC_BASE_ADDR_HI :
+ if (MEM_ADDR_W > 32)
+ s_ctrlport_resp_data[0 +: max(MEM_ADDR_W-32, 1)]
+ <= reg_rec_base_addr[32 +: max(MEM_ADDR_W-32, 1)];
+ REG_REC_BUFFER_SIZE_LO :
+ s_ctrlport_resp_data
+ <= reg_rec_buffer_size[min(32, MEM_SIZE_W)-1:0];
+ REG_REC_BUFFER_SIZE_HI :
+ if (MEM_SIZE_W > 32)
+ s_ctrlport_resp_data[0 +: max(MEM_SIZE_W-32, 1)]
+ <= reg_rec_buffer_size[32 +: max(MEM_SIZE_W-32, 1)];
+ REG_REC_FULLNESS_LO : begin
+ s_ctrlport_resp_data <= reg_rec_fullness[31:0];
+ if (MEM_SIZE_W > 32) begin
+ // The LO register must be read first. Save HI part now to
+ // guarantee coherence when HI register is read.
+ reg_rec_fullness_hi <= 0;
+ reg_rec_fullness_hi[0 +: max(MEM_SIZE_W-32, 1)]
+ <= reg_rec_fullness[32 +: max(MEM_SIZE_W-32, 1)];
+ end
+ end
+ REG_REC_FULLNESS_HI :
+ if (MEM_SIZE_W > 32)
+ // Return the saved value to guarantee coherence
+ s_ctrlport_resp_data <= reg_rec_fullness_hi;
+ REG_PLAY_BASE_ADDR_LO :
+ s_ctrlport_resp_data[min(32, MEM_ADDR_W)-1:0]
+ <= reg_play_base_addr[min(32, MEM_ADDR_W)-1:0];
+ REG_PLAY_BASE_ADDR_HI :
+ if (MEM_ADDR_W > 32)
+ s_ctrlport_resp_data[0 +: max(MEM_ADDR_W-32, 1)]
+ <= reg_play_base_addr[32 +: max(MEM_ADDR_W-32, 1)];
+ REG_PLAY_BUFFER_SIZE_LO :
+ s_ctrlport_resp_data[min(32, MEM_SIZE_W)-1:0]
+ <= reg_play_buffer_size[min(32, MEM_SIZE_W)-1:0];
+ REG_PLAY_BUFFER_SIZE_HI :
+ if (MEM_SIZE_W > 32)
+ s_ctrlport_resp_data[0 +: max(MEM_SIZE_W-32, 1)]
+ <= reg_play_buffer_size[32 +: max(MEM_SIZE_W-32, 1)];
+ REG_PLAY_CMD_NUM_WORDS_LO :
+ s_ctrlport_resp_data <= reg_play_cmd_num_words[31:0];
+ REG_PLAY_CMD_NUM_WORDS_HI :
+ s_ctrlport_resp_data <= reg_play_cmd_num_words[63:32];
+ REG_PLAY_CMD_TIME_LO :
+ s_ctrlport_resp_data <= reg_play_cmd_time[31:0];
+ REG_PLAY_CMD_TIME_HI :
+ s_ctrlport_resp_data <= reg_play_cmd_time[63:32];
+ REG_PLAY_WORDS_PER_PKT :
+ s_ctrlport_resp_data[WPP_W-1:0] <= reg_play_words_per_pkt;
+ REG_PLAY_ITEM_SIZE :
+ s_ctrlport_resp_data[REG_ITEM_SIZE_POS+:REG_ITEM_SIZE_LEN]
+ <= reg_item_size;
+ endcase
+
+ //-----------------------------------------
+ // Register Writes
+ //-----------------------------------------
+
+ end else if (s_ctrlport_req_wr) begin
+ s_ctrlport_resp_ack <= 1;
+ case (s_ctrlport_req_addr)
+ REG_REC_BASE_ADDR_LO :
+ reg_rec_base_addr[min(32, MEM_ADDR_W)-1:0]
+ <= s_ctrlport_req_data;
+ REG_REC_BASE_ADDR_HI :
+ if (MEM_ADDR_W > 32)
+ reg_rec_base_addr[32 +: max(MEM_ADDR_W-32, 1)]
+ <= s_ctrlport_req_data[0 +: max(MEM_ADDR_W-32, 1)];
+ REG_REC_BUFFER_SIZE_LO :
+ reg_rec_buffer_size[min(32, MEM_SIZE_W)-1:0]
+ <= s_ctrlport_req_data;
+ REG_REC_BUFFER_SIZE_HI :
+ if (MEM_SIZE_W > 32)
+ reg_rec_buffer_size[32 +: max(MEM_SIZE_W-32, 1)]
+ <= s_ctrlport_req_data[0 +: max(MEM_SIZE_W-32, 1)];
+ REG_REC_RESTART :
+ rec_restart <= 1'b1;
+ REG_PLAY_BASE_ADDR_LO :
+ reg_play_base_addr[min(32, MEM_ADDR_W)-1:0]
+ <= s_ctrlport_req_data;
+ REG_PLAY_BASE_ADDR_HI :
+ if (MEM_ADDR_W > 32)
+ reg_play_base_addr[32 +: max(MEM_ADDR_W-32, 1)]
+ <= s_ctrlport_req_data[0 +: max(MEM_ADDR_W-32, 1)];
+ REG_PLAY_BUFFER_SIZE_LO :
+ reg_play_buffer_size[min(32, MEM_SIZE_W)-1:0]
+ <= s_ctrlport_req_data;
+ REG_PLAY_BUFFER_SIZE_HI :
+ if (MEM_SIZE_W > 32)
+ reg_play_buffer_size[32 +: max(MEM_SIZE_W-32, 1)]
+ <= s_ctrlport_req_data[0 +: max(MEM_SIZE_W-32, 1)];
+ REG_PLAY_CMD_NUM_WORDS_LO :
+ reg_play_cmd_num_words[31:0] <= s_ctrlport_req_data;
+ REG_PLAY_CMD_NUM_WORDS_HI :
+ reg_play_cmd_num_words[63:32] <= s_ctrlport_req_data;
+ REG_PLAY_CMD_TIME_LO :
+ reg_play_cmd_time[31:0] <= s_ctrlport_req_data;
+ REG_PLAY_CMD_TIME_HI :
+ reg_play_cmd_time[63:32] <= s_ctrlport_req_data;
+ REG_PLAY_CMD : begin
+ reg_play_cmd <= s_ctrlport_req_data[REG_PLAY_CMD_POS+:REG_PLAY_CMD_LEN];
+ reg_play_cmd_timed <= s_ctrlport_req_data[REG_PLAY_TIMED_POS];
+ reg_play_cmd_valid <= 1'b1;
+ if (!play_cmd_stop && s_ctrlport_req_data[REG_PLAY_CMD_LEN-1:0] == PLAY_CMD_STOP) begin
+ play_cmd_stop <= 1;
+ clear_cmd_fifo <= 1;
+ end
+ end
+ REG_PLAY_WORDS_PER_PKT :
+ reg_play_words_per_pkt <= s_ctrlport_req_data[WPP_W-1:0];
+ REG_PLAY_ITEM_SIZE :
+ reg_item_size <= s_ctrlport_req_data[REG_ITEM_SIZE_POS+:REG_ITEM_SIZE_LEN];
+ endcase
+ end
+
+ // Compute the amount by which to increment time for each memory word, as
+ // indicated by reg_item_size.
+ (* parallel_case *)
+ casex (reg_item_size)
+ 8'bxxxxxxx1: items_per_word <= (MEM_DATA_W/8) >> 0;
+ 8'bxxxxxx1x: items_per_word <= (MEM_DATA_W/8) >> 1;
+ 8'bxxxxx1xx: items_per_word <= (MEM_DATA_W/8) >> 2;
+ 8'bxxxx1xxx: items_per_word <= (MEM_DATA_W/8) >> 3;
+ 8'bxxx1xxxx: items_per_word <= (MEM_DATA_W/8) >> 4;
+ 8'bxx1xxxxx: items_per_word <= (MEM_DATA_W/8) >> 5;
+ 8'bx1xxxxxx: items_per_word <= (MEM_DATA_W/8) >> 6;
+ 8'b1xxxxxxx: items_per_word <= (MEM_DATA_W/8) >> 7;
+ endcase
+
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Playback Command FIFO
+ //---------------------------------------------------------------------------
+ //
+ // This block queues up commands for playback.
+ //
+ //---------------------------------------------------------------------------
+
+ // Command FIFO Signals
+ wire [CMD_W-1:0] cmd_cf;
+ wire cmd_timed_cf;
+ wire [NUM_WORDS_W-1:0] cmd_num_words_cf;
+ wire [TIME_W-1:0] cmd_time_cf;
+ wire cmd_fifo_valid;
+ reg cmd_fifo_ready;
+
+ axi_fifo_short #(
+ .WIDTH (1 + CMD_W + NUM_WORDS_W + TIME_W)
+ ) command_fifo (
+ .clk (clk),
+ .reset (rst),
+ .clear (clear_cmd_fifo),
+ .i_tdata ({reg_play_cmd_timed, reg_play_cmd, reg_play_cmd_num_words, reg_play_cmd_time}),
+ .i_tvalid (reg_play_cmd_valid),
+ .i_tready (),
+ .o_tdata ({cmd_timed_cf, cmd_cf, cmd_num_words_cf, cmd_time_cf}),
+ .o_tvalid (cmd_fifo_valid),
+ .o_tready (cmd_fifo_ready),
+ .occupied (),
+ .space ()
+ );
+
+
+ //---------------------------------------------------------------------------
+ // Record Input Data FIFO
+ //---------------------------------------------------------------------------
+ //
+ // This FIFO stores data to be recorded into the external memory.
+ //
+ //---------------------------------------------------------------------------
+
+ axi_fifo #(
+ .WIDTH (MEM_DATA_W),
+ .SIZE (REC_FIFO_ADDR_WIDTH)
+ ) rec_axi_fifo (
+ .clk (clk),
+ .reset (rst),
+ .clear (1'b0),
+ //
+ .i_tdata (i_tdata),
+ .i_tvalid (i_tvalid),
+ .i_tready (i_tready),
+ //
+ .o_tdata (rec_fifo_o_tdata),
+ .o_tvalid (rec_fifo_o_tvalid),
+ .o_tready (rec_fifo_o_tready),
+ //
+ .space (),
+ .occupied (rec_fifo_occupied)
+ );
+
+
+ //---------------------------------------------------------------------------
+ // Record State Machine
+ //---------------------------------------------------------------------------
+
+ // FSM States
+ localparam REC_WAIT_FIFO = 0;
+ localparam REC_CHECK_ALIGN = 1;
+ localparam REC_MEM_REQ = 2;
+ localparam REC_WAIT_MEM_START = 3;
+ localparam REC_WAIT_MEM_COMMIT = 4;
+
+ // State Signals
+ reg [2:0] rec_state;
+
+ // Registers
+ reg [MEM_SIZE_W-1:0] rec_buffer_size; // Last buffer size pulled from register
+ reg [MEM_ADDR_W-1:0] rec_addr; // Current offset into record buffer
+ reg [MEM_ADDR_W-1:0] rec_size; // Number of words to transfer next
+ reg [MEM_ADDR_W-1:0] rec_size_0; // Pipeline stage for computation of rec_size
+
+ // Buffer usage registers
+ reg [MEM_SIZE_W-1:0] rec_buffer_avail; // Amount of free buffer space in words
+ reg [MEM_SIZE_W-1:0] rec_buffer_used; // Amount of occupied buffer space in words
+
+ reg [MEM_SIZE_W-1:0] rec_size_aligned; // Max record size until the next 4k boundary
+
+ // Timer to count how many cycles we've been waiting for new data
+ reg [$clog2(DATA_WAIT_TIMEOUT+1)-1:0] rec_wait_timer;
+ reg rec_wait_timeout;
+
+ assign reg_rec_fullness = rec_buffer_used * BYTES_PER_WORD;
+
+ always @(posedge clk) begin
+ if (rst) begin
+ rec_state <= REC_WAIT_FIFO;
+ write_ctrl_valid <= 1'b0;
+ rec_wait_timer <= 0;
+ rec_wait_timeout <= 0;
+ rec_buffer_avail <= 0;
+ rec_buffer_used <= 0;
+
+ // Don't care:
+ rec_addr <= {MEM_ADDR_W{1'bX}};
+ rec_size_0 <= {MEM_ADDR_W{1'bX}};
+ rec_size <= {MEM_ADDR_W{1'bX}};
+ write_count <= {MEM_COUNT_W{1'bX}};
+ write_addr <= {MEM_ADDR_W{1'bX}};
+
+ end else begin
+
+ // Default assignments
+ rec_restart_clear <= 1'b0;
+
+ // Update wait timer
+ if (i_tvalid || !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;
+ rec_wait_timeout <= 1'b0;
+ end else if (rec_fifo_occupied) begin
+ // If no new word is written, but there's data in the FIFO, update the
+ // timer. Latch timeout condition when we reach our limit.
+ rec_wait_timer <= rec_wait_timer + 1;
+
+ if (rec_wait_timer == DATA_WAIT_TIMEOUT) begin
+ rec_wait_timeout <= 1'b1;
+ end
+ end
+
+ // Pre-calculate the aligned size in words
+ rec_size_aligned <= AXI_ALIGNMENT - ((rec_addr/BYTES_PER_WORD) & (AXI_ALIGNMENT-1));
+
+ //
+ // State logic
+ //
+ case (rec_state)
+
+ REC_WAIT_FIFO : begin
+ // Wait until there's enough data to initiate a transfer from the
+ // FIFO to the RAM.
+
+ // Check if a restart was requested on the record interface
+ if (rec_restart) begin
+ rec_restart_clear <= 1'b1;
+
+ // Latch the new register values. We don't want them to change
+ // while we're running.
+ rec_buffer_size <= rec_buffer_size_sr / BYTES_PER_WORD; // Store size in words
+
+ // Reset counters and address any time we update the buffer size or
+ // base address.
+ rec_buffer_avail <= rec_buffer_size_sr / BYTES_PER_WORD; // Store size in words
+ rec_buffer_used <= 0;
+ rec_addr <= rec_base_addr_sr;
+
+ // Check if there's room left in the record RAM buffer
+ end else if (rec_buffer_used < rec_buffer_size) begin
+ // See if we can transfer a full burst
+ if (rec_fifo_occupied >= MEM_BURST_LEN && rec_buffer_avail >= MEM_BURST_LEN) begin
+ rec_size_0 <= MEM_BURST_LEN;
+ rec_state <= REC_CHECK_ALIGN;
+
+ // Otherwise, if we've been waiting a long time, see if we can
+ // transfer less than a burst.
+ end else if (rec_fifo_occupied > 0 && rec_wait_timeout) begin
+ rec_size_0 <= (rec_fifo_occupied <= rec_buffer_avail) ?
+ rec_fifo_occupied : rec_buffer_avail;
+ rec_state <= REC_CHECK_ALIGN;
+ end
+ end
+ end
+
+ REC_CHECK_ALIGN : begin
+ // Check the address alignment, since AXI requires that an access not
+ // cross 4k boundaries (boo), and the memory interface doesn't handle
+ // this automatically (boo again).
+ rec_size <= rec_size_0 > rec_size_aligned ?
+ rec_size_aligned : rec_size_0;
+
+ // Memory interface is ready, so transaction will begin
+ rec_state <= REC_MEM_REQ;
+ end
+
+ REC_MEM_REQ : begin
+ // The write count written to the memory interface should be 1 less
+ // than the number of words you want to write (not the number of
+ // bytes).
+ write_count <= rec_size - 1;
+
+ // Create the physical RAM byte address by combining the address and
+ // base address.
+ write_addr <= rec_addr;
+
+ // Once the interface is ready, make the memory request
+ if (write_ctrl_ready) begin
+ // Request the write transaction
+ write_ctrl_valid <= 1'b1;
+ rec_state <= REC_WAIT_MEM_START;
+ end
+ end
+
+ REC_WAIT_MEM_START : begin
+ // Wait until memory interface deasserts ready, indicating it has
+ // started on the request.
+ write_ctrl_valid <= 1'b0;
+ if (!write_ctrl_ready) begin
+ rec_state <= REC_WAIT_MEM_COMMIT;
+ end
+ end
+
+ REC_WAIT_MEM_COMMIT : begin
+ // Wait for the memory interface to reassert write_ctrl_ready, which
+ // signals that the interface has received a response for the whole
+ // write transaction and (we assume) it has been committed to RAM.
+ // After this, we can update the write address and start the next
+ // transaction.
+ if (write_ctrl_ready) begin
+ rec_addr <= rec_addr + (rec_size * BYTES_PER_WORD);
+ rec_buffer_used <= rec_buffer_used + rec_size;
+ rec_buffer_avail <= rec_buffer_avail - rec_size;
+ rec_state <= REC_WAIT_FIFO;
+ end
+ end
+
+ default : begin
+ rec_state <= REC_WAIT_FIFO;
+ end
+
+ endcase
+ end
+ end
+
+ // Connect output of record FIFO to input of the memory write interface
+ assign write_data = rec_fifo_o_tdata;
+ assign write_data_valid = rec_fifo_o_tvalid;
+ assign rec_fifo_o_tready = write_data_ready;
+
+
+ //---------------------------------------------------------------------------
+ // Playback State Machine
+ //---------------------------------------------------------------------------
+
+ // FSM States
+ localparam PLAY_IDLE = 0;
+ localparam PLAY_WAIT_DATA_READY = 1;
+ localparam PLAY_CHECK_ALIGN = 2;
+ localparam PLAY_SIZE_CALC = 3;
+ localparam PLAY_MEM_REQ = 4;
+ localparam PLAY_WAIT_MEM_START = 5;
+ localparam PLAY_WAIT_MEM_COMMIT = 6;
+ localparam PLAY_DONE_CHECK = 7;
+
+ // State Signals
+ reg [2:0] play_state;
+
+ // Registers
+ reg [MEM_ADDR_W-1:0] play_addr; // Current byte offset into record buffer
+ reg [ MEM_ADDR_W:0] play_addr_0; // Pipeline stage for computing play_addr.
+ // One bit larger to detect address wrapping.
+ reg [MEM_ADDR_W-1:0] play_addr_1; // Pipeline stage for computing play_addr
+ reg [MEM_SIZE_W-1:0] play_buffer_end; // Address of location after end of buffer
+ reg [MEM_ADDR_W-1:0] max_read_size; // Maximum size of next transfer, in words
+ reg [MEM_ADDR_W-1:0] next_read_size; // Actual size of next transfer, in words
+ reg [MEM_ADDR_W-1:0] play_size_aligned; // Max play size until the next 4K boundary
+ //
+ reg [NUM_WORDS_W-1:0] play_words_remaining; // Number of words left for playback command
+ reg [CMD_W-1:0] cmd; // Copy of cmd_cf from last command
+ reg cmd_timed; // Copy of cmd_timed_cf from last command
+ reg [TIME_W-1:0] cmd_time; // Copy of cmd_time_cf from last command
+ reg last_trans; // Is this the last read transaction for the command?
+
+ reg play_full_burst_avail; // True if we there's a full burst to read
+ reg play_buffer_avail_nonzero; // True if play_buffer_avail > 0
+ reg next_read_size_ok; // True if it's OK to read next_read_size
+
+ reg [MEM_ADDR_W-1:0] next_read_size_m1; // next_read_size - 1
+ reg [MEM_ADDR_W-1:0] play_words_remaining_m1; // play_words_remaining - 1
+
+ reg [MEM_SIZE_W-1:0] play_buffer_avail; // Number of words left to read in record buffer
+ reg [MEM_SIZE_W-1:0] play_buffer_avail_0; // Pipeline stage for computing play_buffer_avail
+
+ reg pause_data_transfer;
+
+ always @(posedge clk)
+ begin
+ if (rst) begin
+ play_state <= PLAY_IDLE;
+ cmd_fifo_ready <= 1'b0;
+
+ // Don't care:
+ play_full_burst_avail <= 1'bX;
+ play_buffer_avail_nonzero <= 1'bX;
+ play_buffer_end <= {MEM_SIZE_W{1'bX}};
+ read_ctrl_valid <= 1'bX;
+ play_addr <= {MEM_ADDR_W{1'bX}};
+ cmd <= {CMD_W{1'bX}};
+ cmd_time <= {TIME_W{1'bX}};
+ cmd_timed <= 1'bX;
+ play_buffer_avail <= {MEM_SIZE_W{1'bX}};
+ play_size_aligned <= {MEM_SIZE_W{1'bX}};
+ play_words_remaining <= {NUM_WORDS_W{1'bX}};
+ max_read_size <= {MEM_ADDR_W{1'bX}};
+ next_read_size <= {MEM_ADDR_W{1'bX}};
+ play_words_remaining_m1 <= {MEM_ADDR_W{1'bX}};
+ next_read_size_m1 <= {MEM_ADDR_W{1'bX}};
+ next_read_size_ok <= 1'bX;
+ read_count <= {MEM_COUNT_W{1'bX}};
+ read_addr <= {MEM_ADDR_W{1'bX}};
+ play_addr_0 <= {MEM_ADDR_W+1{1'bX}};
+ play_buffer_avail_0 <= {MEM_SIZE_W{1'bX}};
+ play_addr_1 <= {MEM_ADDR_W{1'bX}};
+ last_trans <= 1'b0;
+
+ end else begin
+
+ // Calculate how many words are left to read from the record buffer
+ play_full_burst_avail <= (play_buffer_avail >= MEM_BURST_LEN);
+ play_buffer_avail_nonzero <= (play_buffer_avail > 0);
+ play_buffer_end <= play_base_addr_sr + play_buffer_size_sr;
+
+ play_size_aligned <= AXI_ALIGNMENT - ((play_addr/BYTES_PER_WORD) & (AXI_ALIGNMENT-1));
+
+ // Default values
+ cmd_fifo_ready <= 1'b0;
+ read_ctrl_valid <= 1'b0;
+ play_cmd_stop_ack <= 1'b0;
+
+ //
+ // State logic
+ //
+ case (play_state)
+ PLAY_IDLE : begin
+ // Always start reading at the start of the record buffer
+ play_addr <= play_base_addr_sr;
+
+ // Save off command info
+ if (cmd_cf == PLAY_CMD_CONTINUOUS)
+ play_words_remaining <= MEM_BURST_LEN;
+ else
+ play_words_remaining <= cmd_num_words_cf;
+ cmd_timed <= cmd_timed_cf;
+ cmd_time <= cmd_time_cf;
+ cmd <= cmd_cf;
+
+ // Save the buffer info so it doesn't update during playback
+ play_buffer_avail <= play_buffer_size_sr / BYTES_PER_WORD;
+
+ // Wait until we receive a command and we have enough data recorded
+ // to honor it.
+ if (play_cmd_stop) begin
+ play_cmd_stop_ack <= 1'b1;
+ end else if (cmd_fifo_valid && play_buffer_avail_nonzero) begin
+ // Dequeue the command from the FIFO
+ cmd_fifo_ready <= 1'b1;
+ play_state <= PLAY_WAIT_DATA_READY;
+ end
+ end
+
+ PLAY_WAIT_DATA_READY : begin
+ // Save the maximum size we can read from RAM
+ max_read_size <= play_full_burst_avail ? MEM_BURST_LEN : play_buffer_avail;
+
+ // Wait for output FIFO to empty sufficiently so we can read an
+ // entire burst at once. This may be more space than needed, but we
+ // won't know the exact size until the next state.
+ if (play_fifo_space >= MEM_BURST_LEN) begin
+ play_state <= PLAY_CHECK_ALIGN;
+ end
+ end
+
+ PLAY_CHECK_ALIGN : begin
+ // Check the address alignment, since AXI requires that an access not
+ // cross 4k boundaries (boo), and the memory interface doesn't handle
+ // this automatically (boo again).
+ next_read_size <= max_read_size > play_size_aligned ?
+ play_size_aligned : max_read_size;
+ play_state <= PLAY_SIZE_CALC;
+ end
+
+ PLAY_SIZE_CALC : begin
+ // Do some intermediate calculations to determine what the read_count
+ // should be.
+ play_words_remaining_m1 <= play_words_remaining-1;
+ next_read_size_m1 <= next_read_size-1;
+ next_read_size_ok <= play_words_remaining >= next_read_size;
+ play_state <= PLAY_MEM_REQ;
+
+ // Check if this is the last memory transaction
+ if (play_cmd_stop) begin
+ last_trans <= 1'b1;
+ play_cmd_stop_ack <= 1'b1;
+ end else if (cmd == PLAY_CMD_CONTINUOUS) begin
+ last_trans <= 1'b0;
+ end else begin
+ // If not stopping, see if this is the last transaction for a
+ // finite playback command.
+ last_trans <= (play_words_remaining <= next_read_size);
+ end
+ end
+
+ PLAY_MEM_REQ : begin
+ // Load the size of the next read into a register. We try to read the
+ // max amount available (up to the burst size) or however many words
+ // are needed to reach the end of the RAM buffer.
+ //
+ // The read count written to the memory interface should be 1 less
+ // than the number of words you want to read (not the number of
+ // bytes).
+ read_count <= next_read_size_ok ? next_read_size_m1 : play_words_remaining_m1;
+
+ // Load the address to read
+ read_addr <= play_addr;
+
+ // Request the read transaction as soon as memory interface is ready
+ if (read_ctrl_ready) begin
+ read_ctrl_valid <= 1'b1;
+ play_state <= PLAY_WAIT_MEM_START;
+ end
+ end
+
+ PLAY_WAIT_MEM_START : begin
+ // Wait until memory interface deasserts ready, indicating it has
+ // started on the request.
+ read_ctrl_valid <= 1'b0;
+ if (!read_ctrl_ready) begin
+ // Update values for next transaction
+ play_addr_0 <= play_addr +
+ ({{(MEM_ADDR_W-MEM_COUNT_W){1'b0}}, read_count} + 1) * BYTES_PER_WORD;
+ play_words_remaining <= play_words_remaining - ({1'b0, read_count} + 1);
+ play_buffer_avail_0 <= play_buffer_avail - ({1'b0, read_count} + 1);
+
+ play_state <= PLAY_WAIT_MEM_COMMIT;
+ end
+ end
+
+ PLAY_WAIT_MEM_COMMIT : begin
+ // Wait for the memory interface to reassert read_ctrl_ready, which
+ // signals that the interface has received a response for the whole
+ // read transaction.
+ if (read_ctrl_ready) begin
+ // Check if we need to wrap the address for the next transaction.
+ if (play_addr_0 >= play_buffer_end) begin
+ play_addr_1 <= play_base_addr_sr;
+ play_buffer_avail <= play_buffer_size_sr / BYTES_PER_WORD;
+ end else begin
+ play_addr_1 <= play_addr_0[MEM_ADDR_W-1:0];
+ play_buffer_avail <= play_buffer_avail_0;
+ end
+
+ // Update the time for the first word of the next transaction
+ cmd_time <= cmd_time + (read_count + 1) * (MEM_DATA_W/32);
+
+ play_state <= PLAY_DONE_CHECK;
+ end
+ end
+
+ PLAY_DONE_CHECK : begin
+ play_addr <= play_addr_1;
+
+ // Check if we have more data to transfer for this command
+ if (cmd == PLAY_CMD_CONTINUOUS && !last_trans) begin
+ play_words_remaining <= MEM_BURST_LEN;
+ play_state <= PLAY_WAIT_DATA_READY;
+ end else if (play_words_remaining && !last_trans) begin
+ play_state <= PLAY_WAIT_DATA_READY;
+ end else begin
+ play_state <= PLAY_IDLE;
+ end
+ end
+ endcase
+
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // TLAST and Sideband Generation
+ //---------------------------------------------------------------------------
+ //
+ // 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
+ // 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 timing of this block relies on the fact that read_ctrl_ready is not
+ // reasserted by the memory interface until after TLAST gets asserted.
+ //
+ //---------------------------------------------------------------------------
+
+ 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;
+
+ 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;
+ 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;
+ 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
+ end
+
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Playback Output Data FIFO
+ //---------------------------------------------------------------------------
+ //
+ // The play_axi_fifo buffers data that has been read out of RAM as part of a
+ // playback operation.
+ //
+ //---------------------------------------------------------------------------
+
+ // Connect output of memory read interface to play_axi_fifo
+ assign play_fifo_i_tdata = read_data;
+ assign play_fifo_i_tvalid = read_data_valid & ~pause_data_transfer;
+ assign read_data_ready = play_fifo_i_tready & ~pause_data_transfer;
+
+ axi_fifo #(
+ .WIDTH (MEM_DATA_W+1),
+ .SIZE (PLAY_FIFO_ADDR_WIDTH)
+ ) play_axi_fifo (
+ .clk (clk),
+ .reset (rst),
+ .clear (1'b0),
+ //
+ .i_tdata ({play_fifo_i_tlast, play_fifo_i_tdata}),
+ .i_tvalid (play_fifo_i_tvalid),
+ .i_tready (play_fifo_i_tready),
+ //
+ .o_tdata ({o_tlast, o_tdata}),
+ .o_tvalid (o_tvalid),
+ .o_tready (o_tready),
+ //
+ .space (play_fifo_space),
+ .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
+ //---------------------------------------------------------------------------
+ //
+ // The hdr_axi_fifo contains the header information for the next packet, with
+ // one word per packet.
+ //
+ //---------------------------------------------------------------------------
+
+ 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_tready;
+
+ wire [15:0] hdr_fifo_space;
+
+ axi_fifo #(
+ .WIDTH (TIME_W+2),
+ .SIZE (HDR_FIFO_ADDR_WIDTH)
+ ) hdr_axi_fifo (
+ .clk (clk),
+ .reset (rst),
+ .clear (1'b0),
+ //
+ .i_tdata (hdr_fifo_i_tdata),
+ .i_tvalid (hdr_fifo_i_tvalid),
+ .i_tready (),
+ //
+ .o_tdata (hdr_fifo_o_tdata),
+ .o_tvalid (),
+ .o_tready (hdr_fifo_o_tready),
+ //
+ .space (hdr_fifo_space),
+ .occupied ()
+ );
+
+ assign hdr_fifo_i_tdata = {has_time, eob, time_counter};
+
+ // 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 { o_thas_time, o_teob, o_ttimestamp } = hdr_fifo_o_tdata;
+
+
+ // The following state machine prevents overflow of the hdr_axi_fifo by
+ // stopping data transfer if it is almost full. It monitors the state of the
+ // current transfer so as to not violate the AXI-Stream protocol.
+ reg hdr_fifo_almost_full;
+
+ always @(posedge clk) begin
+ if (rst) begin
+ hdr_fifo_almost_full <= 0;
+ pause_data_transfer <= 0;
+ end else begin
+ hdr_fifo_almost_full <= (hdr_fifo_space < 4);
+
+ if (pause_data_transfer) begin
+ if (!hdr_fifo_almost_full) pause_data_transfer <= 0;
+ end else begin
+ // If we're not asserting tvalid, or we're completing a transfer this
+ // cycle, then it is safe to gate tvalid on the next cycle.
+ if (hdr_fifo_almost_full &&
+ (!play_fifo_i_tvalid || (play_fifo_i_tvalid && play_fifo_i_tready))) begin
+ pause_data_transfer <= 1;
+ end
+ end
+ end
+ end
+
+endmodule
+
+
+`default_nettype wire
diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/noc_shell_replay.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/noc_shell_replay.v
new file mode 100644
index 000000000..55ab08b51
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/noc_shell_replay.v
@@ -0,0 +1,306 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: noc_shell_replay
+//
+// Description:
+//
+// This is a tool-generated NoC-shell for the replay block.
+// See the RFNoC specification for more information about NoC shells.
+//
+// Parameters:
+//
+// THIS_PORTID : Control crossbar port to which this block is connected
+// CHDR_W : AXIS-CHDR data bus width
+// MTU : Maximum transmission unit (i.e., maximum packet size in
+//
+
+`default_nettype none
+
+
+module noc_shell_replay #(
+ parameter [9:0] THIS_PORTID = 10'd0,
+ parameter CHDR_W = 64,
+ parameter [5:0] MTU = 10,
+ parameter NUM_PORTS = 2,
+ parameter MEM_DATA_W = 64,
+ parameter MEM_ADDR_W = 30
+) (
+ //---------------------
+ // Framework Interface
+ //---------------------
+
+ // RFNoC Framework Clocks
+ input wire rfnoc_chdr_clk,
+ input wire rfnoc_ctrl_clk,
+ input wire mem_clk,
+
+ // NoC Shell Generated Resets
+ output wire rfnoc_chdr_rst,
+ output wire rfnoc_ctrl_rst,
+ output wire mem_rst,
+
+ // RFNoC Backend Interface
+ input wire [511:0] rfnoc_core_config,
+ output wire [511:0] rfnoc_core_status,
+
+ // AXIS-CHDR Input Ports (from framework)
+ input wire [(0+NUM_PORTS)*CHDR_W-1:0] s_rfnoc_chdr_tdata,
+ input wire [(0+NUM_PORTS)-1:0] s_rfnoc_chdr_tlast,
+ input wire [(0+NUM_PORTS)-1:0] s_rfnoc_chdr_tvalid,
+ output wire [(0+NUM_PORTS)-1:0] s_rfnoc_chdr_tready,
+ // AXIS-CHDR Output Ports (to framework)
+ output wire [(0+NUM_PORTS)*CHDR_W-1:0] m_rfnoc_chdr_tdata,
+ output wire [(0+NUM_PORTS)-1:0] m_rfnoc_chdr_tlast,
+ output wire [(0+NUM_PORTS)-1:0] m_rfnoc_chdr_tvalid,
+ input wire [(0+NUM_PORTS)-1:0] m_rfnoc_chdr_tready,
+
+ // AXIS-Ctrl Control Input Port (from framework)
+ input wire [31:0] s_rfnoc_ctrl_tdata,
+ input wire s_rfnoc_ctrl_tlast,
+ input wire s_rfnoc_ctrl_tvalid,
+ output wire s_rfnoc_ctrl_tready,
+ // AXIS-Ctrl Control Output Port (to framework)
+ output wire [31:0] m_rfnoc_ctrl_tdata,
+ output wire m_rfnoc_ctrl_tlast,
+ output wire m_rfnoc_ctrl_tvalid,
+ input wire m_rfnoc_ctrl_tready,
+
+ //---------------------
+ // Client Interface
+ //---------------------
+
+ // CtrlPort Clock and Reset
+ output wire ctrlport_clk,
+ output wire ctrlport_rst,
+ // CtrlPort Master
+ output wire m_ctrlport_req_wr,
+ output wire m_ctrlport_req_rd,
+ output wire [19:0] m_ctrlport_req_addr,
+ output wire [31:0] m_ctrlport_req_data,
+ input wire m_ctrlport_resp_ack,
+ input wire [31:0] m_ctrlport_resp_data,
+
+ // AXI-Stream Data Clock and Reset
+ output wire axis_data_clk,
+ output wire axis_data_rst,
+ // Data Stream to User Logic: in
+ output wire [NUM_PORTS*32*MEM_DATA_W/32-1:0] m_in_axis_tdata,
+ output wire [NUM_PORTS*MEM_DATA_W/32-1:0] m_in_axis_tkeep,
+ output wire [NUM_PORTS-1:0] m_in_axis_tlast,
+ output wire [NUM_PORTS-1:0] m_in_axis_tvalid,
+ input wire [NUM_PORTS-1:0] m_in_axis_tready,
+ output wire [NUM_PORTS*64-1:0] m_in_axis_ttimestamp,
+ output wire [NUM_PORTS-1:0] m_in_axis_thas_time,
+ output wire [NUM_PORTS*16-1:0] m_in_axis_tlength,
+ output wire [NUM_PORTS-1:0] m_in_axis_teov,
+ output wire [NUM_PORTS-1:0] m_in_axis_teob,
+ // Data Stream to User Logic: out
+ input wire [NUM_PORTS*32*MEM_DATA_W/32-1:0] s_out_axis_tdata,
+ input wire [NUM_PORTS*MEM_DATA_W/32-1:0] s_out_axis_tkeep,
+ input wire [NUM_PORTS-1:0] s_out_axis_tlast,
+ input wire [NUM_PORTS-1:0] s_out_axis_tvalid,
+ output wire [NUM_PORTS-1:0] s_out_axis_tready,
+ input wire [NUM_PORTS*64-1:0] s_out_axis_ttimestamp,
+ input wire [NUM_PORTS-1:0] s_out_axis_thas_time,
+ input wire [NUM_PORTS*16-1:0] s_out_axis_tlength,
+ input wire [NUM_PORTS-1:0] s_out_axis_teov,
+ input wire [NUM_PORTS-1:0] s_out_axis_teob
+);
+
+ //---------------------------------------------------------------------------
+ // Backend Interface
+ //---------------------------------------------------------------------------
+
+ wire data_i_flush_en;
+ wire [31:0] data_i_flush_timeout;
+ wire [63:0] data_i_flush_active;
+ wire [63:0] data_i_flush_done;
+ wire data_o_flush_en;
+ wire [31:0] data_o_flush_timeout;
+ wire [63:0] data_o_flush_active;
+ wire [63:0] data_o_flush_done;
+
+ backend_iface #(
+ .NOC_ID (32'h4E91A000),
+ .NUM_DATA_I (0+NUM_PORTS),
+ .NUM_DATA_O (0+NUM_PORTS),
+ .CTRL_FIFOSIZE ($clog2(32)),
+ .MTU (MTU)
+ ) backend_iface_i (
+ .rfnoc_chdr_clk (rfnoc_chdr_clk),
+ .rfnoc_chdr_rst (rfnoc_chdr_rst),
+ .rfnoc_ctrl_clk (rfnoc_ctrl_clk),
+ .rfnoc_ctrl_rst (rfnoc_ctrl_rst),
+ .rfnoc_core_config (rfnoc_core_config),
+ .rfnoc_core_status (rfnoc_core_status),
+ .data_i_flush_en (data_i_flush_en),
+ .data_i_flush_timeout (data_i_flush_timeout),
+ .data_i_flush_active (data_i_flush_active),
+ .data_i_flush_done (data_i_flush_done),
+ .data_o_flush_en (data_o_flush_en),
+ .data_o_flush_timeout (data_o_flush_timeout),
+ .data_o_flush_active (data_o_flush_active),
+ .data_o_flush_done (data_o_flush_done)
+ );
+
+ //---------------------------------------------------------------------------
+ // Reset Generation
+ //---------------------------------------------------------------------------
+
+ wire mem_rst_pulse;
+
+ pulse_synchronizer #(.MODE ("POSEDGE")) pulse_synchronizer_mem (
+ .clk_a(rfnoc_chdr_clk), .rst_a(1'b0), .pulse_a (rfnoc_chdr_rst), .busy_a (),
+ .clk_b(mem_clk), .pulse_b (mem_rst_pulse)
+ );
+
+ pulse_stretch_min #(.LENGTH(32)) pulse_stretch_min_mem (
+ .clk(mem_clk), .rst(1'b0),
+ .pulse_in(mem_rst_pulse), .pulse_out(mem_rst)
+ );
+
+ //---------------------------------------------------------------------------
+ // Control Path
+ //---------------------------------------------------------------------------
+
+ assign ctrlport_clk = mem_clk;
+ assign ctrlport_rst = mem_rst;
+
+ ctrlport_endpoint #(
+ .THIS_PORTID (THIS_PORTID),
+ .SYNC_CLKS (0),
+ .AXIS_CTRL_MST_EN (0),
+ .AXIS_CTRL_SLV_EN (1),
+ .SLAVE_FIFO_SIZE ($clog2(32))
+ ) ctrlport_endpoint_i (
+ .rfnoc_ctrl_clk (rfnoc_ctrl_clk),
+ .rfnoc_ctrl_rst (rfnoc_ctrl_rst),
+ .ctrlport_clk (ctrlport_clk),
+ .ctrlport_rst (ctrlport_rst),
+ .s_rfnoc_ctrl_tdata (s_rfnoc_ctrl_tdata),
+ .s_rfnoc_ctrl_tlast (s_rfnoc_ctrl_tlast),
+ .s_rfnoc_ctrl_tvalid (s_rfnoc_ctrl_tvalid),
+ .s_rfnoc_ctrl_tready (s_rfnoc_ctrl_tready),
+ .m_rfnoc_ctrl_tdata (m_rfnoc_ctrl_tdata),
+ .m_rfnoc_ctrl_tlast (m_rfnoc_ctrl_tlast),
+ .m_rfnoc_ctrl_tvalid (m_rfnoc_ctrl_tvalid),
+ .m_rfnoc_ctrl_tready (m_rfnoc_ctrl_tready),
+ .m_ctrlport_req_wr (m_ctrlport_req_wr),
+ .m_ctrlport_req_rd (m_ctrlport_req_rd),
+ .m_ctrlport_req_addr (m_ctrlport_req_addr),
+ .m_ctrlport_req_data (m_ctrlport_req_data),
+ .m_ctrlport_req_byte_en (),
+ .m_ctrlport_req_has_time (),
+ .m_ctrlport_req_time (),
+ .m_ctrlport_resp_ack (m_ctrlport_resp_ack),
+ .m_ctrlport_resp_status (2'b0),
+ .m_ctrlport_resp_data (m_ctrlport_resp_data),
+ .s_ctrlport_req_wr (1'b0),
+ .s_ctrlport_req_rd (1'b0),
+ .s_ctrlport_req_addr (20'b0),
+ .s_ctrlport_req_portid (10'b0),
+ .s_ctrlport_req_rem_epid (16'b0),
+ .s_ctrlport_req_rem_portid (10'b0),
+ .s_ctrlport_req_data (32'b0),
+ .s_ctrlport_req_byte_en (4'hF),
+ .s_ctrlport_req_has_time (1'b0),
+ .s_ctrlport_req_time (64'b0),
+ .s_ctrlport_resp_ack (),
+ .s_ctrlport_resp_status (),
+ .s_ctrlport_resp_data ()
+ );
+
+ //---------------------------------------------------------------------------
+ // Data Path
+ //---------------------------------------------------------------------------
+
+ genvar i;
+
+ assign axis_data_clk = mem_clk;
+ assign axis_data_rst = mem_rst;
+
+ //---------------------
+ // Input Data Paths
+ //---------------------
+
+ for (i = 0; i < NUM_PORTS; i = i + 1) begin: gen_input_in
+ chdr_to_axis_data #(
+ .CHDR_W (CHDR_W),
+ .ITEM_W (32),
+ .NIPC (MEM_DATA_W/32),
+ .SYNC_CLKS (0),
+ .INFO_FIFO_SIZE ($clog2(32)),
+ .PYLD_FIFO_SIZE ($clog2(MTU))
+ ) chdr_to_axis_data_in_in (
+ .axis_chdr_clk (rfnoc_chdr_clk),
+ .axis_chdr_rst (rfnoc_chdr_rst),
+ .axis_data_clk (axis_data_clk),
+ .axis_data_rst (axis_data_rst),
+ .s_axis_chdr_tdata (s_rfnoc_chdr_tdata[((0+i)*CHDR_W)+:CHDR_W]),
+ .s_axis_chdr_tlast (s_rfnoc_chdr_tlast[0+i]),
+ .s_axis_chdr_tvalid (s_rfnoc_chdr_tvalid[0+i]),
+ .s_axis_chdr_tready (s_rfnoc_chdr_tready[0+i]),
+ .m_axis_tdata (m_in_axis_tdata[(32*MEM_DATA_W/32)*i+:(32*MEM_DATA_W/32)]),
+ .m_axis_tkeep (m_in_axis_tkeep[MEM_DATA_W/32*i+:MEM_DATA_W/32]),
+ .m_axis_tlast (m_in_axis_tlast[i]),
+ .m_axis_tvalid (m_in_axis_tvalid[i]),
+ .m_axis_tready (m_in_axis_tready[i]),
+ .m_axis_ttimestamp (m_in_axis_ttimestamp[64*i+:64]),
+ .m_axis_thas_time (m_in_axis_thas_time[i]),
+ .m_axis_tlength (m_in_axis_tlength[16*i+:16]),
+ .m_axis_teov (m_in_axis_teov[i]),
+ .m_axis_teob (m_in_axis_teob[i]),
+ .flush_en (data_i_flush_en),
+ .flush_timeout (data_i_flush_timeout),
+ .flush_active (data_i_flush_active[0+i]),
+ .flush_done (data_i_flush_done[0+i])
+ );
+ end
+
+ //---------------------
+ // Output Data Paths
+ //---------------------
+
+ for (i = 0; i < NUM_PORTS; i = i + 1) begin: gen_output_out
+ axis_data_to_chdr #(
+ .CHDR_W (CHDR_W),
+ .ITEM_W (32),
+ .NIPC (MEM_DATA_W/32),
+ .SYNC_CLKS (0),
+ .INFO_FIFO_SIZE ($clog2(32)),
+ .PYLD_FIFO_SIZE ($clog2(MTU)),
+ .MTU (MTU),
+ .SIDEBAND_AT_END (1)
+ ) axis_data_to_chdr_out_out (
+ .axis_chdr_clk (rfnoc_chdr_clk),
+ .axis_chdr_rst (rfnoc_chdr_rst),
+ .axis_data_clk (axis_data_clk),
+ .axis_data_rst (axis_data_rst),
+ .m_axis_chdr_tdata (m_rfnoc_chdr_tdata[(0+i)*CHDR_W+:CHDR_W]),
+ .m_axis_chdr_tlast (m_rfnoc_chdr_tlast[0+i]),
+ .m_axis_chdr_tvalid (m_rfnoc_chdr_tvalid[0+i]),
+ .m_axis_chdr_tready (m_rfnoc_chdr_tready[0+i]),
+ .s_axis_tdata (s_out_axis_tdata[(32*MEM_DATA_W/32)*i+:(32*MEM_DATA_W/32)]),
+ .s_axis_tkeep (s_out_axis_tkeep[MEM_DATA_W/32*i+:MEM_DATA_W/32]),
+ .s_axis_tlast (s_out_axis_tlast[i]),
+ .s_axis_tvalid (s_out_axis_tvalid[i]),
+ .s_axis_tready (s_out_axis_tready[i]),
+ .s_axis_ttimestamp (s_out_axis_ttimestamp[64*i+:64]),
+ .s_axis_thas_time (s_out_axis_thas_time[i]),
+ .s_axis_tlength (s_out_axis_tlength[16*i+:16]),
+ .s_axis_teov (s_out_axis_teov[i]),
+ .s_axis_teob (s_out_axis_teob[i]),
+ .flush_en (data_o_flush_en),
+ .flush_timeout (data_o_flush_timeout),
+ .flush_active (data_o_flush_active[0+i]),
+ .flush_done (data_o_flush_done[0+i])
+ );
+ end
+
+endmodule // noc_shell_replay
+
+
+`default_nettype wire
diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/rfnoc_block_replay.v b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/rfnoc_block_replay.v
new file mode 100644
index 000000000..b853db169
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/rfnoc_block_replay.v
@@ -0,0 +1,518 @@
+//
+// Copyright 2019 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: rfnoc_block_replay
+//
+// Description:
+//
+// RFNoC data record and playback block. This block has the ability to
+// capture all of the data that is sent to it and store it into an attached
+// memory using an AXI memory-mapped interface. It can then play back any
+// part of the data on demand or continuously. Timed playback is also
+// supported. See axis_replay.v for details of replay operation.
+//
+// Parameters:
+//
+// THIS_PORTID : Control crossbar port to which this block is connected
+// CHDR_W : AXIS-CHDR data bus width
+// MTU : Maximum transmission unit (i.e., maximum packet size in
+// CHDR words is 2**MTU).
+// NUM_PORTS : Number of replay instances to instantiate. Each one will
+// have its own register set and memory interface.
+// MEM_DATA_W : Data width to use for the memory interface.
+// MEM_ADDR_W : Byte address width to use for the memory interface.
+//
+
+`default_nettype none
+
+
+module rfnoc_block_replay #(
+ parameter [9:0] THIS_PORTID = 10'd0,
+ parameter CHDR_W = 64,
+ parameter [5:0] MTU = 10,
+ parameter NUM_PORTS = 1,
+ parameter MEM_DATA_W = 64,
+ parameter MEM_ADDR_W = 30
+) (
+ //---------------------------------------------------------------------------
+ // AXIS-CHDR Port
+ //---------------------------------------------------------------------------
+
+ // RFNoC Framework Clocks and Resets
+ input wire rfnoc_chdr_clk,
+
+ // AXIS-CHDR Input Ports (from framework)
+ input wire [(0+NUM_PORTS)*CHDR_W-1:0] s_rfnoc_chdr_tdata,
+ input wire [ (0+NUM_PORTS)-1:0] s_rfnoc_chdr_tlast,
+ input wire [ (0+NUM_PORTS)-1:0] s_rfnoc_chdr_tvalid,
+ output wire [ (0+NUM_PORTS)-1:0] s_rfnoc_chdr_tready,
+
+ // AXIS-CHDR Output Ports (to framework)
+ output wire [(0+NUM_PORTS)*CHDR_W-1:0] m_rfnoc_chdr_tdata,
+ output wire [ (0+NUM_PORTS)-1:0] m_rfnoc_chdr_tlast,
+ output wire [ (0+NUM_PORTS)-1:0] m_rfnoc_chdr_tvalid,
+ input wire [ (0+NUM_PORTS)-1:0] m_rfnoc_chdr_tready,
+
+ // RFNoC Backend Interface
+ input wire [511:0] rfnoc_core_config,
+ output wire [511:0] rfnoc_core_status,
+
+ //---------------------------------------------------------------------------
+ // AXIS-Ctrl Port
+ //---------------------------------------------------------------------------
+
+ input wire rfnoc_ctrl_clk,
+
+ // AXIS-Ctrl Input Port (from framework)
+ input wire [31:0] s_rfnoc_ctrl_tdata,
+ input wire s_rfnoc_ctrl_tlast,
+ input wire s_rfnoc_ctrl_tvalid,
+ output wire s_rfnoc_ctrl_tready,
+ // AXIS-Ctrl Output Port (to framework)
+ output wire [31:0] m_rfnoc_ctrl_tdata,
+ output wire m_rfnoc_ctrl_tlast,
+ output wire m_rfnoc_ctrl_tvalid,
+ input wire m_rfnoc_ctrl_tready,
+
+ //---------------------------------------------------------------------------
+ // AXI Memory Mapped Interface
+ //---------------------------------------------------------------------------
+
+ // AXI Interface Clock and Reset
+ input wire mem_clk,
+ input wire axi_rst,
+
+ // AXI Write address channel
+ output wire [ (NUM_PORTS*1)-1:0] m_axi_awid,
+ output wire [ (NUM_PORTS*MEM_ADDR_W)-1:0] m_axi_awaddr,
+ output wire [ (NUM_PORTS*8)-1:0] m_axi_awlen,
+ output wire [ (NUM_PORTS*3)-1:0] m_axi_awsize,
+ output wire [ (NUM_PORTS*2)-1:0] m_axi_awburst,
+ output wire [ (NUM_PORTS*1)-1:0] m_axi_awlock,
+ output wire [ (NUM_PORTS*4)-1:0] m_axi_awcache,
+ output wire [ (NUM_PORTS*3)-1:0] m_axi_awprot,
+ output wire [ (NUM_PORTS*4)-1:0] m_axi_awqos,
+ output wire [ (NUM_PORTS*4)-1:0] m_axi_awregion,
+ output wire [ (NUM_PORTS*1)-1:0] m_axi_awuser,
+ output wire [ (NUM_PORTS*1)-1:0] m_axi_awvalid,
+ input wire [ (NUM_PORTS*1)-1:0] m_axi_awready,
+ // AXI Write data channel
+ output wire [ (NUM_PORTS*MEM_DATA_W)-1:0] m_axi_wdata,
+ output wire [(NUM_PORTS*MEM_DATA_W/8)-1:0] m_axi_wstrb,
+ output wire [ (NUM_PORTS*1)-1:0] m_axi_wlast,
+ output wire [ (NUM_PORTS*1)-1:0] m_axi_wuser,
+ output wire [ (NUM_PORTS*1)-1:0] m_axi_wvalid,
+ input wire [ (NUM_PORTS*1)-1:0] m_axi_wready,
+ // AXI Write response channel signals
+ input wire [ (NUM_PORTS*1)-1:0] m_axi_bid,
+ input wire [ (NUM_PORTS*2)-1:0] m_axi_bresp,
+ input wire [ (NUM_PORTS*1)-1:0] m_axi_buser,
+ input wire [ (NUM_PORTS*1)-1:0] m_axi_bvalid,
+ output wire [ (NUM_PORTS*1)-1:0] m_axi_bready,
+ // AXI Read address channel
+ output wire [ (NUM_PORTS*1)-1:0] m_axi_arid,
+ output wire [ (NUM_PORTS*MEM_ADDR_W)-1:0] m_axi_araddr,
+ output wire [ (NUM_PORTS*8)-1:0] m_axi_arlen,
+ output wire [ (NUM_PORTS*3)-1:0] m_axi_arsize,
+ output wire [ (NUM_PORTS*2)-1:0] m_axi_arburst,
+ output wire [ (NUM_PORTS*1)-1:0] m_axi_arlock,
+ output wire [ (NUM_PORTS*4)-1:0] m_axi_arcache,
+ output wire [ (NUM_PORTS*3)-1:0] m_axi_arprot,
+ output wire [ (NUM_PORTS*4)-1:0] m_axi_arqos,
+ output wire [ (NUM_PORTS*4)-1:0] m_axi_arregion,
+ output wire [ (NUM_PORTS*1)-1:0] m_axi_aruser,
+ output wire [ (NUM_PORTS*1)-1:0] m_axi_arvalid,
+ input wire [ (NUM_PORTS*1)-1:0] m_axi_arready,
+ // AXI Read data channel
+ input wire [ (NUM_PORTS*1)-1:0] m_axi_rid,
+ input wire [ (NUM_PORTS*MEM_DATA_W)-1:0] m_axi_rdata,
+ input wire [ (NUM_PORTS*2)-1:0] m_axi_rresp,
+ input wire [ (NUM_PORTS*1)-1:0] m_axi_rlast,
+ input wire [ (NUM_PORTS*1)-1:0] m_axi_ruser,
+ input wire [ (NUM_PORTS*1)-1:0] m_axi_rvalid,
+ output wire [ (NUM_PORTS*1)-1:0] m_axi_rready
+);
+
+ `include "rfnoc_block_replay_regs.vh"
+
+
+ //---------------------------------------------------------------------------
+ // Signal Declarations
+ //---------------------------------------------------------------------------
+
+ // CtrlPort Master
+ wire ctrlport_req_wr;
+ wire ctrlport_req_rd;
+ wire [19:0] ctrlport_req_addr;
+ wire [31:0] ctrlport_req_data;
+ wire ctrlport_resp_ack;
+ wire [31:0] ctrlport_resp_data;
+
+ // Data Stream to User Logic: in
+ wire [NUM_PORTS*MEM_DATA_W*1-1:0] in_axis_tdata;
+ wire [ NUM_PORTS-1:0] in_axis_tlast;
+ wire [ NUM_PORTS-1:0] in_axis_tvalid;
+ wire [ NUM_PORTS-1:0] in_axis_tready;
+
+ // Data Stream to User Logic: out
+ wire [NUM_PORTS*MEM_DATA_W*1-1:0] out_axis_tdata;
+ wire [ NUM_PORTS-1:0] out_axis_tlast;
+ wire [ NUM_PORTS-1:0] out_axis_tvalid;
+ wire [ NUM_PORTS-1:0] out_axis_tready;
+ wire [ NUM_PORTS*64-1:0] out_axis_ttimestamp;
+ wire [ NUM_PORTS-1:0] out_axis_thas_time;
+ wire [ NUM_PORTS-1:0] out_axis_teob;
+
+
+ //---------------------------------------------------------------------------
+ // NoC Shell
+ //---------------------------------------------------------------------------
+
+ wire mem_rst_noc_shell;
+
+ noc_shell_replay #(
+ .THIS_PORTID (THIS_PORTID),
+ .CHDR_W (CHDR_W),
+ .MEM_DATA_W (MEM_DATA_W),
+ .MTU (MTU),
+ .NUM_PORTS (NUM_PORTS)
+ ) noc_shell_replay_i (
+ //---------------------
+ // Framework Interface
+ //---------------------
+
+ // Clock Inputs
+ .rfnoc_chdr_clk (rfnoc_chdr_clk),
+ .rfnoc_ctrl_clk (rfnoc_ctrl_clk),
+ .mem_clk (mem_clk),
+ // Reset Outputs
+ .rfnoc_chdr_rst (),
+ .rfnoc_ctrl_rst (),
+ .mem_rst (mem_rst_noc_shell),
+ // RFNoC Backend Interface
+ .rfnoc_core_config (rfnoc_core_config),
+ .rfnoc_core_status (rfnoc_core_status),
+ // CHDR Input Ports (from framework)
+ .s_rfnoc_chdr_tdata (s_rfnoc_chdr_tdata),
+ .s_rfnoc_chdr_tlast (s_rfnoc_chdr_tlast),
+ .s_rfnoc_chdr_tvalid (s_rfnoc_chdr_tvalid),
+ .s_rfnoc_chdr_tready (s_rfnoc_chdr_tready),
+ // CHDR Output Ports (to framework)
+ .m_rfnoc_chdr_tdata (m_rfnoc_chdr_tdata),
+ .m_rfnoc_chdr_tlast (m_rfnoc_chdr_tlast),
+ .m_rfnoc_chdr_tvalid (m_rfnoc_chdr_tvalid),
+ .m_rfnoc_chdr_tready (m_rfnoc_chdr_tready),
+ // AXIS-Ctrl Input Port (from framework)
+ .s_rfnoc_ctrl_tdata (s_rfnoc_ctrl_tdata),
+ .s_rfnoc_ctrl_tlast (s_rfnoc_ctrl_tlast),
+ .s_rfnoc_ctrl_tvalid (s_rfnoc_ctrl_tvalid),
+ .s_rfnoc_ctrl_tready (s_rfnoc_ctrl_tready),
+ // AXIS-Ctrl Output Port (to framework)
+ .m_rfnoc_ctrl_tdata (m_rfnoc_ctrl_tdata),
+ .m_rfnoc_ctrl_tlast (m_rfnoc_ctrl_tlast),
+ .m_rfnoc_ctrl_tvalid (m_rfnoc_ctrl_tvalid),
+ .m_rfnoc_ctrl_tready (m_rfnoc_ctrl_tready),
+
+ //---------------------
+ // Client Interface
+ //---------------------
+
+ // CtrlPort Clock and Reset
+ .ctrlport_clk (),
+ .ctrlport_rst (),
+ // CtrlPort Master
+ .m_ctrlport_req_wr (ctrlport_req_wr),
+ .m_ctrlport_req_rd (ctrlport_req_rd),
+ .m_ctrlport_req_addr (ctrlport_req_addr),
+ .m_ctrlport_req_data (ctrlport_req_data),
+ .m_ctrlport_resp_ack (ctrlport_resp_ack),
+ .m_ctrlport_resp_data (ctrlport_resp_data),
+
+ // AXI-Stream Payload Context Clock and Reset
+ .axis_data_clk (),
+ .axis_data_rst (),
+ // Data Stream to User Logic: in
+ .m_in_axis_tdata (in_axis_tdata),
+ .m_in_axis_tkeep (),
+ .m_in_axis_tlast (in_axis_tlast),
+ .m_in_axis_tvalid (in_axis_tvalid),
+ .m_in_axis_tready (in_axis_tready),
+ .m_in_axis_ttimestamp (),
+ .m_in_axis_thas_time (),
+ .m_in_axis_tlength (),
+ .m_in_axis_teov (),
+ .m_in_axis_teob (),
+ // Data Stream from User Logic: out
+ .s_out_axis_tdata (out_axis_tdata),
+ .s_out_axis_tkeep ({NUM_PORTS*MEM_DATA_W/32{1'b1}}),
+ .s_out_axis_tlast (out_axis_tlast),
+ .s_out_axis_tvalid (out_axis_tvalid),
+ .s_out_axis_tready (out_axis_tready),
+ .s_out_axis_ttimestamp (out_axis_ttimestamp),
+ .s_out_axis_thas_time (out_axis_thas_time),
+ .s_out_axis_tlength ({NUM_PORTS{16'b0}}), // Not used when SIDEBAND_AT_END = 1
+ .s_out_axis_teov ({NUM_PORTS{1'b0}}),
+ .s_out_axis_teob (out_axis_teob)
+ );
+
+ reg mem_rst;
+
+ // Combine the NoC Shell and AXI resets
+ always @(posedge mem_clk) begin
+ mem_rst <= axi_rst | mem_rst_noc_shell;
+ end
+
+
+ //---------------------------------------------------------------------------
+ // CtrlPort Splitter
+ //---------------------------------------------------------------------------
+
+ wire [ 1*NUM_PORTS-1:0] dec_ctrlport_req_wr;
+ wire [ 1*NUM_PORTS-1:0] dec_ctrlport_req_rd;
+ wire [20*NUM_PORTS-1:0] dec_ctrlport_req_addr;
+ wire [32*NUM_PORTS-1:0] dec_ctrlport_req_data;
+ wire [ 1*NUM_PORTS-1:0] dec_ctrlport_resp_ack;
+ wire [32*NUM_PORTS-1:0] dec_ctrlport_resp_data;
+
+ generate
+ if (NUM_PORTS > 1) begin : gen_ctrlport_decoder
+ ctrlport_decoder #(
+ .NUM_SLAVES (NUM_PORTS),
+ .BASE_ADDR (0),
+ .SLAVE_ADDR_W (REPLAY_ADDR_W)
+ ) ctrlport_decoder_i (
+ .ctrlport_clk (mem_clk),
+ .ctrlport_rst (mem_rst),
+ .s_ctrlport_req_wr (ctrlport_req_wr),
+ .s_ctrlport_req_rd (ctrlport_req_rd),
+ .s_ctrlport_req_addr (ctrlport_req_addr),
+ .s_ctrlport_req_data (ctrlport_req_data),
+ .s_ctrlport_req_byte_en (4'hF),
+ .s_ctrlport_req_has_time (1'b0),
+ .s_ctrlport_req_time (64'b0),
+ .s_ctrlport_resp_ack (ctrlport_resp_ack),
+ .s_ctrlport_resp_status (),
+ .s_ctrlport_resp_data (ctrlport_resp_data),
+ .m_ctrlport_req_wr (dec_ctrlport_req_wr),
+ .m_ctrlport_req_rd (dec_ctrlport_req_rd),
+ .m_ctrlport_req_addr (dec_ctrlport_req_addr),
+ .m_ctrlport_req_data (dec_ctrlport_req_data),
+ .m_ctrlport_req_byte_en (),
+ .m_ctrlport_req_has_time (),
+ .m_ctrlport_req_time (),
+ .m_ctrlport_resp_ack (dec_ctrlport_resp_ack),
+ .m_ctrlport_resp_status ({NUM_PORTS{2'b0}}),
+ .m_ctrlport_resp_data (dec_ctrlport_resp_data)
+ );
+ end else begin : gen_no_decoder
+ assign dec_ctrlport_req_wr = ctrlport_req_wr;
+ assign dec_ctrlport_req_rd = ctrlport_req_rd;
+ assign dec_ctrlport_req_addr = {{20-REPLAY_ADDR_W{1'b0}},
+ ctrlport_req_addr[REPLAY_ADDR_W-1:0]};
+ assign dec_ctrlport_req_data = ctrlport_req_data;
+ assign ctrlport_resp_ack = dec_ctrlport_resp_ack;
+ assign ctrlport_resp_data = dec_ctrlport_resp_data;
+ end
+ endgenerate
+
+
+ //---------------------------------------------------------------------------
+ // Replay Block Instances
+ //---------------------------------------------------------------------------
+
+ // Width of memory transfer count. Always 8 for AXI4.
+ localparam MEM_COUNT_W = 8;
+
+ genvar i;
+ generate
+ for (i = 0; i < NUM_PORTS; i = i+1) begin : gen_replay_blocks
+
+ wire [ MEM_ADDR_W-1:0] write_addr;
+ wire [MEM_COUNT_W-1:0] write_count;
+ wire write_ctrl_valid;
+ wire write_ctrl_ready;
+ wire [ MEM_DATA_W-1:0] write_data;
+ wire write_data_valid;
+ wire write_data_ready;
+
+ wire [ MEM_ADDR_W-1:0] read_addr;
+ wire [MEM_COUNT_W-1:0] read_count;
+ wire read_ctrl_valid;
+ wire read_ctrl_ready;
+ wire [ MEM_DATA_W-1:0] read_data;
+ wire read_data_valid;
+ wire read_data_ready;
+
+ //-----------------------------------------------------------------------
+ // Replay Handler
+ //-----------------------------------------------------------------------
+ //
+ // This block implements the state machine and control logic for
+ // recording and playback of data.
+ //
+ //-----------------------------------------------------------------------
+
+ axis_replay #(
+ .MEM_DATA_W (MEM_DATA_W),
+ .MEM_ADDR_W (MEM_ADDR_W),
+ .MEM_COUNT_W (MEM_COUNT_W)
+ ) axis_replay_i (
+ .clk (mem_clk),
+ .rst (mem_rst),
+
+ // CtrlPort Interface
+ .s_ctrlport_req_wr (dec_ctrlport_req_wr [ 1*i +: 1]),
+ .s_ctrlport_req_rd (dec_ctrlport_req_rd [ 1*i +: 1]),
+ .s_ctrlport_req_addr (dec_ctrlport_req_addr [20*i +: 20]),
+ .s_ctrlport_req_data (dec_ctrlport_req_data [32*i +: 32]),
+ .s_ctrlport_resp_ack (dec_ctrlport_resp_ack [ 1*i +: 1]),
+ .s_ctrlport_resp_data (dec_ctrlport_resp_data [32*i +: 32]),
+
+ // AXI Stream Interface
+ //
+ // Input
+ .i_tdata (in_axis_tdata [MEM_DATA_W*i +: MEM_DATA_W]),
+ .i_tvalid (in_axis_tvalid[ 1*i +: 1]),
+ .i_tlast (in_axis_tlast [ 1*i +: 1]),
+ .i_tready (in_axis_tready[ 1*i +: 1]),
+ //
+ // Output
+ .o_tdata (out_axis_tdata [MEM_DATA_W*i +: MEM_DATA_W]),
+ .o_ttimestamp (out_axis_ttimestamp[ 64*i +: 64]),
+ .o_thas_time (out_axis_thas_time [ 1*i +: 1]),
+ .o_teob (out_axis_teob [ 1*i +: 1]),
+ .o_tvalid (out_axis_tvalid [ 1*i +: 1]),
+ .o_tlast (out_axis_tlast [ 1*i +: 1]),
+ .o_tready (out_axis_tready [ 1*i +: 1]),
+
+ // Memory Interface
+ //
+ // Write interface
+ .write_addr (write_addr),
+ .write_count (write_count),
+ .write_ctrl_valid (write_ctrl_valid),
+ .write_ctrl_ready (write_ctrl_ready),
+ .write_data (write_data),
+ .write_data_valid (write_data_valid),
+ .write_data_ready (write_data_ready),
+ //
+ // Read interface
+ .read_addr (read_addr),
+ .read_count (read_count),
+ .read_ctrl_valid (read_ctrl_valid),
+ .read_ctrl_ready (read_ctrl_ready),
+ .read_data (read_data),
+ .read_data_valid (read_data_valid),
+ .read_data_ready (read_data_ready)
+ );
+
+ //-----------------------------------------------------------------------
+ // AXI DMA Master
+ //-----------------------------------------------------------------------
+ //
+ // This block translates simple read and write requests to AXI4
+ // memory-mapped reads and writes for the RAM interface.
+ //
+ //-----------------------------------------------------------------------
+
+ axi_dma_master #(
+ .AWIDTH (MEM_ADDR_W),
+ .DWIDTH (MEM_DATA_W)
+ ) axi_dma_master_i (
+ //
+ // AXI4 Memory Mapped Interface to DRAM
+ //
+ .aclk (mem_clk),
+ .areset (mem_rst),
+
+ // Write control
+ .m_axi_awid (m_axi_awid [ 1*i +: 1]),
+ .m_axi_awaddr (m_axi_awaddr [MEM_ADDR_W*i +: MEM_ADDR_W]),
+ .m_axi_awlen (m_axi_awlen [ 8*i +: 8]),
+ .m_axi_awsize (m_axi_awsize [ 3*i +: 3]),
+ .m_axi_awburst (m_axi_awburst [ 2*i +: 2]),
+ .m_axi_awvalid (m_axi_awvalid [ 1*i +: 1]),
+ .m_axi_awready (m_axi_awready [ 1*i +: 1]),
+ .m_axi_awlock (m_axi_awlock [ 1*i +: 1]),
+ .m_axi_awcache (m_axi_awcache [ 4*i +: 4]),
+ .m_axi_awprot (m_axi_awprot [ 3*i +: 3]),
+ .m_axi_awqos (m_axi_awqos [ 4*i +: 4]),
+ .m_axi_awregion (m_axi_awregion[ 4*i +: 4]),
+ .m_axi_awuser (m_axi_awuser [ 1*i +: 1]),
+
+ // Write Data
+ .m_axi_wdata (m_axi_wdata [ MEM_DATA_W*i +: MEM_DATA_W]),
+ .m_axi_wstrb (m_axi_wstrb [(MEM_DATA_W/8)*i +: (MEM_DATA_W/8)]),
+ .m_axi_wlast (m_axi_wlast [ 1*i +: 1]),
+ .m_axi_wvalid (m_axi_wvalid[ 1*i +: 1]),
+ .m_axi_wready (m_axi_wready[ 1*i +: 1]),
+ .m_axi_wuser (m_axi_wuser [ 1*i +: 1]),
+
+ // Write Response
+ .m_axi_bid (m_axi_bid [1*i +: 1]),
+ .m_axi_bresp (m_axi_bresp [2*i +: 2]),
+ .m_axi_buser (m_axi_buser [1*i +: 1]),
+ .m_axi_bvalid (m_axi_bvalid[1*i +: 1]),
+ .m_axi_bready (m_axi_bready[1*i +: 1]),
+
+ // Read Control
+ .m_axi_arid (m_axi_arid [ 1*i +: 1]),
+ .m_axi_araddr (m_axi_araddr [MEM_ADDR_W*i +: MEM_ADDR_W]),
+ .m_axi_arlen (m_axi_arlen [ 8*i +: 8]),
+ .m_axi_arsize (m_axi_arsize [ 3*i +: 3]),
+ .m_axi_arburst (m_axi_arburst [ 2*i +: 2]),
+ .m_axi_arvalid (m_axi_arvalid [ 1*i +: 1]),
+ .m_axi_arready (m_axi_arready [ 1*i +: 1]),
+ .m_axi_arlock (m_axi_arlock [ 1*i +: 1]),
+ .m_axi_arcache (m_axi_arcache [ 4*i +: 4]),
+ .m_axi_arprot (m_axi_arprot [ 3*i +: 3]),
+ .m_axi_arqos (m_axi_arqos [ 4*i +: 4]),
+ .m_axi_arregion (m_axi_arregion[ 4*i +: 4]),
+ .m_axi_aruser (m_axi_aruser [ 1*i +: 1]),
+
+ // Read Data
+ .m_axi_rid (m_axi_rid [ 1*i +: 1]),
+ .m_axi_rdata (m_axi_rdata [MEM_DATA_W*i +: MEM_DATA_W]),
+ .m_axi_rresp (m_axi_rresp [ 2*i +: 2]),
+ .m_axi_rlast (m_axi_rlast [ 1*i +: 1]),
+ .m_axi_ruser (m_axi_ruser [ 1*i +: 1]),
+ .m_axi_rvalid (m_axi_rvalid[ 1*i +: 1]),
+ .m_axi_rready (m_axi_rready[ 1*i +: 1]),
+
+ //
+ // Interface for Write transactions
+ //
+ .write_addr (write_addr),
+ .write_count (write_count),
+ .write_ctrl_valid (write_ctrl_valid),
+ .write_ctrl_ready (write_ctrl_ready),
+ .write_data (write_data),
+ .write_data_valid (write_data_valid),
+ .write_data_ready (write_data_ready),
+
+ //
+ // Interface for Read transactions
+ //
+ .read_addr (read_addr),
+ .read_count (read_count),
+ .read_ctrl_valid (read_ctrl_valid),
+ .read_ctrl_ready (read_ctrl_ready),
+ .read_data (read_data),
+ .read_data_valid (read_data_valid),
+ .read_data_ready (read_data_ready),
+
+ //
+ // Debug
+ //
+ .debug ()
+ );
+
+ end
+ endgenerate
+
+endmodule // rfnoc_block_replay
+
+
+`default_nettype wire
diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/rfnoc_block_replay_all_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/rfnoc_block_replay_all_tb.sv
new file mode 100644
index 000000000..caf89335a
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/rfnoc_block_replay_all_tb.sv
@@ -0,0 +1,82 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: rfnoc_block_replay_tb
+//
+// Description:
+//
+// This testbench is the top-level testbench for the RFnoC Replay block. It
+// instantiates several different variants of the Replay testbench, each
+// using different parameters, to test different configurations.
+//
+
+`default_nettype none
+
+
+module rfnoc_block_replay_all_tb;
+
+ `include "test_exec.svh"
+ import PkgTestExec::*;
+
+
+ //---------------------------------------------------------------------------
+ // Test Definitions
+ //---------------------------------------------------------------------------
+
+ typedef struct {
+ int CHDR_W;
+ int ITEM_W;
+ int NUM_PORTS;
+ int MEM_DATA_W;
+ int MEM_ADDR_W;
+ int TEST_REGS;
+ int TEST_FULL;
+ int STALL_PROB;
+ } test_config_t;
+
+ localparam NUM_TESTS = 15;
+
+ localparam test_config_t test[NUM_TESTS] = '{
+ // Test different CHDR and memory widths:
+ '{CHDR_W: 64, ITEM_W: 32, NUM_PORTS: 1, MEM_DATA_W: 32, MEM_ADDR_W: 16, TEST_REGS: 0, TEST_FULL: 1, STALL_PROB: 25},
+ '{CHDR_W: 128, ITEM_W: 32, NUM_PORTS: 1, MEM_DATA_W: 32, MEM_ADDR_W: 16, TEST_REGS: 0, TEST_FULL: 0, STALL_PROB: 25},
+ '{CHDR_W: 256, ITEM_W: 32, NUM_PORTS: 1, MEM_DATA_W: 32, MEM_ADDR_W: 16, TEST_REGS: 0, TEST_FULL: 1, STALL_PROB: 25},
+ '{CHDR_W: 64, ITEM_W: 32, NUM_PORTS: 2, MEM_DATA_W: 64, MEM_ADDR_W: 16, TEST_REGS: 1, TEST_FULL: 1, STALL_PROB: 25},
+ '{CHDR_W: 128, ITEM_W: 32, NUM_PORTS: 1, MEM_DATA_W: 64, MEM_ADDR_W: 16, TEST_REGS: 1, TEST_FULL: 0, STALL_PROB: 25},
+ '{CHDR_W: 256, ITEM_W: 32, NUM_PORTS: 1, MEM_DATA_W: 64, MEM_ADDR_W: 16, TEST_REGS: 0, TEST_FULL: 0, STALL_PROB: 25},
+ '{CHDR_W: 64, ITEM_W: 32, NUM_PORTS: 1, MEM_DATA_W: 128, MEM_ADDR_W: 16, TEST_REGS: 0, TEST_FULL: 0, STALL_PROB: 25},
+ '{CHDR_W: 128, ITEM_W: 32, NUM_PORTS: 1, MEM_DATA_W: 128, MEM_ADDR_W: 16, TEST_REGS: 0, TEST_FULL: 0, STALL_PROB: 25},
+ '{CHDR_W: 64, ITEM_W: 32, NUM_PORTS: 1, MEM_DATA_W: 256, MEM_ADDR_W: 16, TEST_REGS: 0, TEST_FULL: 1, STALL_PROB: 25},
+ '{CHDR_W: 64, ITEM_W: 32, NUM_PORTS: 1, MEM_DATA_W: 512, MEM_ADDR_W: 16, TEST_REGS: 0, TEST_FULL: 0, STALL_PROB: 25},
+ // Test different stall probabilities:
+ '{CHDR_W: 64, ITEM_W: 32, NUM_PORTS: 2, MEM_DATA_W: 64, MEM_ADDR_W: 16, TEST_REGS: 1, TEST_FULL: 1, STALL_PROB: 0},
+ '{CHDR_W: 64, ITEM_W: 32, NUM_PORTS: 2, MEM_DATA_W: 64, MEM_ADDR_W: 16, TEST_REGS: 1, TEST_FULL: 1, STALL_PROB: 75},
+ // Test large memory (> 32-bit) to check 64-bit registers:
+ '{CHDR_W: 64, ITEM_W: 32, NUM_PORTS: 2, MEM_DATA_W: 64, MEM_ADDR_W: 34, TEST_REGS: 1, TEST_FULL: 0, STALL_PROB: 0},
+ // Test different item widths to check time is handled correctly
+ '{CHDR_W: 64, ITEM_W: 16, NUM_PORTS: 1, MEM_DATA_W: 32, MEM_ADDR_W: 16, TEST_REGS: 0, TEST_FULL: 1, STALL_PROB: 25},
+ '{CHDR_W: 256, ITEM_W: 8, NUM_PORTS: 1, MEM_DATA_W: 32, MEM_ADDR_W: 16, TEST_REGS: 0, TEST_FULL: 1, STALL_PROB: 25}
+ };
+
+
+ //---------------------------------------------------------------------------
+ // DUT Instances
+ //---------------------------------------------------------------------------
+
+ genvar i;
+ for (i = 0; i < NUM_TESTS; i++) begin : gen_test_config
+ rfnoc_block_replay_tb #(
+ .CHDR_W (test[i].CHDR_W ),
+ .NUM_PORTS (test[i].NUM_PORTS ),
+ .MEM_DATA_W (test[i].MEM_DATA_W),
+ .MEM_ADDR_W (test[i].MEM_ADDR_W),
+ .TEST_FULL (test[i].TEST_FULL )
+ ) rfnoc_block_replay_tb_i ();
+ end : gen_test_config
+
+endmodule : rfnoc_block_replay_all_tb
+
+
+`default_nettype wire
diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/rfnoc_block_replay_regs.vh b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/rfnoc_block_replay_regs.vh
new file mode 100644
index 000000000..4849da47f
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/rfnoc_block_replay_regs.vh
@@ -0,0 +1,205 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: rfnoc_block_replay_regs (Header)
+//
+// Description:
+//
+// This is a header file that contains the register descriptions for the
+// RFNoC Replay block.
+//
+// Each RFNoC Replay block consists of NUM_PORTS independent replay engines.
+// Each one has its own address space that is REPLAY_ADDR_W bits wide. That
+// is, replay block N can be addressed starting at byte offset
+// N*(2**REPLAY_ADDR_W).
+//
+// All 64-bit registers should be read/written least-significant word first
+// to guarantee coherence.
+//
+
+//-----------------------------------------------------------------------------
+// Register Space
+//-----------------------------------------------------------------------------
+
+// The amount of address space taken up by each replay engine. That is, the
+// address space for port N starts at N*(2^REPLAY_ADDR_W).
+localparam REPLAY_ADDR_W = 20'h00008;
+
+
+//-----------------------------------------------------------------------------
+// Replay Register Descriptions
+//-----------------------------------------------------------------------------
+
+// REG_COMPAT (R)
+//
+// Compatibility version. This read-only register is used by software to
+// determine if this block's version is compatible with the running software. A
+// major version change indicates the software for the previous major version
+// is no longer compatible. A minor version change means the previous version
+// is compatible, but some new features may be unavailable.
+//
+// [31:16] Major version
+// [15: 0] Minor version
+//
+localparam REG_COMPAT = 'h00;
+//
+localparam REG_MAJOR_POS = 16;
+localparam REG_MAJOR_LEN = 16;
+//
+localparam REG_MINOR_POS = 0;
+localparam REG_MINOR_LEN = 16;
+
+// REG_MEM_SIZE (R)
+//
+// Returns information about the size of the attached memory. The address size
+// allows software to determine what buffer size and base address values are
+// valid.
+//
+// [31:16] : Memory Data Word Size. Returns the bit width of the RAM word size.
+// [15: 0] : Memory Address Size. Returns the bit width of the RAM byte
+// address. That is, the memory is 2**VALUE bytes in size.
+//
+localparam REG_MEM_SIZE = 'h04;
+//
+localparam REG_DATA_SIZE_LEN = 16;
+localparam REG_DATA_SIZE_POS = 16;
+//
+localparam REG_ADDR_SIZE_LEN = 16;
+localparam REG_ADDR_SIZE_POS = 0;
+
+// REG_REC_RESTART (W)
+//
+// Record Buffer Restart Register. Software must write to this register after
+// updating the base address or buffer size. This will cause recording to
+// restart at the indicated location. It does not matter what value you write.
+//
+localparam REG_REC_RESTART = 'h08;
+
+// REG_REC_BASE_ADDR (R/W)
+//
+// Record Base Address Register. This is the byte address that controls where
+// in the attached memory that recorded data should be stored. This must be a
+// multiple of memory word size (REG_DATA_SIZE) in bytes.
+//
+localparam REG_REC_BASE_ADDR_LO = 'h10;
+localparam REG_REC_BASE_ADDR_HI = 'h14;
+
+// REG_REC_BUFFER_SIZE (R/W)
+//
+// Record Buffer Size Register. This controls the portion of the RAM allocated
+// to the record buffer, in bytes. This must be a multiple of memory word size
+// (REG_DATA_SIZE) in bytes.
+//
+localparam REG_REC_BUFFER_SIZE_LO = 'h18;
+localparam REG_REC_BUFFER_SIZE_HI = 'h1C;
+
+// REG_REC_FULLNESS (R)
+//
+// Record Fullness. Returns the number of bytes that have been recorded in the
+// record buffer.
+//
+// This is is a 64-bit register in which the least-significant 32-bit word must
+// be read first.
+//
+localparam REG_REC_FULLNESS_LO = 'h20;
+localparam REG_REC_FULLNESS_HI = 'h24;
+
+// REG_PLAY_BASE_ADDR (R/W)
+//
+// Playback Base Address Register. This is the byte address that controls where
+// in the attached memory to read the data to be played back. This must be a
+// multiple of memory word size (REG_DATA_SIZE) in bytes.
+//
+localparam REG_PLAY_BASE_ADDR_LO = 'h28;
+localparam REG_PLAY_BASE_ADDR_HI = 'h2C;
+
+// REG_PLAY_BUFFER_SIZE (R/W)
+//
+// Playback Buffer Size Register. This controls the size, in bytes, of the
+// playback buffer in the attached memory. This must be a multiple of memory
+// word size (REG_DATA_SIZE) in bytes.
+//
+localparam REG_PLAY_BUFFER_SIZE_LO = 'h30;
+localparam REG_PLAY_BUFFER_SIZE_HI = 'h34;
+
+// REG_PLAY_CMD_NUM_WORDS (R/W)
+//
+// Playback Command Number of Words. This register controls the number of
+// memory data words to play back.
+//
+localparam REG_PLAY_CMD_NUM_WORDS_LO = 'h38;
+localparam REG_PLAY_CMD_NUM_WORDS_HI = 'h3C;
+//
+localparam REG_CMD_NUM_WORDS_LEN = 64;
+
+// REG_PLAY_CMD_TIME (R/W)
+//
+// Playback Command Time. This register indicates the timestamp to attach to
+// the first packet that is played back, if timed playback is enabled.
+// Subsequent packets will have the correctly incremented timestamp attached.
+//
+localparam REG_PLAY_CMD_TIME_LO = 'h40;
+localparam REG_PLAY_CMD_TIME_HI = 'h44;
+//
+localparam REG_CMD_TIME_LEN = 64;
+
+// REG_PLAY_CMD (W)
+//
+// Playback Command Register. This register mirrors the behavior of the RFNoC
+// RX radio block. All commands are queued up in the replay command FIFO. The
+// fields are as follows.
+//
+// [31] : Timed flag. Indicates if the command is timed (1) or not (0).
+//
+// [1:0] : Command field. The command indicates what you want the playback to
+// do. It can be one of the following:
+//
+// 0 (PLAY_CMD_STOP) : Stop playing back data
+// 1 (PLAY_CMD_FINITE) : Acquire NUM_SAMPS then stop
+// 2 (PLAY_CMD_CONTINUOUS) : Play back continuously until stopped.
+//
+localparam REG_PLAY_CMD = 'h48;
+//
+localparam REG_PLAY_TIMED_POS = 31;
+localparam REG_PLAY_TIMED_LEN = 1;
+//
+localparam REG_PLAY_CMD_POS = 0;
+localparam REG_PLAY_CMD_LEN = 2;
+
+// REG_PLAY_WORDS_PER_PKT (R/W)
+//
+// [15:0] Words Per Packet. This registers controls how many memory data words
+// (REG_DATA_SIZE bits each) are inserted into each packet during
+// playback. Effectively, it controls the samples-per-packet (SPP), but
+// the replay block is sample-size agnostic.
+//
+// This value should never be set such that the total RFNoC packet size
+// would exceed the system MTU or the maximum packet size allowed by
+// RFNoC (2^16 bytes). Also note that the last packet of a command may
+// be less than this size.
+//
+localparam REG_PLAY_WORDS_PER_PKT = 'h4C;
+//
+localparam REG_PLAY_WORDS_PER_PKT_LEN = 16;
+//
+localparam REG_PLAY_WORDS_PER_PKT_INIT = 160;
+
+// REG_PLAY_ITEM_SIZE (R/W)
+//
+// [7:0] Number of bytes per item. This controls how much time is incremented
+// for each memory word of data. This must be a power of 2.
+//
+localparam REG_PLAY_ITEM_SIZE = 'h50;
+//
+localparam REG_ITEM_SIZE_POS = 0;
+localparam REG_ITEM_SIZE_LEN = 8;
+
+//-----------------------------------------------------------------------------
+// Playback Commands
+//-----------------------------------------------------------------------------
+
+localparam PLAY_CMD_STOP = 2'h0;
+localparam PLAY_CMD_FINITE = 2'h1;
+localparam PLAY_CMD_CONTINUOUS = 2'h2; \ No newline at end of file
diff --git a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/rfnoc_block_replay_tb.sv b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/rfnoc_block_replay_tb.sv
new file mode 100644
index 000000000..9a0d9c810
--- /dev/null
+++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/rfnoc_block_replay_tb.sv
@@ -0,0 +1,1775 @@
+//
+// Copyright 2020 Ettus Research, a National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: rfnoc_block_replay_tb
+//
+// Description:
+//
+// Testbench for the Replay RFNoC block. This testbench is parameterizable to
+// test different configurations of the IP.
+//
+// Parameters:
+//
+// CHDR_W : CHDR_W parameter to use for the DUT
+// ITEM_W : ITEM_W to use for this simulation
+// NUM_PORTS : NUM_PORTS parameter to use for the DUT
+// MEM_DATA_W : MEM_DATA_W parameter to use for the DUT
+// MEM_ADDR_W : MEM_ADDR_W parameter to use for the DUT
+// TEST_REGS : Controls whether or not to test the registers
+// TEST_FULL : Controls whether or not to test filling the memory (may take
+// a long time to simulate).
+// STALL_PROB : Stall probability (0 to 100) to set on BFMs for this test
+//
+
+`default_nettype none
+
+
+module rfnoc_block_replay_tb#(
+ parameter int CHDR_W = 64,
+ parameter int ITEM_W = 32,
+ parameter int NUM_PORTS = 1,
+ parameter int MEM_DATA_W = 64,
+ parameter int MEM_ADDR_W = 16,
+ parameter int TEST_REGS = 1,
+ parameter int TEST_FULL = 1,
+ parameter int STALL_PROB = 25
+);
+
+ `include "test_exec.svh"
+
+ import PkgTestExec::*;
+ import PkgChdrUtils::*;
+ import PkgRfnocBlockCtrlBfm::*;
+ import PkgRfnocItemUtils::*;
+
+ `include "rfnoc_block_replay_regs.vh"
+
+
+ //---------------------------------------------------------------------------
+ // Testbench Configuration
+ //---------------------------------------------------------------------------
+
+ // Clock rates
+ localparam real CHDR_CLK_PER = 5.0; // 200 MHz
+ localparam real CTRL_CLK_PER = 8.0; // 125 MHz
+ localparam real MEM_CLK_PER = 3.0; // 333.333 MHz
+
+ // Block configuration
+ localparam [ 9:0] THIS_PORTID = 10'h123;
+ localparam [31:0] NOC_ID = 32'h4E91A000;
+ localparam int MTU = 10;
+
+ // Data sizes
+ localparam int SPP = 128; // Samples/items per packet
+ localparam int BPP = SPP * (ITEM_W/8); // Bytes per packet
+
+ // Simulation Data Randomness
+ //
+ // Currently, Vivado 2019.1.1 doesn't seed it's RNG properly, making it so we
+ // can't regenerate the same sequence. So for now, I've turned off random.
+ localparam bit USE_RANDOM = 0; // Use random or sequential data
+
+ // Useful constants
+ localparam int MEM_WORD_SIZE = MEM_DATA_W/8;
+ localparam int CHDR_WORD_SIZE = CHDR_W/8;
+ localparam int ITEM_SIZE = ITEM_W/8;
+
+ // Memory burst size in memory words, as defined in the axis_replay block.
+ // This is essentially hard coded to match the maximum transfer size
+ // supported by the DMA master.
+ const int MEM_BURST_LEN =
+ rfnoc_block_replay_i.gen_replay_blocks[0].axis_replay_i.MEM_BURST_LEN;
+
+ // AXI alignment boundary, in bytes
+ localparam AXI_ALIGNMENT = 4096;
+
+ // Arbitrary timeout to use for each test. Just needs to be longer than any
+ // test would take.
+ time TEST_TIMEOUT = 500us;
+
+
+ //---------------------------------------------------------------------------
+ // Clocks and Resets
+ //---------------------------------------------------------------------------
+
+ bit rfnoc_chdr_clk;
+ bit rfnoc_ctrl_clk;
+ bit mem_clk, mem_rst;
+
+ // Don't start the clocks automatically (AUTOSTART=0), since we expect
+ // multiple instances of this testbench to run in sequence. They will be
+ // started before the first test.
+ sim_clock_gen #(.PERIOD(CHDR_CLK_PER), .AUTOSTART(0))
+ rfnoc_chdr_clk_gen (.clk(rfnoc_chdr_clk), .rst());
+ sim_clock_gen #(.PERIOD(CTRL_CLK_PER), .AUTOSTART(0))
+ rfnoc_ctrl_clk_gen (.clk(rfnoc_ctrl_clk), .rst());
+ sim_clock_gen #(.PERIOD(MEM_CLK_PER), .AUTOSTART(0))
+ mem_clk_gen (.clk(mem_clk), .rst(mem_rst));
+
+
+ //---------------------------------------------------------------------------
+ // Bus Functional Models
+ //---------------------------------------------------------------------------
+
+ // Backend Interface
+ RfnocBackendIf backend (rfnoc_chdr_clk, rfnoc_ctrl_clk);
+
+ // AXIS-Ctrl Interface
+ AxiStreamIf #(32) m_ctrl (rfnoc_ctrl_clk, 1'b0);
+ AxiStreamIf #(32) s_ctrl (rfnoc_ctrl_clk, 1'b0);
+
+ // AXIS-CHDR Interfaces
+ AxiStreamIf #(CHDR_W) m_chdr [NUM_PORTS] (rfnoc_chdr_clk, 1'b0);
+ AxiStreamIf #(CHDR_W) s_chdr [NUM_PORTS] (rfnoc_chdr_clk, 1'b0);
+
+ // Block Controller BFM
+ RfnocBlockCtrlBfm #(CHDR_W, ITEM_W) blk_ctrl = new(backend, m_ctrl, s_ctrl);
+
+ // CHDR word and item/sample data types
+ typedef ChdrData #(CHDR_W, ITEM_W)::chdr_word_t chdr_word_t;
+ typedef ChdrData #(CHDR_W, ITEM_W)::item_t item_t;
+ typedef ChdrData #(CHDR_W, ITEM_W)::item_queue_t item_queue_t;
+
+ // Connect block controller to BFMs
+ for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_bfm_connections
+ initial begin
+ blk_ctrl.connect_master_data_port(i, m_chdr[i], BPP);
+ blk_ctrl.set_master_stall_prob(i, STALL_PROB);
+ blk_ctrl.connect_slave_data_port(i, s_chdr[i]);
+ blk_ctrl.set_slave_stall_prob(i, STALL_PROB);
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // AXI Memory Model
+ //---------------------------------------------------------------------------
+
+ // AXI Write Address Channel
+ wire [ NUM_PORTS*1-1:0] m_axi_awid;
+ wire [ NUM_PORTS*MEM_ADDR_W-1:0] m_axi_awaddr;
+ wire [ NUM_PORTS*8-1:0] m_axi_awlen;
+ wire [ NUM_PORTS*3-1:0] m_axi_awsize;
+ wire [ NUM_PORTS*2-1:0] m_axi_awburst;
+ wire [ NUM_PORTS*1-1:0] m_axi_awlock; // Unused master output
+ wire [ NUM_PORTS*4-1:0] m_axi_awcache; // Unused master output
+ wire [ NUM_PORTS*3-1:0] m_axi_awprot; // Unused master output
+ wire [ NUM_PORTS*4-1:0] m_axi_awqos; // Unused master output
+ wire [ NUM_PORTS*4-1:0] m_axi_awregion; // Unused master output
+ wire [ NUM_PORTS*1-1:0] m_axi_awuser; // Unused master output
+ wire [ NUM_PORTS*1-1:0] m_axi_awvalid;
+ wire [ NUM_PORTS*1-1:0] m_axi_awready;
+ // AXI Write Data Channel
+ wire [ NUM_PORTS*MEM_DATA_W-1:0] m_axi_wdata;
+ wire [NUM_PORTS*MEM_DATA_W/8-1:0] m_axi_wstrb;
+ wire [ NUM_PORTS*1-1:0] m_axi_wlast;
+ wire [ NUM_PORTS*1-1:0] m_axi_wuser; // Unused master output
+ wire [ NUM_PORTS*1-1:0] m_axi_wvalid;
+ wire [ NUM_PORTS*1-1:0] m_axi_wready;
+ // AXI Write Response Channel
+ wire [ NUM_PORTS*1-1:0] m_axi_bid;
+ wire [ NUM_PORTS*2-1:0] m_axi_bresp;
+ wire [ NUM_PORTS*1-1:0] m_axi_buser; // Unused master input
+ wire [ NUM_PORTS*1-1:0] m_axi_bvalid;
+ wire [ NUM_PORTS*1-1:0] m_axi_bready;
+ // AXI Read Address Channel
+ wire [ NUM_PORTS*1-1:0] m_axi_arid;
+ wire [ NUM_PORTS*MEM_ADDR_W-1:0] m_axi_araddr;
+ wire [ NUM_PORTS*8-1:0] m_axi_arlen;
+ wire [ NUM_PORTS*3-1:0] m_axi_arsize;
+ wire [ NUM_PORTS*2-1:0] m_axi_arburst;
+ wire [ NUM_PORTS*1-1:0] m_axi_arlock; // Unused master output
+ wire [ NUM_PORTS*4-1:0] m_axi_arcache; // Unused master output
+ wire [ NUM_PORTS*3-1:0] m_axi_arprot; // Unused master output
+ wire [ NUM_PORTS*4-1:0] m_axi_arqos; // Unused master output
+ wire [ NUM_PORTS*4-1:0] m_axi_arregion; // Unused master output
+ wire [ NUM_PORTS*1-1:0] m_axi_aruser; // Unused master output
+ wire [ NUM_PORTS*1-1:0] m_axi_arvalid;
+ wire [ NUM_PORTS*1-1:0] m_axi_arready;
+ // AXI Read Data Channel
+ wire [ NUM_PORTS*1-1:0] m_axi_rid;
+ wire [NUM_PORTS*MEM_DATA_W-1:0] m_axi_rdata;
+ wire [ NUM_PORTS*2-1:0] m_axi_rresp;
+ wire [ NUM_PORTS*1-1:0] m_axi_rlast;
+ wire [ NUM_PORTS*1-1:0] m_axi_ruser; // Unused master input
+ wire [ NUM_PORTS*1-1:0] m_axi_rvalid;
+ wire [ NUM_PORTS*1-1:0] m_axi_rready;
+
+ // Unused master input signals
+ assign m_axi_buser = {NUM_PORTS{1'b0}};
+ assign m_axi_ruser = {NUM_PORTS{1'b0}};
+
+ for (genvar i = 0; i < NUM_PORTS; i = i+1) begin : gen_sim_axi_ram
+ sim_axi_ram #(
+ .AWIDTH (MEM_ADDR_W),
+ .DWIDTH (MEM_DATA_W),
+ .IDWIDTH (1),
+ .BIG_ENDIAN (0),
+ .STALL_PROB (STALL_PROB)
+ ) sim_axi_ram_i (
+ .s_aclk (mem_clk),
+ .s_aresetn (~mem_rst),
+ .s_axi_awid (m_axi_awid[i]),
+ .s_axi_awaddr (m_axi_awaddr[i*MEM_ADDR_W +: MEM_ADDR_W]),
+ .s_axi_awlen (m_axi_awlen[i*8 +: 8]),
+ .s_axi_awsize (m_axi_awsize[i*3 +: 3]),
+ .s_axi_awburst (m_axi_awburst[i*2 +: 2]),
+ .s_axi_awvalid (m_axi_awvalid[i]),
+ .s_axi_awready (m_axi_awready[i]),
+ .s_axi_wdata (m_axi_wdata[i*MEM_DATA_W +: MEM_DATA_W]),
+ .s_axi_wstrb (m_axi_wstrb[i*(MEM_DATA_W/8) +: (MEM_DATA_W/8)]),
+ .s_axi_wlast (m_axi_wlast[i]),
+ .s_axi_wvalid (m_axi_wvalid[i]),
+ .s_axi_wready (m_axi_wready[i]),
+ .s_axi_bid (m_axi_bid[i]),
+ .s_axi_bresp (m_axi_bresp[i*2 +: 2]),
+ .s_axi_bvalid (m_axi_bvalid[i]),
+ .s_axi_bready (m_axi_bready[i]),
+ .s_axi_arid (m_axi_arid[i]),
+ .s_axi_araddr (m_axi_araddr[i*MEM_ADDR_W +: MEM_ADDR_W]),
+ .s_axi_arlen (m_axi_arlen[i*8 +: 8]),
+ .s_axi_arsize (m_axi_arsize[i*3 +: 3]),
+ .s_axi_arburst (m_axi_arburst[i*2 +: 2]),
+ .s_axi_arvalid (m_axi_arvalid[i]),
+ .s_axi_arready (m_axi_arready[i]),
+ .s_axi_rid (m_axi_rid[i]),
+ .s_axi_rdata (m_axi_rdata[i*MEM_DATA_W +: MEM_DATA_W]),
+ .s_axi_rresp (m_axi_rresp[i*2 +: 2]),
+ .s_axi_rlast (m_axi_rlast[i]),
+ .s_axi_rvalid (m_axi_rvalid[i]),
+ .s_axi_rready (m_axi_rready[i])
+ );
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Device Under Test (DUT)
+ //---------------------------------------------------------------------------
+
+ // DUT Slave (Input) Port Signals
+ logic [CHDR_W*NUM_PORTS-1:0] s_rfnoc_chdr_tdata;
+ logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tlast;
+ logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tvalid;
+ logic [ NUM_PORTS-1:0] s_rfnoc_chdr_tready;
+
+ // DUT Master (Output) Port Signals
+ logic [CHDR_W*NUM_PORTS-1:0] m_rfnoc_chdr_tdata;
+ logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tlast;
+ logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tvalid;
+ logic [ NUM_PORTS-1:0] m_rfnoc_chdr_tready;
+
+ // Map the array of BFMs to a flat vector for the DUT connections
+ for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_dut_input_connections
+ // Connect BFM master to DUT slave port
+ assign s_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W] = m_chdr[i].tdata;
+ assign s_rfnoc_chdr_tlast[i] = m_chdr[i].tlast;
+ assign s_rfnoc_chdr_tvalid[i] = m_chdr[i].tvalid;
+ assign m_chdr[i].tready = s_rfnoc_chdr_tready[i];
+ end
+ for (genvar i = 0; i < NUM_PORTS; i++) begin : gen_dut_output_connections
+ // Connect BFM slave to DUT master port
+ assign s_chdr[i].tdata = m_rfnoc_chdr_tdata[CHDR_W*i+:CHDR_W];
+ assign s_chdr[i].tlast = m_rfnoc_chdr_tlast[i];
+ assign s_chdr[i].tvalid = m_rfnoc_chdr_tvalid[i];
+ assign m_rfnoc_chdr_tready[i] = s_chdr[i].tready;
+ end
+
+ rfnoc_block_replay #(
+ .THIS_PORTID (THIS_PORTID),
+ .CHDR_W (CHDR_W),
+ .MTU (MTU),
+ .NUM_PORTS (NUM_PORTS),
+ .MEM_DATA_W (MEM_DATA_W),
+ .MEM_ADDR_W (MEM_ADDR_W)
+ ) rfnoc_block_replay_i (
+ .rfnoc_chdr_clk (rfnoc_chdr_clk),
+ .rfnoc_ctrl_clk (rfnoc_ctrl_clk),
+ .rfnoc_core_config (backend.cfg),
+ .rfnoc_core_status (backend.sts),
+ .s_rfnoc_chdr_tdata (s_rfnoc_chdr_tdata),
+ .s_rfnoc_chdr_tlast (s_rfnoc_chdr_tlast),
+ .s_rfnoc_chdr_tvalid (s_rfnoc_chdr_tvalid),
+ .s_rfnoc_chdr_tready (s_rfnoc_chdr_tready),
+ .m_rfnoc_chdr_tdata (m_rfnoc_chdr_tdata),
+ .m_rfnoc_chdr_tlast (m_rfnoc_chdr_tlast),
+ .m_rfnoc_chdr_tvalid (m_rfnoc_chdr_tvalid),
+ .m_rfnoc_chdr_tready (m_rfnoc_chdr_tready),
+ .s_rfnoc_ctrl_tdata (m_ctrl.tdata),
+ .s_rfnoc_ctrl_tlast (m_ctrl.tlast),
+ .s_rfnoc_ctrl_tvalid (m_ctrl.tvalid),
+ .s_rfnoc_ctrl_tready (m_ctrl.tready),
+ .m_rfnoc_ctrl_tdata (s_ctrl.tdata),
+ .m_rfnoc_ctrl_tlast (s_ctrl.tlast),
+ .m_rfnoc_ctrl_tvalid (s_ctrl.tvalid),
+ .m_rfnoc_ctrl_tready (s_ctrl.tready),
+ .mem_clk (mem_clk),
+ .axi_rst (mem_rst),
+ .m_axi_awid (m_axi_awid),
+ .m_axi_awaddr (m_axi_awaddr),
+ .m_axi_awlen (m_axi_awlen),
+ .m_axi_awsize (m_axi_awsize),
+ .m_axi_awburst (m_axi_awburst),
+ .m_axi_awlock (m_axi_awlock),
+ .m_axi_awcache (m_axi_awcache),
+ .m_axi_awprot (m_axi_awprot),
+ .m_axi_awqos (m_axi_awqos),
+ .m_axi_awregion (m_axi_awregion),
+ .m_axi_awuser (m_axi_awuser),
+ .m_axi_awvalid (m_axi_awvalid),
+ .m_axi_awready (m_axi_awready),
+ .m_axi_wdata (m_axi_wdata),
+ .m_axi_wstrb (m_axi_wstrb),
+ .m_axi_wlast (m_axi_wlast),
+ .m_axi_wuser (m_axi_wuser),
+ .m_axi_wvalid (m_axi_wvalid),
+ .m_axi_wready (m_axi_wready),
+ .m_axi_bid (m_axi_bid),
+ .m_axi_bresp (m_axi_bresp),
+ .m_axi_buser (m_axi_buser),
+ .m_axi_bvalid (m_axi_bvalid),
+ .m_axi_bready (m_axi_bready),
+ .m_axi_arid (m_axi_arid),
+ .m_axi_araddr (m_axi_araddr),
+ .m_axi_arlen (m_axi_arlen),
+ .m_axi_arsize (m_axi_arsize),
+ .m_axi_arburst (m_axi_arburst),
+ .m_axi_arlock (m_axi_arlock),
+ .m_axi_arcache (m_axi_arcache),
+ .m_axi_arprot (m_axi_arprot),
+ .m_axi_arqos (m_axi_arqos),
+ .m_axi_arregion (m_axi_arregion),
+ .m_axi_aruser (m_axi_aruser),
+ .m_axi_arvalid (m_axi_arvalid),
+ .m_axi_arready (m_axi_arready),
+ .m_axi_rid (m_axi_rid),
+ .m_axi_rdata (m_axi_rdata),
+ .m_axi_rresp (m_axi_rresp),
+ .m_axi_rlast (m_axi_rlast),
+ .m_axi_ruser (m_axi_ruser),
+ .m_axi_rvalid (m_axi_rvalid),
+ .m_axi_rready (m_axi_rready)
+ );
+
+
+ //---------------------------------------------------------------------------
+ // Helper Tasks
+ //---------------------------------------------------------------------------
+
+ // Write a 32-bit register
+ task automatic write_reg(int port, bit [19:0] addr, bit [31:0] value);
+ blk_ctrl.reg_write((2**REPLAY_ADDR_W)*port + addr, value);
+ endtask : write_reg
+
+ // Write a 64-bit register
+ task automatic write_reg_64(int port, bit [19:0] addr, bit [63:0] value);
+ blk_ctrl.reg_write((2**REPLAY_ADDR_W)*port + addr + 0, value[31: 0]);
+ blk_ctrl.reg_write((2**REPLAY_ADDR_W)*port + addr + 4, value[63:32]);
+ endtask : write_reg_64
+
+ // Read a 32-bit register
+ task automatic read_reg(int port, bit [19:0] addr, output logic [63:0] value);
+ blk_ctrl.reg_read((2**REPLAY_ADDR_W)*port + addr, value[31: 0]);
+ endtask : read_reg
+
+ // Read a 32-bit register
+ task automatic read_reg_64(int port, bit [19:0] addr, output logic [63:0] value);
+ blk_ctrl.reg_read((2**REPLAY_ADDR_W)*port + addr + 0, value[31: 0]);
+ blk_ctrl.reg_read((2**REPLAY_ADDR_W)*port + addr + 4, value[63:32]);
+ endtask : read_reg_64
+
+
+ // Generate a set of data items to use as a test payload. The seed argument
+ // can be used to seed the initial value so that the same sequence of data is
+ // repeated.
+ function automatic item_queue_t gen_test_data(
+ int num_items,
+ integer seed = 32'hX
+ );
+ item_t queue[$];
+
+ if (USE_RANDOM) begin
+ // Generate random data, to make it unlikely we get correct value by
+ // coincidence.
+ if (seed !== 32'hX) begin
+ // Seed the RNG with the indicated value
+ std::process p;
+ p = process::self();
+ p.srandom(seed);
+ end
+ for (int i = 0; i < num_items; i++) begin
+ queue.push_back($urandom());
+ end
+ end else begin
+ // Generate sequential data, for easier debugging.
+ static int count = 0;
+ item_t first = 0;
+ if (seed !== 32'hX) first = seed;
+ else begin
+ first = count; // Continue after the last value from previous run
+ count = count + num_items;
+ end
+ for (int i = 0; i < num_items; i++) begin
+ queue.push_back(first+i);
+ end
+ end
+
+ return queue;
+ endfunction : gen_test_data
+
+
+ // Read out and discard all packets received, stopping after there's been no
+ // new packets received for a delay of "timeout".
+ task automatic flush_rx(
+ input int port = 0,
+ input time timeout = CHDR_CLK_PER*100
+ );
+ ChdrPacket #(CHDR_W) chdr_packet;
+ time prev_time;
+
+ begin
+ prev_time = $time;
+
+ while (1) begin
+ // Check if there's a frame waiting
+ if (blk_ctrl.num_received(port) != 0) begin
+ // Read frame
+ blk_ctrl.get_chdr(port, chdr_packet);
+ // Restart timeout
+ prev_time = $time;
+
+ end else begin
+ // If timeout has expired, we're done
+ if ($time - prev_time > timeout) break;
+ // No frame, so wait a cycle
+ #(CHDR_CLK_PER);
+ end
+ end
+ end
+ endtask : flush_rx
+
+
+ // Wait until the expected number of bytes are accumulated in the record
+ // buffer. Produce a failure if the data never arrives.
+ task automatic wait_record_fullness(
+ input int port,
+ input int num_bytes,
+ input time timeout = (10 + num_bytes) * CHDR_CLK_PER * 10
+ );
+ time prev_time;
+ logic [63:0] value;
+
+ // Poll REG_REC_FULLNESS until fullness is reached
+ prev_time = $time;
+ while (1) begin
+ read_reg_64(port, REG_REC_FULLNESS_LO, value);
+ if (value >= num_bytes) break;
+
+ // Check if it's taking too long
+ if ($time - prev_time > timeout) begin
+ `ASSERT_ERROR(0, "Timeout waiting for fullness to be reached");
+ end
+ end
+ endtask : wait_record_fullness
+
+
+ // Make sure nothing is received until the timeout has elapsed
+ task automatic check_rx_idle(
+ input int port,
+ input time timeout = CHDR_CLK_PER * 1000
+ );
+ int active;
+ time start_time;
+
+ @(negedge rfnoc_chdr_clk);
+ start_time = $time;
+ fork
+ begin : tvalid_check
+ // Wait for any activity on tvalid (should be 0 initially)
+ while (!m_rfnoc_chdr_tvalid[port])
+ @(posedge rfnoc_chdr_clk);
+ if ($time - start_time < timeout)
+ `ASSERT_ERROR(0, "Received additional data during idle");
+ end
+ begin
+ // Just wait for the time to elapse
+ #(timeout);
+ end
+ join_any
+ disable tvalid_check;
+ endtask : check_rx_idle
+
+
+ // Record data and start its playback
+ //
+ // port : Replay block port to use
+ // send_items : Data to send to the replay block to be recorded
+ // buffer_size : Buffer size in bytes to configure for record buffer
+ // num_items : Number of items to play back
+ // spp : Samples per packet for playback
+ // base_addr : Base address to use for record buffer
+ // continuous : Set to 1 for continuous playback, 0 for num_items only
+ // timestamp : Timestamp to use for playback
+ //
+ task automatic start_replay (
+ input int port,
+ input item_t send_items[$],
+ input longint unsigned buffer_size = 1024 * MEM_WORD_SIZE,
+ input longint unsigned num_items = 1024,
+ input int spp = SPP,
+ input longint unsigned base_addr = 0,
+ input bit continuous = 1'b0,
+ input logic [63:0] timestamp = 64'bX
+ );
+ logic [31:0] cmd;
+ bit has_time;
+ int expected_fullness;
+
+ // Check for bad input arguments
+ `ASSERT_FATAL(num_items * ITEM_W % MEM_DATA_W == 0,
+ "num_items to play back must be a multiple of the memory data word size");
+ `ASSERT_FATAL(base_addr < 2**MEM_ADDR_W,
+ "Base address is beyond available memory");
+ `ASSERT_FATAL(base_addr + buffer_size <= 2**MEM_ADDR_W,
+ "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");
+
+ // Update record buffer settings
+ write_reg_64(port, REG_REC_BASE_ADDR_LO, base_addr);
+ write_reg_64(port, REG_REC_BUFFER_SIZE_LO, buffer_size);
+ write_reg_64(port, REG_PLAY_BASE_ADDR_LO, base_addr);
+ write_reg_64(port, REG_PLAY_BUFFER_SIZE_LO, buffer_size);
+ write_reg (port, REG_PLAY_WORDS_PER_PKT, spp * ITEM_SIZE / MEM_WORD_SIZE);
+ write_reg (port, REG_PLAY_ITEM_SIZE, ITEM_W/8);
+
+ // Restart the record buffer
+ write_reg(port, REG_REC_RESTART, 0);
+
+ // Write the payload to the record buffer
+ blk_ctrl.send_packets_items(port, send_items);
+
+ // Wait until all the data has been written (up to the size of the buffer)
+ expected_fullness = send_items.size() * (ITEM_W/8) < buffer_size ?
+ send_items.size() * ITEM_SIZE : buffer_size;
+ wait_record_fullness(port, expected_fullness);
+
+ // Set the timestamp for playback
+ if (timestamp !== 64'bX) begin
+ write_reg_64(port, REG_PLAY_CMD_TIME_LO, timestamp);
+ has_time = 1'b1;
+ end else begin
+ has_time = 1'b0;
+ end
+
+ // Start playback of the recorded data
+ if (num_items != 0) begin
+ cmd = continuous ? PLAY_CMD_CONTINUOUS : PLAY_CMD_FINITE;
+ cmd = cmd | (32'(has_time) << REG_PLAY_TIMED_POS);
+ write_reg_64(port, REG_PLAY_CMD_NUM_WORDS_LO, num_items*ITEM_W/MEM_DATA_W);
+ write_reg(port, REG_PLAY_CMD, (32'(has_time) << REG_PLAY_TIMED_POS) | cmd);
+ end
+ endtask : start_replay
+
+
+ // Read the data on the indicated port and check that it matches exp_items.
+ // Also check that the length of the packets received add up to the length of
+ // exp_items, if it takes multiple packets. An error string is returned if
+ // there's an error, otherwise an empty string is returned.
+ //
+ // port : Port on which to receive and verify the data
+ // error_msg : Output string to write error message to, if any
+ // 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)
+ // timestamp : The timestamp we expect to receive for the first item (set to
+ // 'X to skip timestamp checking)
+ //
+ task automatic verify_rx_data(
+ input int port,
+ output string error_msg,
+ input item_t exp_items[$],
+ input logic eob = 1'b1,
+ input logic [63:0] timestamp = 64'bX
+ );
+ item_t recv_items[$];
+ chdr_word_t md[$];
+ packet_info_t pkt_info;
+ int item_count;
+ int packet_count;
+ item_t expected_value;
+ item_t actual_value;
+ chdr_timestamp_t expected_time;
+
+ item_count = 0;
+ packet_count = 0;
+ error_msg = "";
+ expected_time = timestamp;
+
+ while (item_count < exp_items.size()) begin
+ // Grab the next frame
+ blk_ctrl.recv_items_adv(port, recv_items, md, pkt_info);
+
+ // 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());
+ return;
+ end
+
+ // Check the EOB flag
+ if (eob !== 1'bX) begin
+ if (item_count + recv_items.size() >= exp_items.size()) begin
+ // This is the last packet, so make sure EOB matches expected value
+ if (pkt_info.eob != eob) begin
+ $sformat(error_msg,
+ "On packet %0d, expected EOB to be %0b, actual is %0b",
+ packet_count, eob, pkt_info.eob);
+ return;
+ end
+ end else begin
+ // This is NOT the last packet, so EOB should be 0
+ if (pkt_info.eob != 1'b0) begin
+ $display("item_count is %0d", item_count);
+ $display("recv size is %0d", recv_items.size());
+ $display("packet_count is %0d", packet_count);
+ $display("Expected items is %0d", exp_items.size());
+ $sformat(error_msg,
+ "On packet %0d, expected EOB to be 0 mid-burst, actual is %0b",
+ packet_count, pkt_info.eob);
+ return;
+ end
+ end
+ end
+
+ // Check the timestamp
+ if (timestamp !== 64'bX) begin
+ if (!pkt_info.timestamp) begin
+ $sformat(error_msg,
+ "On packet %0d, timestamp is missing",
+ packet_count);
+ return;
+ end
+ if (expected_time != pkt_info.timestamp) begin
+ $sformat(error_msg,
+ "On packet %0d, expected timestamp %X but received %X",
+ packet_count, expected_time, pkt_info.timestamp);
+ return;
+ end
+ expected_time = expected_time + recv_items.size();
+ end else begin
+ // Make sure we don't have a timestamp unexpectedly
+ if (pkt_info.has_time) begin
+ $sformat(error_msg,
+ "On packet %0d, expected no timestamp but received one",
+ packet_count);
+ end
+ end
+
+ packet_count++;
+
+ // Check the payload data
+ for (int i = 0; i < recv_items.size(); i++) begin
+ expected_value = exp_items[item_count];
+ actual_value = recv_items[i];
+ if (actual_value != expected_value) begin
+ $sformat(error_msg,
+ "On item %0d (packet %0d, item index %0d), Expected: 0x%x, Received: 0x%x",
+ item_count, packet_count, i, expected_value, actual_value);
+ return;
+ end
+ item_count++;
+ end
+ end
+ endtask : verify_rx_data
+
+
+ //---------------------------------------------------------------------------
+ // Register Test Tasks
+ //---------------------------------------------------------------------------
+
+ // Test a read/write register for correct functionality
+ //
+ // port : Replay block port to use
+ // addr : Register byte address
+ // width : Register size (32 or 64-bit)
+ // num_bits : Number of bits actually used by register
+ // initial_value : Value we expect to read initially
+ //
+ task automatic test_read_write_reg(
+ int port,
+ bit [19:0] addr,
+ int width,
+ int num_bits = width,
+ logic [63:0] initial_value = 0
+ );
+ string err_msg;
+
+ err_msg = $sformatf("Register 0x%X failed read/write test: ", addr);
+
+ if (width <= 32) begin
+ logic [31:0] value;
+ logic [31:0] expected;
+
+ // Check initial value
+ expected = initial_value;
+ read_reg(port, addr, value);
+ `ASSERT_ERROR(value == expected, {err_msg, "initial value"});
+
+ // Test writing 0
+ expected = 0;
+ write_reg(port, addr, expected);
+ read_reg(port, addr, value);
+ `ASSERT_ERROR(value == expected, {err_msg, "write zero"});
+
+ // Write maximum value
+ expected = (64'(1'b1) << num_bits) - 1;
+ write_reg(port, addr, '1);
+ read_reg(port, addr, value);
+ `ASSERT_ERROR(value == expected, {err_msg, "write max value"});
+
+ // Restore original value
+ write_reg(port, addr, initial_value);
+
+ end else begin
+ logic [63:0] value;
+ logic [63:0] expected;
+
+ // Check initial value
+ expected = initial_value;
+ read_reg_64(port, addr, value);
+ `ASSERT_ERROR(value == expected, {err_msg, "initial value"});
+
+ // Test writing 0
+ expected = 0;
+ write_reg_64(port, addr, expected);
+ read_reg_64(port, addr, value);
+ `ASSERT_ERROR(value == expected, {err_msg, "write zero"});
+
+ // Write maximum value
+ expected = (64'(1'b1) << num_bits) - 1;
+ write_reg_64(port, addr, '1);
+ read_reg_64(port, addr, value);
+ `ASSERT_ERROR(value == expected, {err_msg, "write max value"});
+
+ // Restore original value
+ write_reg(port, addr, initial_value);
+ end
+ endtask : test_read_write_reg
+
+
+ // Test a read-only register for correct functionality
+ //
+ // port : Replay block port to use
+ // addr : Register byte address
+ // width : Register size (32 or 64-bit)
+ // exp_value : Value we expect to read
+ //
+ task automatic test_read_reg(
+ int port,
+ bit [19:0] addr,
+ int width,
+ logic [63:0] exp_value = 0
+ );
+ string err_msg;
+ logic [63:0] value;
+
+ err_msg = $sformatf("Register 0x%X failed read test", addr);
+ if (width <= 32) begin
+ read_reg(port, addr, value);
+ value[63:32] = 0;
+ end else begin
+ read_reg_64(port, addr, value);
+ end
+
+ `ASSERT_ERROR(value == exp_value, err_msg);
+ endtask : test_read_reg
+
+
+ //---------------------------------------------------------------------------
+ // Test block info
+ //---------------------------------------------------------------------------
+
+ task automatic test_block_info();
+ test.start_test("Verify Block Info", 2us);
+ `ASSERT_ERROR(blk_ctrl.get_noc_id() == NOC_ID, "Incorrect NOC_ID Value");
+ `ASSERT_ERROR(blk_ctrl.get_num_data_i() == NUM_PORTS, "Incorrect NUM_DATA_I Value");
+ `ASSERT_ERROR(blk_ctrl.get_num_data_o() == NUM_PORTS, "Incorrect NUM_DATA_O Value");
+ `ASSERT_ERROR(blk_ctrl.get_mtu() == MTU, "Incorrect MTU Value");
+ test.end_test();
+ endtask : test_block_info
+
+
+ //---------------------------------------------------------------------------
+ // Test registers
+ //---------------------------------------------------------------------------
+ //
+ // This test confirms that all read/write registers are appropriately
+ // readable, writable, and update as expected.
+ //
+ //---------------------------------------------------------------------------
+
+ task automatic test_registers(int port = 0);
+ if (TEST_REGS) begin
+ int major, minor, compat;
+ test.start_test("Test registers", TEST_TIMEOUT);
+
+ // Determine expected compat value
+ major = rfnoc_block_replay_i.gen_replay_blocks[0].axis_replay_i.COMPAT_MAJOR;
+ minor = rfnoc_block_replay_i.gen_replay_blocks[0].axis_replay_i.COMPAT_MINOR;
+ compat = (major << REG_MAJOR_POS) | (minor << REG_MINOR_POS);
+
+ test_read_reg (port, REG_COMPAT, 32, compat);
+ test_read_write_reg(port, REG_REC_BASE_ADDR_LO, 64, MEM_ADDR_W);
+ test_read_write_reg(port, REG_REC_BUFFER_SIZE_LO, 64, MEM_ADDR_W+1);
+ test_read_write_reg(port, REG_PLAY_BASE_ADDR_LO, 64, MEM_ADDR_W);
+ test_read_write_reg(port, REG_PLAY_BUFFER_SIZE_LO, 64, MEM_ADDR_W+1);
+ test_read_write_reg(port, REG_PLAY_CMD_NUM_WORDS_LO, 64, 64);
+ test_read_write_reg(port, REG_PLAY_CMD_TIME_LO, 64, 64);
+ test_read_write_reg(port, REG_PLAY_WORDS_PER_PKT, 32,
+ REG_PLAY_WORDS_PER_PKT_LEN, REG_PLAY_WORDS_PER_PKT_INIT);
+
+ // The following registers are read only:
+ test_read_reg(port, REG_MEM_SIZE, 32,
+ (32'(MEM_DATA_W) << REG_DATA_SIZE_POS) |
+ (32'(MEM_ADDR_W) << REG_ADDR_SIZE_POS)
+ );
+ test_read_reg(port, REG_REC_FULLNESS_LO, 64);
+
+ // The following registers are write only and aren't tested here.
+ // REG_REC_RESTART - Tested during every record operation
+ // REG_PLAY_CMD - Tested during every playback operation
+
+ test.end_test();
+ end
+ endtask : test_registers
+
+
+ //---------------------------------------------------------------------------
+ // Test basic recording and playback
+ //---------------------------------------------------------------------------
+ //
+ // A quick and easy test to make sure replay is working.
+ //
+ //---------------------------------------------------------------------------
+
+ task automatic test_basic(int port = 0);
+ item_t send_items[$];
+ string error_string;
+ logic [31:0] cmd;
+
+ test.start_test("Basic recording and playback", TEST_TIMEOUT);
+
+ // Configure buffers registers
+ write_reg_64(port, REG_REC_BASE_ADDR_LO, 1024);
+ write_reg_64(port, REG_REC_BUFFER_SIZE_LO, BPP);
+ write_reg_64(port, REG_PLAY_BASE_ADDR_LO, 1024);
+ write_reg_64(port, REG_PLAY_BUFFER_SIZE_LO, BPP);
+ write_reg (port, REG_PLAY_WORDS_PER_PKT, SPP * ITEM_SIZE/MEM_WORD_SIZE);
+ write_reg (port, REG_REC_RESTART, 0);
+
+ // Send a random packet
+ send_items = gen_test_data(SPP);
+ blk_ctrl.send_items(port, send_items);
+
+ // Wait until all the data has been written
+ wait_record_fullness(port, SPP*ITEM_SIZE);
+
+ // Start replay
+ cmd = PLAY_CMD_FINITE;
+
+ write_reg_64(port, REG_PLAY_CMD_NUM_WORDS_LO, SPP*ITEM_SIZE/MEM_WORD_SIZE);
+ write_reg(port, REG_PLAY_CMD, cmd);
+
+ // Check the output
+ verify_rx_data(port, error_string, send_items, 1);
+ `ASSERT_ERROR(error_string == "", error_string);
+
+ // Make sure there are no more packets
+ check_rx_idle(port);
+
+ test.end_test();
+ endtask : test_basic
+
+
+ //---------------------------------------------------------------------------
+ // Test packet sizes
+ //---------------------------------------------------------------------------
+ //
+ // Test boundary conditions where the packet size is close to the memory
+ // burst size and the WORDS_PER_PKT size.
+ //
+ //---------------------------------------------------------------------------
+
+ task automatic test_packet_sizes(int port = 0);
+ item_t send_items[$];
+ string error_string;
+ int buffer_size;
+ int mem_words, num_items, spp;
+
+ test.start_test("Test packet sizes", TEST_TIMEOUT);
+
+ // Calculate buffer size in bytes (2 memory bursts)
+ buffer_size = 2*MEM_BURST_LEN * MEM_WORD_SIZE;
+
+ // Calculate one memory burst size in words
+ mem_words = 2*MEM_BURST_LEN;
+ num_items = mem_words * MEM_WORD_SIZE / ITEM_SIZE;
+
+ // Generate payload to use for testing (2 memory burst)
+ send_items = gen_test_data(num_items);
+
+ // 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.
+
+ // Test packet size equals burst size
+ spp = MEM_BURST_LEN * MEM_WORD_SIZE / ITEM_SIZE;
+ start_replay(port, send_items, buffer_size, num_items, spp);
+ verify_rx_data(port, error_string, send_items, 1);
+ `ASSERT_ERROR(error_string == "", error_string);
+
+ // Test packet is one less than burst size
+ spp = (MEM_BURST_LEN-1) * MEM_WORD_SIZE / ITEM_SIZE;
+ start_replay(port, send_items, buffer_size, num_items, spp);
+ verify_rx_data(port, error_string, send_items, 1);
+ `ASSERT_ERROR(error_string == "", error_string);
+
+ // Test packet is one more than burst size
+ spp = (MEM_BURST_LEN+1) * MEM_WORD_SIZE / ITEM_SIZE;
+ start_replay(port, send_items, buffer_size, num_items, spp);
+ verify_rx_data(port, error_string, send_items, 1);
+ `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;
+
+ // 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);
+ `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);
+ `ASSERT_ERROR(error_string == "", error_string);
+
+ // Make sure there are no more packets
+ check_rx_idle(port);
+
+ test.end_test();
+ endtask : test_packet_sizes
+
+
+ //---------------------------------------------------------------------------
+ // Test small replay
+ //---------------------------------------------------------------------------
+ //
+ // Make sure the smallest possible replay size works correctly.
+ //
+ //---------------------------------------------------------------------------
+
+ task automatic test_small_replay(int port = 0);
+ item_t send_items[$];
+ string error_string;
+ int mem_words, num_items;
+
+ test.start_test("Test small replay", TEST_TIMEOUT);
+
+ // Test the smallest size we can
+ mem_words = 1;
+ num_items = mem_words * MEM_WORD_SIZE / ITEM_SIZE;
+
+ send_items = gen_test_data(num_items);
+ start_replay(port, send_items, BPP, num_items);
+ verify_rx_data(port, error_string, send_items, 1);
+ `ASSERT_ERROR(error_string == "", error_string);
+
+ test.end_test();
+ endtask : test_small_replay
+
+
+ //---------------------------------------------------------------------------
+ // Test playback that's larger than buffer
+ //---------------------------------------------------------------------------
+ //
+ // We want to make sure that playback wraps as expected back to the beginning
+ // of the buffer and that buffers that aren't a multiple of the burst size
+ // wrap correctly.
+ //
+ //---------------------------------------------------------------------------
+
+ task automatic test_oversized_playback(int port = 0);
+ item_t send_items[$];
+ item_t exp_items[$];
+ string error_string;
+ logic [31:0] cmd;
+ int buffer_size;
+ int num_items_rec;
+ int num_items_play;
+
+ test.start_test("Test oversized playback", TEST_TIMEOUT);
+
+ // 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)
+ ITEM_SIZE;
+
+ // Start playback of data
+ send_items = gen_test_data(num_items_rec);
+ start_replay(port, send_items, buffer_size, num_items_play);
+
+ // Since we recorded 1.5 memory bursts and are playing back 2, we should
+ // get the a repeat of the first third of data.
+ exp_items = { send_items, send_items[0:num_items_rec/3-1] };
+ verify_rx_data(port, error_string, exp_items, 1);
+ `ASSERT_ERROR(error_string == "", error_string);
+
+ // Make sure there are no more packets
+ check_rx_idle(port);
+
+ test.end_test();
+ endtask : test_oversized_playback
+
+
+ //---------------------------------------------------------------------------
+ // Test continuous (infinite) playback
+ //---------------------------------------------------------------------------
+
+ task automatic test_continuous(int port = 0);
+ item_t send_items[$];
+ string error_string;
+ logic [31:0] cmd;
+ int num_items, mem_words;
+
+ test.start_test("Test continuous mode", TEST_TIMEOUT);
+
+ mem_words = 70;
+ num_items = mem_words * MEM_WORD_SIZE / ITEM_SIZE;
+
+ // 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);
+
+ // Restart the record buffer
+ write_reg(port, REG_REC_RESTART, 0);
+
+ // Write num_items to record buffer
+ send_items = gen_test_data(num_items);
+ blk_ctrl.send_items(port, send_items);
+
+ // Wait until all the data has been written
+ wait_record_fullness(port, num_items*ITEM_SIZE);
+
+ // 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);
+
+ // 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);
+ `ASSERT_ERROR(error_string == "", error_string);
+ end
+
+ // Send the stop command
+ cmd = PLAY_CMD_STOP;
+ write_reg(port, REG_PLAY_CMD, cmd);
+
+ // Keep reading packets until we get the EOB
+ begin
+ item_t recv_items[$];
+ chdr_word_t md[$];
+ packet_info_t pkt_info;
+ item_t expected_value;
+ item_t actual_value;
+ int item_count = 0;
+ do begin
+ blk_ctrl.recv_items_adv(port, recv_items, md, pkt_info);
+
+ // Check the data
+ for (int i = 0; i < recv_items.size(); i++) begin
+ expected_value = send_items[item_count];
+ actual_value = recv_items[i];
+ `ASSERT_ERROR(
+ actual_value == expected_value,
+ $sformatf("Data mismatch while waiting for EOB. Expected: 0x%x, Received: 0x%x",
+ expected_value, actual_value)
+ );
+ item_count++;
+ if (item_count >= send_items.size()) item_count = 0;
+ end
+ end while (pkt_info.eob != 1);
+ end
+
+ // Make sure there are no more packets
+ check_rx_idle(port);
+
+ test.end_test();
+ endtask : test_continuous
+
+
+ //---------------------------------------------------------------------------
+ // Test changing the offset
+ //---------------------------------------------------------------------------
+ //
+ // Change the offset to be near the maximum memory address then test filling
+ // the buffer and playing it back.
+ //
+ //---------------------------------------------------------------------------
+
+ task automatic test_offset(int port = 0);
+ item_t send_items[$];
+ string error_string;
+ int mem_words, num_items;
+ int buffer_size;
+ longint unsigned base_addr;
+ logic [63:0] val64;
+
+ test.start_test("Test offset", TEST_TIMEOUT);
+
+ mem_words = 32; // Number of memory words to send for each record
+ num_items = mem_words * MEM_WORD_SIZE / ITEM_SIZE;
+ buffer_size = mem_words * MEM_WORD_SIZE;
+
+ // Make our offset buffer_size before the end of the buffer
+ base_addr = 2**MEM_ADDR_W - buffer_size;
+
+ // Record and playback the data
+ send_items = gen_test_data(num_items);
+ start_replay(port, send_items, buffer_size, num_items, SPP, base_addr);
+
+ // Check the result
+ verify_rx_data(port, error_string, send_items, 1);
+
+ // Check the fullness
+ read_reg_64(port, REG_REC_FULLNESS_LO, val64);
+ `ASSERT_ERROR(val64 == buffer_size, "Memory fullness is not correct");
+
+ // Send more data, even though buffer should be full
+ send_items = gen_test_data(num_items);
+ blk_ctrl.send_items(port, send_items);
+
+ // Give extra time for the data to get through
+ #(CHDR_CLK_PER * num_items * 20);
+
+ // Make sure fullness didn't change
+ read_reg_64(port, REG_REC_FULLNESS_LO, val64);
+ `ASSERT_ERROR(val64 == buffer_size, "Memory fullness is not correct");
+
+ // Restart recording, to get the rest of the data
+ write_reg(port, REG_REC_RESTART, 0);
+
+ // Wait for the rest of the data to be recorded
+ wait_record_fullness(port, buffer_size);
+
+ // Restart recording
+ write_reg(port, REG_REC_RESTART, 0);
+
+ // Make sure the fullness went back to 0
+ read_reg_64(port, REG_REC_FULLNESS_LO, val64);
+ `ASSERT_ERROR(val64 == 0, "Memory fullness is not correct");
+
+ test.end_test();
+ endtask : test_offset
+
+
+ //---------------------------------------------------------------------------
+ // Test stopping with multiple commands queued
+ //---------------------------------------------------------------------------
+
+ task automatic test_stop_queue(int port = 0);
+ item_t send_items[$];
+ int buffer_size;
+ int num_items;
+ int play_words;
+
+ test.start_test("Test stopping with queued commands", TEST_TIMEOUT);
+
+ // Configure a small buffer
+ num_items = 64 * MEM_WORD_SIZE / ITEM_SIZE;
+ buffer_size = num_items * ITEM_SIZE;
+
+ // Choose a huge number for playback
+ play_words = 'h01000000;
+
+ // Start playing back this huge number
+ send_items = gen_test_data(num_items);
+ start_replay(port, send_items, buffer_size, play_words);
+
+ // Queue up a bunch more commands
+ repeat (4) begin
+ write_reg(port, REG_PLAY_CMD, PLAY_CMD_FINITE);
+ end
+
+ // Stop playback (empty the queue)
+ write_reg(port, REG_PLAY_CMD, PLAY_CMD_STOP);
+
+ // Keep reading packets until we get the EOB.
+ begin
+ item_t recv_items[$];
+ chdr_word_t md[$];
+ packet_info_t pkt_info;
+ item_t expected_value;
+ item_t actual_value;
+ int item_count = 0;
+ do begin
+ blk_ctrl.recv_items_adv(port, recv_items, md, pkt_info);
+
+ // Check the data
+ for (int i = 0; i < recv_items.size(); i++) begin
+ expected_value = send_items[item_count];
+ actual_value = recv_items[i];
+ `ASSERT_ERROR(
+ actual_value == expected_value,
+ $sformatf("Data mismatch while waiting for EOB. Expected: 0x%x, Received: 0x%x",
+ expected_value, actual_value)
+ );
+ item_count++;
+ if (item_count >= send_items.size()) item_count = 0;
+ end
+ end while (pkt_info.eob != 1);
+ end
+
+ // Make sure there are no more packets
+ check_rx_idle(port);
+
+ test.end_test();
+ endtask : test_stop_queue
+
+
+ //---------------------------------------------------------------------------
+ // Test overfilled record buffer
+ //---------------------------------------------------------------------------
+ //
+ // Record more words than the buffer can fit. Make sure we don't overflow our
+ // buffer and make sure reading it back plays only the data that should have
+ // been captured.
+ //
+ //---------------------------------------------------------------------------
+
+ task automatic test_overfilled_record(int port = 0);
+ item_t send_items[$];
+ item_t exp_items[$];
+ string error_string;
+ int num_items, mem_words;
+ 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
+
+ // Restart the record buffer
+ write_reg(port, REG_REC_RESTART, 0);
+
+ // Generate more record data than can fit in the buffer
+ 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);
+
+ // 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);
+ `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);
+ `ASSERT_ERROR(error_string == "", error_string);
+
+ // Make sure REG_REC_FULLNESS didn't keep increasing
+ read_reg_64(port, REG_REC_FULLNESS_LO, val64);
+ `ASSERT_ERROR(
+ val64 == num_items_buf*ITEM_SIZE,
+ "REG_REC_FULLNESS went beyond expected bounds"
+ );
+
+ // Reset record buffer so that it accepts the rest of the data that's
+ // stalled in input FIFO.
+ write_reg_64(port, REG_REC_BUFFER_SIZE_LO, num_items*ITEM_SIZE);
+ write_reg(port, REG_REC_RESTART, 0);
+
+ // Wait until all the data has been written
+ wait_record_fullness(port, (num_items - num_items_buf)*ITEM_SIZE);
+
+ test.end_test();
+ endtask : test_overfilled_record
+
+
+ //---------------------------------------------------------------------------
+ // Test burst size
+ //---------------------------------------------------------------------------
+ //
+ // Record amount of data that's larger than the configured RAM burst length
+ // to make sure full-length bursts are handled correctly.
+ //
+ //---------------------------------------------------------------------------
+
+ task automatic test_burst_size(int port = 0);
+ item_t send_items[$];
+ string error_string;
+ int num_items, mem_words;
+ int buffer_size;
+
+ test.start_test("Test burst size", TEST_TIMEOUT);
+
+ mem_words = 4*MEM_BURST_LEN; // Multiple of the burst size
+ num_items = mem_words * MEM_WORD_SIZE / ITEM_SIZE;
+ buffer_size = mem_words * MEM_WORD_SIZE; // Size in bytes
+
+ send_items = gen_test_data(num_items);
+ start_replay(port, send_items, buffer_size, num_items);
+ verify_rx_data(port, error_string, send_items, 1);
+ `ASSERT_ERROR(error_string == "", error_string);
+
+ test.end_test();
+ endtask : test_burst_size
+
+
+ //---------------------------------------------------------------------------
+ // Test 4K AXI boundary
+ //---------------------------------------------------------------------------
+ //
+ // AXI doesn't allow bursts to cross a 4 KiB boundary. Make sure that we can
+ // correctly replay up to and across this boundary.
+ //
+ //---------------------------------------------------------------------------
+
+ task automatic test_4k_boundary(int port = 0);
+ item_t send_items[$];
+ string error_string;
+ int num_items, mem_words;
+ int buffer_size;
+ int base_addr;
+
+ test.start_test("Test 4K AXI Boundary", TEST_TIMEOUT);
+
+ //
+ // Test bursting up to and after boundary
+ //
+
+ // Setup two bursts
+ mem_words = 2*MEM_BURST_LEN;
+ num_items = mem_words * MEM_WORD_SIZE / ITEM_SIZE;
+ buffer_size = mem_words * MEM_WORD_SIZE; // Size in bytes
+
+ // Choose a base address such that we end the first burst at the 4 KiB
+ // boundary and start the next burst on the boundary.
+ if (mem_words/2 * MEM_WORD_SIZE >= AXI_ALIGNMENT) begin
+ // In this case our memory burst size is bigger than 4K, so we're
+ // guaranteed to cross the 4K alignment boundary.
+ base_addr = 0;
+ end else begin
+ base_addr = AXI_ALIGNMENT - (mem_words/2)*MEM_WORD_SIZE;
+ end
+
+ // Record data across the 4K boundary then play it back
+ send_items = gen_test_data(num_items);
+ start_replay(port, send_items, buffer_size, num_items, SPP, base_addr);
+
+ // Verify the data
+ verify_rx_data(port, error_string, send_items, 1);
+ `ASSERT_ERROR(error_string == "", error_string);
+
+ //
+ // Test bursting across boundary
+ //
+
+ // Setup a single burst across the 4 KiB boundary
+ mem_words = MEM_BURST_LEN;
+ num_items = mem_words * MEM_WORD_SIZE / ITEM_SIZE;
+ buffer_size = mem_words * MEM_WORD_SIZE; // Size in bytes
+
+ // Choose a base address such that we end a burst on the 4 KiB boundary,
+ // then continue on the other side.
+ if (mem_words/2 * MEM_WORD_SIZE >= AXI_ALIGNMENT) begin
+ // In this case our memory burst size is bigger than 4K, so we're
+ // guaranteed to cross the 4K alignment boundary.
+ base_addr = 0;
+ end else begin
+ base_addr = AXI_ALIGNMENT - (mem_words/2)*MEM_WORD_SIZE;
+ end
+
+ // Record data across the 4K boundary then play it back
+ send_items = gen_test_data(num_items);
+ start_replay(port, send_items, buffer_size, num_items, SPP, base_addr);
+
+ // Verify the data received
+ verify_rx_data(port, error_string, send_items, 1);
+ `ASSERT_ERROR(error_string == "", error_string);
+
+ test.end_test();
+ endtask : test_4k_boundary
+
+
+ //---------------------------------------------------------------------------
+ // Test small packet size (smaller than memory burst size)
+ //---------------------------------------------------------------------------
+
+ task test_small_packet(int port = 0);
+ item_t send_items[$];
+ string error_string;
+ logic [31:0] cmd;
+ int buffer_size;
+ int num_items;
+ int pkt_size_words;
+ int pkt_size_items;
+
+ test.start_test("Test small packet size", TEST_TIMEOUT);
+
+ //
+ // Test smaller than burst size
+ //
+
+ buffer_size = 2 * MEM_BURST_LEN * MEM_WORD_SIZE; // 2 memory bursts in size (in bytes)
+ num_items = buffer_size / ITEM_SIZE; // Same as buffer_size (in items)
+
+ pkt_size_words = MEM_BURST_LEN / 4;
+ pkt_size_items = pkt_size_words * MEM_WORD_SIZE / ITEM_SIZE;
+
+ 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.
+ 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));
+ `ASSERT_ERROR(error_string == "", error_string);
+ end
+
+ //
+ // Test shortest supported packet size (WPP = 2)
+ //
+
+ buffer_size = 2 * MEM_BURST_LEN * MEM_WORD_SIZE; // 2 memory bursts in size (in bytes)
+ num_items = 20 * MEM_WORD_SIZE / ITEM_SIZE; // 20 memory words
+
+ pkt_size_words = 2;
+ pkt_size_items = pkt_size_words * MEM_WORD_SIZE / ITEM_SIZE;
+
+ send_items = gen_test_data(num_items);
+ start_replay(port, send_items, buffer_size, num_items, pkt_size_items);
+
+ // We should get many packets with length equal to the memory word size,
+ // 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);
+ `ASSERT_FATAL(error_string == "", error_string);
+ end
+
+ test.end_test();
+ endtask : test_small_packet
+
+
+ //---------------------------------------------------------------------------
+ // Test timed playback
+ //---------------------------------------------------------------------------
+
+ task automatic test_timed_playback(int port = 0);
+ item_t send_items[$];
+ string error_string;
+ logic [64:0] timestamp;
+ int num_items, mem_words;
+ int buffer_size;
+ int spp;
+
+ test.start_test("Test timed playback", TEST_TIMEOUT);
+
+ mem_words = MEM_BURST_LEN;
+ num_items = mem_words * MEM_WORD_SIZE / ITEM_SIZE;
+ buffer_size = num_items * ITEM_SIZE;
+ timestamp = 64'h0123456789ABCDEF;
+
+ // Set the packet size small enough so that we get multiple packets
+ // (multiple timestamps).
+ spp = num_items/8;
+
+ 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);
+ `ASSERT_ERROR(error_string == "", error_string);
+
+ test.end_test();
+ endtask : test_timed_playback
+
+
+ //---------------------------------------------------------------------------
+ // Test multiple ports
+ //---------------------------------------------------------------------------
+ //
+ // This test ensures that working with one port isn't accidentally affecting
+ // another port. Their operation should be independent.
+ //
+ //---------------------------------------------------------------------------
+
+ task automatic test_multiple_ports(int port = 0);
+ // This test only applies if there is more than one port
+ if (NUM_PORTS > 1) begin
+ bit [63:0] val64;
+ int num_items;
+ int mem_words;
+ item_t test_data[$], port0_data[$], port1_data[$];
+ string error_str;
+
+ // We test port+0 and port+1
+ `ASSERT_FATAL(NUM_PORTS > port+1, "Not enough ports for this test");
+
+ test.start_test("Test multiple ports", TEST_TIMEOUT);
+
+ //-----------------------------------------
+ // Verify that registers are independent
+ //-----------------------------------------
+
+ write_reg_64(port+0, REG_REC_BASE_ADDR_LO, 'hA);
+ write_reg_64(port+1, REG_REC_BASE_ADDR_LO, 'hB);
+ read_reg_64(port+0, REG_REC_BASE_ADDR_LO, val64);
+ write_reg(port+0, REG_PLAY_WORDS_PER_PKT, 'hC);
+ write_reg(port+1, REG_PLAY_WORDS_PER_PKT, 'hD);
+ `ASSERT_ERROR(val64 == 'hA, "Register didn't read correct value");
+ read_reg_64(port+1, REG_REC_BASE_ADDR_LO, val64);
+ `ASSERT_ERROR(val64 == 'hB, "Register didn't read correct value");
+ read_reg(port+0, REG_PLAY_WORDS_PER_PKT, val64);
+ `ASSERT_ERROR(val64 == 'hC, "Register didn't read correct value");
+ read_reg(port+1, REG_PLAY_WORDS_PER_PKT, val64);
+ `ASSERT_ERROR(val64 == 'hD, "Register didn't read correct value");
+
+ //-----------------------------------------------
+ // Verify the memory interfaces are independent
+ //-----------------------------------------------
+
+ // Configure the two interfaces using the same settings and make sure
+ // they read independently. This assumes that each port has its own
+ // address space in the attached memories.
+
+ for (int i = port; i < 2; i++) begin
+ write_reg_64(i, REG_REC_BASE_ADDR_LO, 0);
+ write_reg_64(i, REG_REC_BUFFER_SIZE_LO, BPP);
+ write_reg_64(i, REG_PLAY_BASE_ADDR_LO, 0);
+ write_reg_64(i, REG_PLAY_BUFFER_SIZE_LO, BPP);
+ write_reg(i, REG_REC_RESTART, 0);
+ end
+
+ num_items = BPP / ITEM_SIZE;
+ mem_words = BPP / MEM_WORD_SIZE;
+ test_data = gen_test_data(2*num_items);
+ port0_data = test_data[ 0 : num_items-1];
+ port1_data = test_data[num_items : 2*num_items-1];
+
+ // Record the two test payloads
+ blk_ctrl.send_items(port+0, port0_data);
+ blk_ctrl.send_items(port+1, port1_data);
+
+ // Play back on each port
+ 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);
+ write_reg(port+1, REG_PLAY_CMD, PLAY_CMD_FINITE);
+
+ // Check the output from each port
+ verify_rx_data(port+0, error_str, port0_data, 1);
+ `ASSERT_ERROR(error_str == "", error_str);
+ verify_rx_data(port+1, error_str, port1_data, 1);
+ `ASSERT_ERROR(error_str == "", error_str);
+
+ test.end_test();
+ end
+
+ endtask : test_multiple_ports
+
+
+ //---------------------------------------------------------------------------
+ // Test Filling the memory
+ //---------------------------------------------------------------------------
+ //
+ // In this test we configure the buffers to use the entire memory, then send
+ // more than a full memory worth of data. This verifies that the fullness is
+ // correct up to the maximum size and that no overflow occurs.
+ //
+ //---------------------------------------------------------------------------
+
+ task test_full_memory(int port = 0);
+ // This test can take a long time, so we only run it if enabled by the
+ // parameter.
+ if (TEST_FULL) begin
+ item_t send_items[$];
+ item_t recv_items[$];
+ int num_items, num_packets;
+ longint unsigned mem_size;
+ logic [63:0] val64;
+
+ test.start_test("Test full memory", 2ms);
+
+ mem_size = 2**MEM_ADDR_W; // Memory size in bytes
+ num_items = 2**$clog2(SPP); // Pick a power of 2 near SPP
+ num_packets = mem_size / ITEM_SIZE / num_items;
+
+ // Set up entire memory as the record/playback buffer
+ write_reg_64(port, REG_REC_BASE_ADDR_LO, 0);
+ write_reg_64(port, REG_REC_BUFFER_SIZE_LO, mem_size);
+ write_reg_64(port, REG_PLAY_BASE_ADDR_LO, 0);
+ write_reg_64(port, REG_PLAY_BUFFER_SIZE_LO, mem_size);
+ write_reg (port, REG_PLAY_WORDS_PER_PKT, num_items * ITEM_SIZE / MEM_WORD_SIZE);
+ write_reg (port, REG_REC_RESTART, 0);
+
+ // Send enough data to fill the buffer, plus an extra packet
+ for (int i = 0; i < num_packets+1; i++) begin
+ // Send a different random sequence for each packet
+ send_items = gen_test_data(num_items, i*num_items);
+ blk_ctrl.send_items(port, send_items);
+ end
+
+ // Wait for the memory to fill
+ wait_record_fullness(port, mem_size);
+
+ // Give extra time for the last packet
+ #(CHDR_CLK_PER * num_items * 20);
+
+ // Check the fullness
+ read_reg_64(port, REG_REC_FULLNESS_LO, val64);
+ `ASSERT_ERROR(val64 == mem_size, "Memory fullness is not correct");
+
+ // Play back the entire memory, plus one word, which should wrap around
+ write_reg_64(port, REG_PLAY_CMD_NUM_WORDS_LO, mem_size / MEM_WORD_SIZE + 1);
+ write_reg(port, REG_PLAY_CMD, PLAY_CMD_FINITE);
+ for (int i = 0; i < num_packets; i++) begin
+ // Regenerate the same sequence of test data
+ send_items = gen_test_data(num_items, i*num_items);
+ blk_ctrl.recv_items(port, recv_items);
+ `ASSERT_ERROR(
+ ChdrData#(CHDR_W, ITEM_W)::item_equal(send_items, recv_items),
+ "Playback data did not match"
+ );
+ end
+ // Verify the last word
+ send_items = gen_test_data(MEM_WORD_SIZE/ITEM_SIZE, 0);
+ blk_ctrl.recv_items(port, recv_items);
+ $display("received: %p", recv_items);
+ $display("expected: %p", send_items);
+ `ASSERT_ERROR(
+ ChdrData#(CHDR_W, ITEM_W)::item_equal(send_items, recv_items),
+ "Playback data did not match on last word"
+ );
+
+ // Restart recording to get the extra packet we sent at the beginning
+ write_reg(port, REG_REC_RESTART, 0);
+ wait_record_fullness(port, num_items*ITEM_SIZE);
+
+ // Playback the new data, which should continue the values from the last
+ // record operation.
+ write_reg_64(port, REG_PLAY_CMD_NUM_WORDS_LO, num_items * ITEM_SIZE / MEM_WORD_SIZE);
+ write_reg(port, REG_PLAY_CMD, PLAY_CMD_FINITE);
+ send_items = gen_test_data(num_items, num_packets*num_items);
+ blk_ctrl.recv_items(port, recv_items);
+ `ASSERT_ERROR(
+ ChdrData#(CHDR_W, ITEM_W)::item_equal(send_items, recv_items),
+ "Playback data did not match"
+ );
+
+ // Make sure there are no more packets
+ check_rx_idle(port);
+
+ test.end_test();
+ end
+ endtask : test_full_memory
+
+
+ //---------------------------------------------------------------------------
+ // Main Test Process
+ //---------------------------------------------------------------------------
+
+ initial begin : tb_main
+ string tb_name;
+
+ // 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",
+ "MEM_DATA_W = %03d, MEM_ADDR_W = %02d,\n",
+ "TEST_REGS = %03d, TEST_FULL = %02d,\n",
+ "STALL_PROB = %03d" },
+ CHDR_W, ITEM_W, NUM_PORTS, MEM_DATA_W, MEM_ADDR_W, TEST_REGS, TEST_FULL, STALL_PROB
+ );
+
+ // Initialize the test exec object for this testbench
+ test.start_tb(tb_name);
+
+ // 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
+ // running, which speeds up simulation time.
+ rfnoc_chdr_clk_gen.start();
+ rfnoc_ctrl_clk_gen.start();
+ mem_clk_gen.start();
+
+ // Start the BFMs running
+ blk_ctrl.run();
+
+ //--------------------------------
+ // Reset
+ //--------------------------------
+
+ test.start_test("Flush block then reset it", 10us);
+ blk_ctrl.flush_and_reset();
+ test.end_test();
+
+ //--------------------------------
+ // Test Sequences
+ //--------------------------------
+
+ test_block_info();
+ for (int port = 0; port < NUM_PORTS; port++) begin
+ // Run the basic tests on all ports
+ test_registers(port);
+ test_basic(port);
+ end
+ test_multiple_ports();
+ test_packet_sizes();
+ test_small_replay();
+ test_oversized_playback();
+ test_continuous();
+ test_stop_queue();
+ test_offset();
+ test_overfilled_record();
+ test_burst_size();
+ test_4k_boundary();
+ test_small_packet();
+ test_timed_playback();
+ test_full_memory();
+
+ //--------------------------------
+ // Finish Up
+ //--------------------------------
+
+ // Display final statistics and results, bot don't $finish
+ test.end_tb(0);
+
+ // Kill the clocks to end this instance of the testbench
+ rfnoc_chdr_clk_gen.kill();
+ rfnoc_ctrl_clk_gen.kill();
+ mem_clk_gen.kill();
+ end : tb_main
+
+endmodule : rfnoc_block_replay_tb
+
+
+`default_nettype wire
diff --git a/fpga/usrp3/lib/rfnoc/core/axis_data_to_chdr.v b/fpga/usrp3/lib/rfnoc/core/axis_data_to_chdr.v
index 50a4b7615..2f7ee11d8 100644
--- a/fpga/usrp3/lib/rfnoc/core/axis_data_to_chdr.v
+++ b/fpga/usrp3/lib/rfnoc/core/axis_data_to_chdr.v
@@ -231,7 +231,6 @@ module axis_data_to_chdr #(
wire in_pyld_tlast;
wire in_pyld_tvalid;
wire in_pyld_tready;
- wire width_conv_tready;
wire [CHDR_W-1:0] out_pyld_tdata;
wire out_pyld_tlast;
@@ -368,12 +367,6 @@ module axis_data_to_chdr #(
endgenerate
-
-
-
-
-
-
// This state machine prevents data from transferring when the pkt_info_fifo
// is stalled. This ensures that we don't overflow the pkt_info_fifo.
always @(posedge axis_chdr_clk) begin