Gaps in transmission were seen when using the replay buffered TX streamer caused by the Replay block setting the end of burst flag at the end of every send() call. This fix adds a flag to the Replay block play command register to avoid setting the end of burst flag and modifies the replay buffered TX streamer to use the flag based on the metadata passed in to the send() call. Signed-off-by: michael-west <michael.west@ettus.com> Original-commit: 2419f405775934efa517e025929356ef0a0d181e
1229 lines
47 KiB
Verilog
1229 lines
47 KiB
Verilog
//
|
|
// 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 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
|
|
// 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 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
|
|
// 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 = 2;
|
|
|
|
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 = MEM_COUNT_W+1; // Log2 of input/record FIFO size
|
|
localparam PLAY_FIFO_ADDR_WIDTH = MEM_COUNT_W+1; // 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 [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_no_eob;
|
|
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;
|
|
|
|
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_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;
|
|
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_no_eob <= '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;
|
|
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
|
|
//-----------------------------------------
|
|
|
|
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_no_eob <= s_ctrlport_req_data[REG_PLAY_NO_EOB_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 cmd_no_eob_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 (MEM_ADDR_W + MEM_SIZE_W + 2 + CMD_W + NUM_WORDS_W + TIME_W)
|
|
) command_fifo (
|
|
.clk (clk),
|
|
.reset (rst),
|
|
.clear (clear_cmd_fifo),
|
|
.i_tdata ({play_base_addr_sr, play_buffer_size_sr, reg_play_cmd_timed, reg_play_cmd_no_eob, reg_play_cmd, reg_play_cmd_num_words, reg_play_cmd_time}),
|
|
.i_tvalid (reg_play_cmd_valid),
|
|
.i_tready (reg_play_cmd_ready),
|
|
.o_tdata ({cmd_base_addr_cf, cmd_buffer_size_cf, cmd_timed_cf, cmd_no_eob_cf, cmd_cf, cmd_num_words_cf, cmd_time_cf}),
|
|
.o_tvalid (cmd_fifo_valid),
|
|
.o_tready (cmd_fifo_ready),
|
|
.occupied (),
|
|
.space (reg_cmd_fifo_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;
|
|
assign reg_rec_pos = rec_addr;
|
|
|
|
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'b0}};
|
|
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 && i_tready) || !rec_fifo_occupied) begin
|
|
// If a new word is presented to the input FIFO, or the FIFO is empty,
|
|
// then reset the timer.
|
|
rec_wait_timer <= 0;
|
|
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_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 [3: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_eob; // Inverse copy of cmd_no_eob_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 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
|
|
|
|
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;
|
|
|
|
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_end <= {MEM_SIZE_W{1'bX}};
|
|
read_ctrl_valid <= 1'bX;
|
|
cmd <= {CMD_W{1'bX}};
|
|
cmd_eob <= 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}};
|
|
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}};
|
|
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_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
|
|
// Save needed command info
|
|
cmd <= cmd_cf;
|
|
cmd_eob <= ~cmd_no_eob_cf;
|
|
cmd_base_addr <= cmd_base_addr_cf;
|
|
cmd_buffer_size <= cmd_buffer_size_cf / BYTES_PER_WORD;
|
|
|
|
// Initialize the play variables
|
|
if (cmd_cf == PLAY_CMD_CONTINUOUS) begin
|
|
play_words_remaining <= MEM_BURST_LEN;
|
|
num_words_zero <= 0;
|
|
end else begin
|
|
play_words_remaining <= cmd_num_words_cf;
|
|
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
|
|
if (play_cmd_stop) begin
|
|
play_cmd_stop_ack <= 1'b1;
|
|
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
|
|
|
|
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 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.
|
|
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
|
|
|
|
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 reg_play_words_per_pkt words and at the end of the last packet, so
|
|
// that no packets are longer than the length indicated by the
|
|
// REG_PLAY_WORDS_PER_PKT register.
|
|
//
|
|
// The sideband signals consist of the timestamp, has-time flag, and EOB (end
|
|
// of burst) flag. Timestamp and has_time are set for the first packet of
|
|
// each playback. EOB applies to each packet but is only set to 1 for the
|
|
// last packet of a playback.
|
|
//
|
|
// The timing of this section relies on the fact axi_dma_master doesn't allow
|
|
// overlapping read transactions. This means that the next read_ctrl_ready
|
|
// won't be asserted until after previous memory transaction finishes being
|
|
// read out.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
reg [MEM_COUNT_W-1:0] read_counter; // Track outstanding words to read
|
|
reg [ WPP_W-1:0] length_counter; // Track packet length
|
|
reg [ TIME_W-1:0] timestamp; // Timestamp for the current burst
|
|
reg has_time; // Is current burst timed?
|
|
reg eob; // End of burst
|
|
reg play_fifo_i_tlast; // End of packet
|
|
|
|
always @(posedge clk)
|
|
begin
|
|
// synthesis translate_off
|
|
//
|
|
// Check our assumption about non-overlapping read transactions.
|
|
if (read_ctrl_ready && play_fifo_i_tvalid) begin
|
|
$fatal(1, "New read transaction started before the previous one completed!");
|
|
end
|
|
// synthesis translate_on
|
|
|
|
if (read_ctrl_valid && read_ctrl_ready) begin
|
|
read_counter <= read_count;
|
|
|
|
// If read_count is 0, then the next word is also the last word
|
|
if (read_count == 0) begin
|
|
play_fifo_i_tlast <= 1'b1;
|
|
eob <= last_trans & cmd_eob;
|
|
end
|
|
end
|
|
|
|
if (play_fifo_i_tvalid && play_fifo_i_tready) begin
|
|
read_counter <= read_counter - 1;
|
|
length_counter <= length_counter - 1;
|
|
|
|
// Check if the current word is the last of the packet
|
|
if (play_fifo_i_tlast) begin
|
|
length_counter <= reg_play_words_per_pkt;
|
|
|
|
// Clear tlast, unless the first word of the next packet is also the
|
|
// last word of the next packet.
|
|
if (!(last_trans && read_counter == 1)) begin
|
|
play_fifo_i_tlast <= 1'b0;
|
|
end
|
|
|
|
// The timestamp only applies to the first packet, so disable for
|
|
// subsequent packets.
|
|
has_time <= 1'b0;
|
|
end
|
|
|
|
// Check if the next word will be the last of the packet.
|
|
//
|
|
// First, check if the next word is the last word of playback, in which
|
|
// case it's both the last word of the packet and the end of the burst.
|
|
if (last_trans && read_counter == 1) begin
|
|
play_fifo_i_tlast <= 1'b1;
|
|
eob <= cmd_eob;
|
|
|
|
// Next, check if this is the last word of the packet according to packet
|
|
// length. But note that the next word won't be the last if we're already
|
|
// outputting the last word of a burst on the current cycle.
|
|
end else if (length_counter == 2 && !(eob && play_fifo_i_tlast)) begin
|
|
play_fifo_i_tlast <= 1'b1;
|
|
end
|
|
end
|
|
|
|
if (play_state == PLAY_IDLE) begin
|
|
// Reset signals for the next playback
|
|
length_counter <= reg_play_words_per_pkt;
|
|
timestamp <= cmd_time_cf;
|
|
has_time <= cmd_timed_cf;
|
|
eob <= 0;
|
|
play_fifo_i_tlast <= 1'b0;
|
|
end
|
|
end
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// 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 ()
|
|
);
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// 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_tvalid;
|
|
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 (hdr_fifo_o_tvalid),
|
|
.o_tready (hdr_fifo_o_tready),
|
|
//
|
|
.space (hdr_fifo_space),
|
|
.occupied ()
|
|
);
|
|
|
|
// synthesis translate_off
|
|
//
|
|
// The FIFO code above assumes the header info will always be available when
|
|
// the last word of the payload FIFO is read out. Check that assumption here.
|
|
always @(posedge clk) begin
|
|
if (hdr_fifo_o_tready && !hdr_fifo_o_tvalid) begin
|
|
$fatal(1, "Header FIFO read without valid data!");
|
|
end
|
|
end
|
|
// synthesis translate_on
|
|
|
|
assign hdr_fifo_i_tdata = {has_time, eob, timestamp };
|
|
|
|
// Pop the timestamp whenever we finish reading out a data packet
|
|
assign hdr_fifo_o_tready = o_tvalid & o_tready & o_tlast;
|
|
|
|
// Write the timestamp at the start of each packet
|
|
assign hdr_fifo_i_tvalid = play_fifo_i_tvalid & play_fifo_i_tready & play_fifo_i_tlast;
|
|
|
|
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
|