Co-authored-by: Martin Braun <martin.braun@ettus.com> Co-authored-by: Wade Fife <wade.fife@ni.com> Co-authored-by: Ryan Marlow <ryan@lmarlow.com> Original-commit: 596760a12e4834e47589c12f8a4fd083aa2f7c25
1233 lines
47 KiB
Verilog
1233 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);
|
|
//
|
|
// Burst length in bytes
|
|
localparam BURST_LENGTH = 2**MEM_COUNT_W * BYTES_PER_WORD;
|
|
//
|
|
// AXI alignment requirement (normally 4096 bytes) in MEM_DATA_W-bit words
|
|
localparam AXI_ALIGNMENT = (BURST_LENGTH <= 4096) ? 4096 / BYTES_PER_WORD :
|
|
BURST_LENGTH / 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
|