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 | |
| parent | 24f8bb39fd2769ff93d11b21152a834500152de4 (diff) | |
| download | uhd-6d92a1828121ca4b57d496bbf522820f961244b9.tar.gz uhd-6d92a1828121ca4b57d496bbf522820f961244b9.tar.bz2 uhd-6d92a1828121ca4b57d496bbf522820f961244b9.zip | |
fpga: rfnoc: Add RFNoC Replay block
12 files changed, 4164 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 diff --git a/host/include/uhd/rfnoc/blocks/replay.yml b/host/include/uhd/rfnoc/blocks/replay.yml new file mode 100644 index 000000000..b871932bc --- /dev/null +++ b/host/include/uhd/rfnoc/blocks/replay.yml @@ -0,0 +1,63 @@ +schema: rfnoc_modtool_args +module_name: replay +version: 1.0 +rfnoc_version: 1.0 +chdr_width: 64 +noc_id: 0x4E91A000 +makefile_srcs: "${fpga_lib_dir}/blocks/rfnoc_block_replay/Makefile.srcs" + +parameters: +  NUM_PORTS: 2 +  MEM_DATA_W: 64 +  MEM_ADDR_W: 30 + +clocks: +  - name: rfnoc_chdr +    freq: "[]" +  - name: rfnoc_ctrl +    freq: "[]" +  - name: mem +    freq: "[]" + +control: +  sw_iface: nocscript +  fpga_iface: ctrlport +  interface_direction: slave +  fifo_depth: 32 +  clk_domain: mem +  ctrlport: +    byte_mode: False +    timed: False +    has_status: False + +data: +  fpga_iface: axis_data +  clk_domain: mem +  inputs: +    in: +      num_ports: NUM_PORTS +      item_width: 32 +      nipc: MEM_DATA_W/32 +      info_fifo_depth: 32 +      payload_fifo_depth: MTU +      format: int32 +      mdata_sig: ~ +  outputs: +    out: +      num_ports: NUM_PORTS +      item_width: 32 +      nipc: MEM_DATA_W/32 +      info_fifo_depth: 32 +      payload_fifo_depth: MTU +      sideband_at_end: 1 +      format: int32 +      mdata_sig: ~ + +io_ports: +  axi_ram: +    type: axi4_mm_2x64_4g +    drive: master + +registers: + +properties: | 
