diff options
author | Wade Fife <wade.fife@ettus.com> | 2020-04-06 16:19:50 -0500 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2020-08-04 15:40:08 -0500 |
commit | 6d92a1828121ca4b57d496bbf522820f961244b9 (patch) | |
tree | f0659ab6d1a6e1f8801db356e2237388d90724f2 /fpga/usrp3 | |
parent | 24f8bb39fd2769ff93d11b21152a834500152de4 (diff) | |
download | uhd-6d92a1828121ca4b57d496bbf522820f961244b9.tar.gz uhd-6d92a1828121ca4b57d496bbf522820f961244b9.tar.bz2 uhd-6d92a1828121ca4b57d496bbf522820f961244b9.zip |
fpga: rfnoc: Add RFNoC Replay block
Diffstat (limited to 'fpga/usrp3')
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 |