diff options
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/blocks')
3 files changed, 266 insertions, 50 deletions
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 index e30ee81e2..194aed3e9 100644 --- a/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/axis_replay.v +++ b/fpga/usrp3/lib/rfnoc/blocks/rfnoc_block_replay/axis_replay.v @@ -32,7 +32,8 @@ // 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 +// The beginning of the playback is set by first writing to +// REG_PLAY_BASE_ADDR. The number of words to play back is set by first // writing to REG_PLAY_CMD_NUM_WORDS. // // The length of the packets generated during playback is configured by the @@ -40,9 +41,7 @@ // // 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. +// command write. The timestamp will be included in the first output 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 @@ -150,7 +149,7 @@ module axis_replay #( //--------------------------------------------------------------------------- localparam [REG_MAJOR_LEN-1:0] COMPAT_MAJOR = 1; - localparam [REG_MINOR_LEN-1:0] COMPAT_MINOR = 0; + localparam [REG_MINOR_LEN-1:0] COMPAT_MINOR = 1; localparam [REG_ITEM_SIZE_LEN-1:0] DEFAULT_ITEM_SIZE = 4; // 4 bytes for sc16 @@ -240,20 +239,26 @@ module axis_replay #( 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 [31:0] reg_rec_pos_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 [31:0] reg_play_pos_hi; 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; + wire reg_play_cmd_ready; 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 [5:0] reg_cmd_fifo_space; wire [63:0] reg_rec_fullness; + wire [63:0] reg_rec_pos; + wire [63:0] reg_play_pos; reg rec_restart_clear; reg play_cmd_stop_ack; @@ -275,8 +280,10 @@ module axis_replay #( reg_rec_base_addr <= 0; reg_rec_buffer_size <= 0; reg_rec_fullness_hi <= 'bX; + reg_rec_pos_hi <= 'bX; reg_play_base_addr <= 0; reg_play_buffer_size <= 0; + reg_play_pos_hi <= 'bX; reg_play_cmd_num_words <= 0; reg_play_cmd_time <= 0; reg_play_words_per_pkt <= REG_PLAY_WORDS_PER_PKT_INIT; @@ -379,13 +386,46 @@ module axis_replay #( REG_PLAY_ITEM_SIZE : s_ctrlport_resp_data[REG_ITEM_SIZE_POS+:REG_ITEM_SIZE_LEN] <= reg_item_size; + REG_REC_POS_LO : begin + s_ctrlport_resp_data <= reg_rec_pos[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_pos_hi <= 0; + reg_rec_pos_hi[0 +: max(MEM_SIZE_W-32, 1)] + <= reg_rec_pos[32 +: max(MEM_SIZE_W-32, 1)]; + end + end + REG_REC_POS_HI : + if (MEM_SIZE_W > 32) begin + // Return the saved value to guarantee coherence + s_ctrlport_resp_data <= reg_rec_pos_hi; + end + REG_PLAY_POS_LO : begin + s_ctrlport_resp_data <= reg_play_pos[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_play_pos_hi <= 0; + reg_play_pos_hi[0 +: max(MEM_SIZE_W-32, 1)] + <= reg_play_pos[32 +: max(MEM_SIZE_W-32, 1)]; + end + end + REG_PLAY_POS_HI : + if (MEM_SIZE_W > 32) begin + // Return the saved value to guarantee coherence + s_ctrlport_resp_data <= reg_play_pos_hi; + end + REG_PLAY_CMD_FIFO_SPACE : + s_ctrlport_resp_data[5:0] <= reg_cmd_fifo_space; endcase + end //----------------------------------------- // Register Writes //----------------------------------------- - end else if (s_ctrlport_req_wr) begin + if (s_ctrlport_req_wr) begin s_ctrlport_resp_ack <= 1; case (s_ctrlport_req_addr) REG_REC_BASE_ADDR_LO : @@ -473,23 +513,25 @@ module axis_replay #( wire cmd_timed_cf; wire [NUM_WORDS_W-1:0] cmd_num_words_cf; wire [TIME_W-1:0] cmd_time_cf; + wire [MEM_ADDR_W-1:0] cmd_base_addr_cf; + wire [MEM_SIZE_W-1:0] cmd_buffer_size_cf; wire cmd_fifo_valid; reg cmd_fifo_ready; axi_fifo_short #( - .WIDTH (1 + CMD_W + NUM_WORDS_W + TIME_W) + .WIDTH (MEM_ADDR_W + MEM_SIZE_W + 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_tdata ({play_base_addr_sr, play_buffer_size_sr, 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}), + .i_tready (reg_play_cmd_ready), + .o_tdata ({cmd_base_addr_cf, cmd_buffer_size_cf, cmd_timed_cf, cmd_cf, cmd_num_words_cf, cmd_time_cf}), .o_tvalid (cmd_fifo_valid), .o_tready (cmd_fifo_ready), .occupied (), - .space () + .space (reg_cmd_fifo_space) ); @@ -553,6 +595,7 @@ module axis_replay #( reg rec_wait_timeout; assign reg_rec_fullness = rec_buffer_used * BYTES_PER_WORD; + assign reg_rec_pos = rec_addr; always @(posedge clk) begin if (rst) begin @@ -564,7 +607,7 @@ module axis_replay #( rec_buffer_used <= 0; // Don't care: - rec_addr <= {MEM_ADDR_W{1'bX}}; + rec_addr <= {MEM_ADDR_W{1'b0}}; rec_size_0 <= {MEM_ADDR_W{1'bX}}; rec_size <= {MEM_ADDR_W{1'bX}}; write_count <= {MEM_COUNT_W{1'bX}}; @@ -706,16 +749,17 @@ module axis_replay #( // 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; + localparam PLAY_CHECK_SIZES = 1; + localparam PLAY_WAIT_DATA_READY = 2; + localparam PLAY_CHECK_ALIGN = 3; + localparam PLAY_SIZE_CALC = 4; + localparam PLAY_MEM_REQ = 5; + localparam PLAY_WAIT_MEM_START = 6; + localparam PLAY_WAIT_MEM_COMMIT = 7; + localparam PLAY_DONE_CHECK = 8; // State Signals - reg [2:0] play_state; + reg [3:0] play_state; // Registers reg [MEM_ADDR_W-1:0] play_addr; // Current byte offset into record buffer @@ -729,13 +773,14 @@ module axis_replay #( // 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 [MEM_ADDR_W-1:0] cmd_base_addr; // Copy of cmd_base_addr_cf from last command + reg [MEM_SIZE_W-1:0] cmd_buffer_size; // Copy of cmd_buffer_size_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 play_buffer_zero; // True if play buffer size is zero + reg num_words_zero; // True if number of words to play is zero 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 @@ -745,21 +790,23 @@ module axis_replay #( reg pause_data_transfer; + assign reg_play_pos = play_addr; + always @(posedge clk) begin if (rst) begin play_state <= PLAY_IDLE; cmd_fifo_ready <= 1'b0; + play_addr <= {MEM_ADDR_W{1'b0}}; + last_trans <= 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; + cmd_base_addr <= {MEM_ADDR_W{1'bX}}; + cmd_buffer_size <= {MEM_SIZE_W{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}}; @@ -773,14 +820,13 @@ module axis_replay #( 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; + play_buffer_zero <= 1'bX; + num_words_zero <= 1'bX; 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)); @@ -794,28 +840,43 @@ module axis_replay #( // case (play_state) PLAY_IDLE : begin - // Always start reading at the start of the record buffer - play_addr <= play_base_addr_sr; + // Save needed command info + cmd <= cmd_cf; + cmd_base_addr <= cmd_base_addr_cf; + cmd_buffer_size <= cmd_buffer_size_cf / BYTES_PER_WORD; - // Save off command info - if (cmd_cf == PLAY_CMD_CONTINUOUS) + // Initialize the play variables + if (cmd_cf == PLAY_CMD_CONTINUOUS) begin play_words_remaining <= MEM_BURST_LEN; - else + num_words_zero <= 0; + end else begin 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; + num_words_zero <= (cmd_num_words_cf == 0); + end + play_buffer_avail <= cmd_buffer_size_cf / BYTES_PER_WORD; + play_buffer_end <= {1'b0, cmd_base_addr_cf} + cmd_buffer_size_cf; + play_buffer_zero <= (cmd_buffer_size_cf == 0); - // Wait until we receive a command and we have enough data recorded - // to honor it. + // Wait until we receive a command if (play_cmd_stop) begin play_cmd_stop_ack <= 1'b1; - end else if (cmd_fifo_valid && play_buffer_avail_nonzero) begin + end else if (cmd_fifo_valid) begin + // Only update the play address when valid so readback is accurate + play_addr <= cmd_base_addr_cf; + // Dequeue the command from the FIFO cmd_fifo_ready <= 1'b1; + + play_state <= PLAY_CHECK_SIZES; + end + end + + PLAY_CHECK_SIZES : begin + // Check buffer and num_word sizes and allow propagation of + // play_full_burst_avail. + if (play_buffer_zero | num_words_zero) begin + play_state <= PLAY_IDLE; + end else begin play_state <= PLAY_WAIT_DATA_READY; end end @@ -902,18 +963,21 @@ module axis_replay #( // signals that the interface has received a response for the whole // read transaction. if (read_ctrl_ready) begin + // Check if this is the last transaction. + if (last_trans) begin + play_addr_1 <= play_addr_0[MEM_ADDR_W-1:0]; + play_buffer_avail <= 0; + // 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 if (play_addr_0 >= play_buffer_end) begin + play_addr_1 <= cmd_base_addr; + play_buffer_avail <= cmd_buffer_size; + 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 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 index 4849da47f..a04c9f231 100644 --- 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 @@ -196,6 +196,26 @@ localparam REG_PLAY_ITEM_SIZE = 'h50; localparam REG_ITEM_SIZE_POS = 0; localparam REG_ITEM_SIZE_LEN = 8; +// REG_REC_POS (R) +// +// Returns the byte address of the record pointer. +// +localparam REG_REC_POS_LO = 'h54; +localparam REG_REC_POS_HI = 'h58; + +// REG_PLAY_POS (R) +// +// Returns the byte address of the play pointer. +// +localparam REG_PLAY_POS_LO = 'h5C; +localparam REG_PLAY_POS_HI = 'h60; + +// REG_PLAY_CMD_FIFO_SPACE (R) +// +// Returns remaining space in the command FIFO +// +localparam REG_PLAY_CMD_FIFO_SPACE = 'h64; + //----------------------------------------------------------------------------- // Playback Commands //----------------------------------------------------------------------------- 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 index 5674a3cf6..d4cb071cf 100644 --- 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 @@ -476,6 +476,30 @@ module rfnoc_block_replay_tb#( endtask : wait_record_fullness + // Validate record position + task automatic validate_record_position( + input int port, + input longint position + ); + logic [63:0] value; + + read_reg_64(port, REG_REC_POS_LO, value); + `ASSERT_ERROR(value == position, $sformatf("Record position expected at 0x%h, but at 0x%h", position, value)); + endtask : validate_record_position + + + // Validate record position + task automatic validate_play_position( + input int port, + input longint position + ); + logic [63:0] value; + + read_reg_64(port, REG_PLAY_POS_LO, value); + `ASSERT_ERROR(value == position, $sformatf("Play position expected at 0x%h, but at 0x%h", position, value)); + endtask : validate_play_position + + // Make sure nothing is received until the timeout has elapsed task automatic check_rx_idle( input int port, @@ -863,6 +887,10 @@ module rfnoc_block_replay_tb#( (32'(MEM_ADDR_W) << REG_ADDR_SIZE_POS) ); test_read_reg(port, REG_REC_FULLNESS_LO, 64); + test_read_reg(port, REG_PLAY_ITEM_SIZE, 32, 4); + test_read_reg(port, REG_REC_POS_LO, 64); + test_read_reg(port, REG_PLAY_POS_LO, 64); + test_read_reg(port, REG_PLAY_CMD_FIFO_SPACE, 32, 32); // The following registers are write only and aren't tested here. // REG_REC_RESTART - Tested during every record operation @@ -1640,6 +1668,109 @@ module rfnoc_block_replay_tb#( //--------------------------------------------------------------------------- + // Test Record and Play position + //--------------------------------------------------------------------------- + // + // Test record and play positions advance properly. Checks position after + // recording and playing each packet and checks proper position during wraps. + // + //--------------------------------------------------------------------------- + + task test_position(int port = 0); + 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 record and play positions", 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 = 5; + + // Set up the record buffer + write_reg_64(port, REG_REC_BASE_ADDR_LO, 0); + write_reg_64(port, REG_REC_BUFFER_SIZE_LO, num_packets*num_items*ITEM_SIZE); + write_reg (port, REG_REC_RESTART, 0); + + // Fill the buffer and validate the record position after each packet. + for (int i = 0; i < num_packets; 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); + wait_record_fullness(port, (i+1)*num_items*ITEM_SIZE); + validate_record_position(port, ((i+1)*num_items*ITEM_SIZE)); + end + + // Send one additional packet to ensure the position does not advance + send_items = gen_test_data(num_items, num_packets*num_items); + blk_ctrl.send_items(port, send_items); + + // Give extra time for the last packet + #(CHDR_CLK_PER * num_items * 20); + + // Make sure the position has not advanced and the fullness has not changed + validate_record_position(port, num_packets*num_items*ITEM_SIZE); + read_reg_64(port, REG_REC_FULLNESS_LO, val64); + `ASSERT_ERROR(val64 == num_packets*num_items*ITEM_SIZE, "Memory fullness is not correct"); + + // Play back the data one packet at a time and validate play position + write_reg(port, REG_PLAY_WORDS_PER_PKT, num_items*ITEM_SIZE/MEM_WORD_SIZE); + write_reg_64(port, REG_PLAY_CMD_NUM_WORDS_LO, num_items*ITEM_SIZE/MEM_WORD_SIZE); + write_reg_64(port, REG_PLAY_BUFFER_SIZE_LO, num_items*ITEM_SIZE); + for (int i = 0; i < num_packets; i++) begin + write_reg_64(port, REG_PLAY_BASE_ADDR_LO, i*num_items*ITEM_SIZE); + write_reg(port, REG_PLAY_CMD, PLAY_CMD_FINITE); + 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" + ); + validate_play_position(port, ((i+1)*num_items*ITEM_SIZE)); + end + + // Restart recording to get the extra packet we sent at the beginning + // to validate the record position wraps. + write_reg(port, REG_REC_RESTART, 0); + wait_record_fullness(port, num_items*ITEM_SIZE); + validate_record_position(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_BASE_ADDR_LO, 0); + write_reg_64(port, REG_PLAY_BUFFER_SIZE_LO, num_items*ITEM_SIZE); + 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" + ); + validate_play_position(port, num_items*ITEM_SIZE); + + // Test the play wrap. Play num_packets plus one and validate position. + // This is done at the end of the memory space to test the wrap within the + // buffer space as well as the wrap at the end of the memory space. + write_reg_64(port, REG_PLAY_BASE_ADDR_LO, mem_size-(num_packets*num_items*ITEM_SIZE)); + write_reg_64(port, REG_PLAY_BUFFER_SIZE_LO, num_packets*num_items*ITEM_SIZE); + write_reg_64(port, REG_PLAY_CMD_NUM_WORDS_LO, (num_packets+1)*num_items*ITEM_SIZE/MEM_WORD_SIZE); + write_reg(port, REG_PLAY_CMD, PLAY_CMD_FINITE); + for (int i = 0; i < num_packets+1; i++) begin + blk_ctrl.recv_items(port, recv_items); + end + validate_play_position(port, mem_size-((num_packets-1)*num_items*ITEM_SIZE)); + + // Make sure there are no more packets + check_rx_idle(port); + + test.end_test(); + endtask : test_position + + + //--------------------------------------------------------------------------- // Test Filling the memory //--------------------------------------------------------------------------- // @@ -1795,6 +1926,7 @@ module rfnoc_block_replay_tb#( test_4k_boundary(); test_small_packet(); test_timed_playback(); + test_position(); test_full_memory(); //-------------------------------- |