Files
b210-k7-fpga/lib/rfnoc/blocks/rfnoc_block_replay/axis_replay.v
T
5cadf901c7 fpga: Add X440/FBX support
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
2023-06-12 10:27:29 -05:00

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