aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/lib/axi/axi_replay.v
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/lib/axi/axi_replay.v')
-rw-r--r--fpga/usrp3/lib/axi/axi_replay.v867
1 files changed, 867 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/axi/axi_replay.v b/fpga/usrp3/lib/axi/axi_replay.v
new file mode 100644
index 000000000..49e4318c5
--- /dev/null
+++ b/fpga/usrp3/lib/axi/axi_replay.v
@@ -0,0 +1,867 @@
+//
+// Copyright 2017 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0
+//
+// Module: axi_replay.v
+// Description:
+//
+// This block implements the state machine and control logic for recording and
+// playback of AXI-Stream data, using a DMA-accessible memory as a buffer.
+
+
+module axi_replay #(
+ parameter DATA_WIDTH = 64,
+ parameter ADDR_WIDTH = 32, // Byte address width used by DMA master
+ parameter COUNT_WIDTH = 8 // Length of counters used to connect to the DMA
+ // master's read and write interfaces.
+) (
+ input wire clk,
+ input wire rst, // Synchronous to clk
+
+ //---------------------------------------------------------------------------
+ // Settings Bus
+ //---------------------------------------------------------------------------
+
+ input wire set_stb,
+ input wire [ 7:0] set_addr,
+ input wire [31:0] set_data,
+ output reg [31:0] rb_data,
+ input wire [ 7:0] rb_addr,
+
+ //---------------------------------------------------------------------------
+ // AXI Stream Interface
+ //---------------------------------------------------------------------------
+
+ // Input
+ input wire [DATA_WIDTH-1:0] i_tdata,
+ input wire i_tvalid,
+ input wire i_tlast,
+ output wire i_tready,
+
+ // Output
+ output wire [DATA_WIDTH-1:0] o_tdata,
+ output wire o_tvalid,
+ output wire o_tlast,
+ input wire o_tready,
+
+ //---------------------------------------------------------------------------
+ // DMA Interface
+ //---------------------------------------------------------------------------
+
+ // Write interface
+ output reg [ ADDR_WIDTH-1:0] write_addr, // Byte address for start of write
+ // transaction (64-bit aligned).
+ output reg [COUNT_WIDTH-1:0] write_count, // Count of 64-bit words to write, minus 1.
+ output reg write_ctrl_valid,
+ input wire write_ctrl_ready,
+ output wire [ DATA_WIDTH-1:0] write_data,
+ output wire write_data_valid,
+ input wire write_data_ready,
+
+ // Read interface
+ output reg [ ADDR_WIDTH-1:0] read_addr, // Byte address for start of read
+ // transaction (64-bit aligned).
+ output reg [COUNT_WIDTH-1:0] read_count, // Count of 64-bit words to read, minus 1.
+ output reg read_ctrl_valid,
+ input wire read_ctrl_ready,
+ input wire [ DATA_WIDTH-1:0] read_data,
+ input wire read_data_valid,
+ output wire read_data_ready
+);
+
+ //---------------------------------------------------------------------------
+ // Constants
+ //---------------------------------------------------------------------------
+
+ // Size constants
+ localparam CMD_WIDTH = 32; // Command width
+ localparam LINES_WIDTH = 28; // Width of cmd_num_lines
+ localparam WORD_SIZE = DATA_WIDTH/8; // Size of DATA_WIDTH in bytes
+
+ // Register offsets
+ localparam [7:0] SR_REC_BASE_ADDR = 128;
+ localparam [7:0] SR_REC_BUFFER_SIZE = 129;
+ localparam [7:0] SR_REC_RESTART = 130;
+ localparam [7:0] SR_REC_FULLNESS = 131;
+ localparam [7:0] SR_PLAY_BASE_ADDR = 132;
+ localparam [7:0] SR_PLAY_BUFFER_SIZE = 133;
+ localparam [7:0] SR_RX_CTRL_COMMAND = 152; // Same offset as radio
+ localparam [7:0] SR_RX_CTRL_HALT = 155; // Same offset as radio
+ localparam [7:0] SR_RX_CTRL_MAXLEN = 156; // Same offset as radio
+
+
+ // Memory buffering parameters:
+ //
+ // Log base 2 of the depth of the input and output FIFOs to use. The FIFOs
+ // should be large enough to store more than a complete burst
+ // (MEM_BURST_SIZE). A size of 9 (512 64-bit words) is one 36-kbit BRAM.
+ localparam REC_FIFO_ADDR_WIDTH = 9; // Log2 of input/record FIFO size
+ localparam PLAY_FIFO_ADDR_WIDTH = 9; // Log2 of output/playback FIFO size
+ //
+ // Amount of data to buffer before writing to RAM. This should be a power of
+ // two so that it evenly divides the AXI_ALIGNMENT requirement. It also must
+ // not exceed 2**COUNT_WIDTH (the maximum count allowed by DMA master).
+ localparam MEM_BURST_SIZE = 2**COUNT_WIDTH; // Size in DATA_WIDTH-sized words
+ //
+ // AXI alignment requirement (4096 bytes) in DATA_WIDTH-bit words
+ localparam AXI_ALIGNMENT = 4096 / WORD_SIZE;
+ //
+ // Clock cycles to wait before writing something less than MEM_BURST_SIZE
+ // to memory.
+ localparam DATA_WAIT_TIMEOUT = 31;
+
+
+ //---------------------------------------------------------------------------
+ // Signals
+ //---------------------------------------------------------------------------
+
+ // Command wires
+ wire cmd_send_imm_cf, cmd_chain_cf, cmd_reload_cf, cmd_stop_cf;
+ wire [LINES_WIDTH-1:0] cmd_num_lines_cf;
+
+ // Settings registers signals
+ wire [ ADDR_WIDTH-1:0] rec_base_addr_sr; // Byte address
+ wire [ ADDR_WIDTH-1:0] rec_buffer_size_sr; // Size in bytes
+ wire [ ADDR_WIDTH-1:0] play_base_addr_sr; // Byte address
+ wire [ ADDR_WIDTH-1:0] play_buffer_size_sr; // Size in bytes
+ reg rec_restart;
+ reg rec_restart_clear;
+ wire [ CMD_WIDTH-1:0] command;
+ wire command_valid;
+ reg play_halt;
+ reg play_halt_clear;
+ wire [COUNT_WIDTH:0] play_max_len_sr;
+
+ // Command FIFO
+ wire cmd_fifo_valid;
+ reg cmd_fifo_ready;
+
+ // Record Data FIFO (Input)
+ wire [DATA_WIDTH-1:0] rec_fifo_o_tdata;
+ wire rec_fifo_o_tvalid;
+ wire rec_fifo_o_tready;
+ wire [ 15:0] rec_fifo_occupied;
+
+ // Playback Data FIFO (Output)
+ wire [DATA_WIDTH-1:0] play_fifo_i_tdata;
+ wire play_fifo_i_tvalid;
+ wire play_fifo_i_tready;
+ wire [ 15:0] play_fifo_space; // Free space in play_axi_fifo
+
+ // Buffer usage registers
+ reg [ADDR_WIDTH-1:0] rec_buffer_avail; // Amount of free buffer space in words
+ reg [ADDR_WIDTH-1:0] rec_buffer_used; // Amount of occupied buffer space in words
+
+
+ //---------------------------------------------------------------------------
+ // Registers
+ //---------------------------------------------------------------------------
+
+ // Record Base Address Register. Address is a byte address. This must be a
+ // multiple of 8 bytes.
+ setting_reg #(
+ .my_addr (SR_REC_BASE_ADDR),
+ .width (ADDR_WIDTH)
+ ) sr_rec_base_addr (
+ .clk (clk),
+ .rst (rst),
+ .strobe (set_stb),
+ .addr (set_addr),
+ .in (set_data),
+ .out (rec_base_addr_sr),
+ .changed ()
+ );
+
+
+ // Record Buffer Size Register. This indicates the portion of the RAM
+ // allocated to the record buffer, in bytes. This should be a multiple of 8
+ // bytes.
+ setting_reg #(
+ .my_addr (SR_REC_BUFFER_SIZE),
+ .width (ADDR_WIDTH)
+ ) sr_rec_buffer_size (
+ .clk (clk),
+ .rst (rst),
+ .strobe (set_stb),
+ .addr (set_addr),
+ .in (set_data),
+ .out (rec_buffer_size_sr),
+ .changed ()
+ );
+
+
+ // Playback Base Address Register. Address is a byte address. This must be a
+ // multiple of the 8 bytes.
+ setting_reg #(
+ .my_addr (SR_PLAY_BASE_ADDR),
+ .width (ADDR_WIDTH)
+ ) sr_play_base_addr (
+ .clk (clk),
+ .rst (rst),
+ .strobe (set_stb),
+ .addr (set_addr),
+ .in (set_data),
+ .out (play_base_addr_sr),
+ .changed ()
+ );
+
+
+ // Playback Buffer Size Register. This indicates the portion of the RAM
+ // allocated to the record buffer, in bytes. This should be a multiple of 8
+ // bytes.
+ setting_reg #(
+ .my_addr (SR_PLAY_BUFFER_SIZE),
+ .width (ADDR_WIDTH)
+ ) sr_play_buffer_size (
+ .clk (clk),
+ .rst (rst),
+ .strobe (set_stb),
+ .addr (set_addr),
+ .in (set_data),
+ .out (play_buffer_size_sr),
+ .changed ()
+ );
+
+
+ // Record Buffer Restart Register. Software must write to this register after
+ // updating the base address or buffer size. A write to this register means
+ // we need to stop any recording in progress and reset the record buffers
+ // according to the current buffer base address and size registers.
+ always @(posedge clk)
+ begin : sr_restart
+ if(rst) begin
+ rec_restart <= 1'b0;
+ end else begin
+ if(set_stb & (set_addr == SR_REC_RESTART)) begin
+ rec_restart <= 1'b1;
+ end else if (rec_restart_clear) begin
+ rec_restart <= 1'b0;
+ end
+ end
+ end
+
+
+ // Halt Register. A write to this register stops any replay operation as soon
+ // as the current DRAM transaction completes.
+ always @(posedge clk)
+ begin : sr_halt
+ if(rst) begin
+ play_halt <= 1'b0;
+ end else begin
+ if(set_stb & (set_addr == SR_RX_CTRL_HALT)) begin
+ play_halt <= 1'b1;
+ end else if (play_halt_clear) begin
+ play_halt <= 1'b0;
+ end
+ end
+ end
+
+
+ // Play Command Register
+ //
+ // This register mirrors the behavior of the RFNoC RX radio block. All
+ // commands are queued up in the replay command FIFO. The fields are as
+ // follows.
+ //
+ // send_imm [31] Send command immediately (don't use time).
+ //
+ // chain [30] When done with num_lines, immediately run next command.
+ //
+ // reload [29] When done with num_lines, rerun the same command if
+ // cmd_chain is set and no new command is available.
+ //
+ // stop [28] When done with num_lines, stop transferring if
+ // cmd_chain is set.
+ //
+ // num_lines [27:0] Number of samples to transfer to/from block.
+ //
+ setting_reg #(
+ .my_addr (SR_RX_CTRL_COMMAND),
+ .width (CMD_WIDTH)
+ ) sr_command (
+ .clk (clk),
+ .rst (rst),
+ .strobe (set_stb),
+ .addr (set_addr),
+ .in (set_data),
+ .out (command),
+ .changed (command_valid)
+ );
+
+
+ // Max Length Register. This register sets the number of words for the
+ // maximum packet size.
+ setting_reg #(
+ .my_addr (SR_RX_CTRL_MAXLEN),
+ .width (COUNT_WIDTH+1),
+ .at_reset({1'b1, {COUNT_WIDTH{1'b0}}})
+ ) sr_max_len (
+ .clk (clk),
+ .rst (rst),
+ .strobe (set_stb),
+ .addr (set_addr),
+ .in (set_data),
+ .out (play_max_len_sr),
+ .changed ()
+ );
+
+
+ // Implement register read
+ always @(*) begin
+ case (rb_addr)
+ SR_REC_BASE_ADDR : rb_data = rec_base_addr_sr;
+ SR_REC_BUFFER_SIZE : rb_data = rec_buffer_size_sr;
+ SR_REC_FULLNESS : rb_data = rec_buffer_used * WORD_SIZE;
+ SR_PLAY_BASE_ADDR : rb_data = play_base_addr_sr;
+ SR_PLAY_BUFFER_SIZE : rb_data = play_buffer_size_sr;
+ SR_RX_CTRL_MAXLEN : rb_data = play_max_len_sr;
+ default : rb_data = 32'h0;
+ endcase
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Playback Command FIFO
+ //---------------------------------------------------------------------------
+ //
+ // This block queues up commands for playback control.
+ //
+ //---------------------------------------------------------------------------
+
+ axi_fifo_short #(
+ .WIDTH (CMD_WIDTH)
+ ) command_fifo (
+ .clk (clk),
+ .reset (rst),
+ .clear (play_halt_clear),
+ .i_tdata (command),
+ .i_tvalid (command_valid),
+ .i_tready (),
+ .o_tdata ({cmd_send_imm_cf, cmd_chain_cf, cmd_reload_cf, cmd_stop_cf, cmd_num_lines_cf}),
+ .o_tvalid (cmd_fifo_valid),
+ .o_tready (cmd_fifo_ready),
+ .occupied (),
+ .space ()
+ );
+
+
+ //---------------------------------------------------------------------------
+ // Record Input Data FIFO
+ //---------------------------------------------------------------------------
+ //
+ // This FIFO stores data to be recording into the RAM buffer.
+ //
+ //---------------------------------------------------------------------------
+
+ axi_fifo #(
+ .WIDTH (DATA_WIDTH),
+ .SIZE (REC_FIFO_ADDR_WIDTH)
+ ) rec_axi_fifo (
+ .clk (clk),
+ .reset (rst),
+ .clear (1'b0),
+ //
+ .i_tdata (i_tdata),
+ .i_tvalid (i_tvalid),
+ .i_tready (i_tready),
+ //
+ .o_tdata (rec_fifo_o_tdata),
+ .o_tvalid (rec_fifo_o_tvalid),
+ .o_tready (rec_fifo_o_tready),
+ //
+ .space (),
+ .occupied (rec_fifo_occupied)
+ );
+
+
+ //---------------------------------------------------------------------------
+ // Record State Machine
+ //---------------------------------------------------------------------------
+
+ // FSM States
+ localparam REC_WAIT_FIFO = 0;
+ localparam REC_CHECK_ALIGN = 1;
+ localparam REC_DMA_REQ = 2;
+ localparam REC_WAIT_DMA_START = 3;
+ localparam REC_WAIT_DMA_COMMIT = 4;
+
+ // State Signals
+ reg [2:0] rec_state;
+
+ // Registers
+ reg [ADDR_WIDTH-1:0] rec_base_addr; // Last base address pulled from settings register
+ reg [ADDR_WIDTH-1:0] rec_buffer_size; // Last buffer size pulled from settings register
+ reg [ADDR_WIDTH-1:0] rec_addr; // Current offset into record buffer
+ reg [ADDR_WIDTH-1:0] rec_size; // Number of words to transfer next
+ reg [ADDR_WIDTH-1:0] rec_size_0; // Pipeline stage for computation of rec_size
+
+ reg signed [ADDR_WIDTH:0] rec_size_aligned; // rec_size reduced to not cross 4k boundary
+
+ // Timer to count how many cycles we've been waiting for new data
+ reg [$clog2(DATA_WAIT_TIMEOUT+1)-1:0] rec_wait_timer;
+ reg rec_wait_timeout;
+
+ always @(posedge clk) begin
+ if (rst) begin
+ rec_state <= REC_WAIT_FIFO;
+ rec_addr <= 0;
+ write_ctrl_valid <= 1'b0;
+
+ rec_buffer_avail <= 0;
+ rec_buffer_used <= 0;
+ rec_wait_timer <= 0;
+ rec_wait_timeout <= 0;
+
+ end else begin
+
+ // Default assignments
+ rec_restart_clear <= 1'b0;
+
+ // Update wait timer
+ if (i_tvalid || !rec_fifo_occupied) begin
+ // If a new word is presented to the input FIFO, or the FIFO is empty,
+ // then reset the timer.
+ rec_wait_timer <= 0;
+ rec_wait_timeout <= 1'b0;
+ end else if (rec_fifo_occupied) begin
+ // If no new word is written, but there's data in the FIFO, update the
+ // timer. Latch timeout condition when we reach out limit.
+ rec_wait_timer <= rec_wait_timer + 1;
+
+ if (rec_wait_timer == DATA_WAIT_TIMEOUT) begin
+ rec_wait_timeout <= 1'b1;
+ end
+ end
+
+ // Pre-calculate the aligned size
+ rec_size_aligned <= $signed(AXI_ALIGNMENT) - $signed(rec_addr & (AXI_ALIGNMENT-1));
+
+ //
+ // State logic
+ //
+ case (rec_state)
+
+ REC_WAIT_FIFO : begin
+ // Wait until there's enough data to initiate a transfer from the
+ // FIFO to the RAM.
+
+ // Check if a restart was requested on the record interface
+ if (rec_restart) begin
+ rec_restart_clear <= 1'b1;
+
+ // Latch the new register values. We don't want them to change
+ // while we're running.
+ rec_base_addr <= rec_base_addr_sr;
+ rec_buffer_size <= rec_buffer_size_sr / WORD_SIZE; // Store size in words
+
+ // Reset counters and address any time we update the buffer size or
+ // base address.
+ rec_buffer_avail <= rec_buffer_size_sr / WORD_SIZE; // Store size in words
+ rec_buffer_used <= 0;
+ rec_addr <= rec_base_addr_sr;
+
+ // Check if there's room left in the record RAM buffer
+ end else if (rec_buffer_used < rec_buffer_size) begin
+ // See if we can transfer a full burst
+ if (rec_fifo_occupied >= MEM_BURST_SIZE && rec_buffer_avail >= MEM_BURST_SIZE) begin
+ rec_size_0 <= MEM_BURST_SIZE;
+ rec_state <= REC_CHECK_ALIGN;
+
+ // Otherwise, if we've been waiting a long time, see if we can
+ // transfer less than a burst.
+ end else if (rec_fifo_occupied > 0 && rec_wait_timeout) begin
+ rec_size_0 <= (rec_fifo_occupied <= rec_buffer_avail) ?
+ rec_fifo_occupied : rec_buffer_avail;
+ rec_state <= REC_CHECK_ALIGN;
+ end
+ end
+ end
+
+ REC_CHECK_ALIGN : begin
+ // Check the address alignment, since AXI requires that an access not
+ // cross 4k boundaries (boo), and the axi_dma_master doesn't handle
+ // this automatically (boo again).
+ rec_size <= ($signed({1'b0,rec_size_0}) > rec_size_aligned) ?
+ rec_size_aligned : rec_size_0;
+
+ // DMA interface is ready, so transaction will begin
+ rec_state <= REC_DMA_REQ;
+ end
+
+ REC_DMA_REQ : begin
+ // The write count written to the DMA engine should be 1 less than
+ // the number of words you want to write (not the number of bytes).
+ write_count <= rec_size - 1;
+
+ // Create the physical RAM byte address by combining the address and
+ // base address.
+ write_addr <= rec_addr;
+
+ // Once the interface is ready, make the DMA request
+ if (write_ctrl_ready) begin
+ // Request the write transaction
+ write_ctrl_valid <= 1'b1;
+ rec_state <= REC_WAIT_DMA_START;
+ end
+ end
+
+ REC_WAIT_DMA_START : begin
+ // Wait until DMA interface deasserts ready, indicating it has
+ // started on the request.
+ write_ctrl_valid <= 1'b0;
+ if (!write_ctrl_ready) begin
+ rec_state <= REC_WAIT_DMA_COMMIT;
+ end
+ end
+
+ REC_WAIT_DMA_COMMIT : begin
+ // Wait for the DMA interface to reassert write_ctrl_ready, which
+ // signals that the DMA engine has received a response for the whole
+ // write transaction and (we assume) it has been committed to RAM.
+ // After this, we can update the write address and start the next
+ // transaction.
+ if (write_ctrl_ready) begin
+ rec_addr <= rec_addr + (rec_size * WORD_SIZE);
+ rec_buffer_used <= rec_buffer_used + rec_size;
+ rec_buffer_avail <= rec_buffer_avail - rec_size;
+ rec_state <= REC_WAIT_FIFO;
+ end
+ end
+
+ default : begin
+ rec_state <= REC_WAIT_FIFO;
+ end
+
+ endcase
+ end
+ end
+
+ // Connect output of record FIFO to input of DMA write interface
+ assign write_data = rec_fifo_o_tdata;
+ assign write_data_valid = rec_fifo_o_tvalid;
+ assign rec_fifo_o_tready = write_data_ready;
+
+
+ //---------------------------------------------------------------------------
+ // Playback State Machine
+ //---------------------------------------------------------------------------
+
+ // FSM States
+ localparam PLAY_IDLE = 0;
+ localparam PLAY_WAIT_DATA_READY = 1;
+ localparam PLAY_SIZE_CALC = 2;
+ localparam PLAY_DMA_REQ = 3;
+ localparam PLAY_WAIT_DMA_START = 4;
+ localparam PLAY_WAIT_DMA_COMMIT = 5;
+ localparam PLAY_DONE_CHECK = 6;
+
+ // State Signals
+ reg [2:0] play_state;
+
+ // Registers
+ reg [ADDR_WIDTH-1:0] play_base_addr; // Last base address pulled from settings register
+ reg [ADDR_WIDTH-1:0] play_buffer_size; // Last buffer size pulled from settings register
+ reg [ADDR_WIDTH-1:0] play_addr; // Current byte offset into record buffer
+ reg [ADDR_WIDTH-1:0] play_addr_0; // Pipeline stage for computing play_addr
+ reg [ADDR_WIDTH-1:0] play_addr_1; // Pipeline stage for computing play_addr
+ reg [ADDR_WIDTH-1:0] play_buffer_end; // Address of location after end of buffer
+ reg [ADDR_WIDTH-1:0] max_dma_size; // Maximum size of next transfer, in words
+ //
+ reg [LINES_WIDTH-1:0] cmd_num_lines; // Copy of cmd_num_lines from last command
+ reg [LINES_WIDTH-1:0] play_words_remaining; // Number of lines left to read for command
+ reg cmd_chain; // Copy of cmd_chain from last command
+ reg cmd_reload; // Copy of cmd_reload from last command
+
+ reg play_full_burst_avail; // True if we there's a full burst to read
+ reg play_buffer_avail_nonzero; // True if > 0
+ reg cmd_num_lines_cf_nonzero; // True if > 0
+ reg max_dma_size_ok; // True if it's OK to read max_dma_size
+
+ reg [ADDR_WIDTH-1:0] max_dma_size_m1; // max_dma_size - 1
+ reg [ADDR_WIDTH-1:0] play_words_remaining_m1; // play_words_remaining - 1
+
+ reg [ADDR_WIDTH-1:0] play_buffer_avail; // Number of words left to read in record buffer
+ reg [ADDR_WIDTH-1:0] play_buffer_avail_0; // Pipeline stage for computing play_buffer_avail
+
+ always @(posedge clk)
+ begin
+ if (rst) begin
+ play_state <= PLAY_IDLE;
+ cmd_fifo_ready <= 1'b0;
+
+ end else begin
+
+ // Calculate how many words are left to read from the record buffer
+ play_full_burst_avail <= (play_buffer_avail >= MEM_BURST_SIZE);
+ play_buffer_avail_nonzero <= (play_buffer_avail > 0);
+ cmd_num_lines_cf_nonzero <= (cmd_num_lines_cf > 0);
+ play_buffer_end <= play_base_addr_sr + play_buffer_size_sr;
+
+ // Default values
+ cmd_fifo_ready <= 1'b0;
+ read_ctrl_valid <= 1'b0;
+ play_halt_clear <= 1'b0;
+
+ //
+ // State logic
+ //
+ case (play_state)
+ PLAY_IDLE : begin
+ // Always start reading at the start of the record buffer
+ play_addr <= play_base_addr_sr;
+
+ // Save off command info, in case we need to repeat the command
+ cmd_num_lines <= cmd_num_lines_cf;
+ cmd_reload <= cmd_reload_cf;
+ cmd_chain <= cmd_chain_cf;
+
+ // Save the buffer info so it doesn't update during playback
+ play_base_addr <= play_base_addr_sr;
+ play_buffer_size <= play_buffer_size_sr;
+ play_buffer_avail <= play_buffer_size_sr / WORD_SIZE;
+
+ // Wait until we receive a command and we have enough data recorded
+ // to honor it.
+ if (cmd_fifo_valid && ~play_halt_clear) begin
+ // Load the number of word remaining to complete this command
+ play_words_remaining <= cmd_num_lines_cf;
+
+ // We don't support time yet, so we require send_imm to do
+ // anything. Also, we can't do anything until we have data recorded.
+ if (cmd_stop_cf) begin
+ // Do nothing, except clear command from the FIFO
+ cmd_fifo_ready <= 1'b1;
+ end else if (cmd_send_imm_cf
+ && play_buffer_avail_nonzero
+ && cmd_num_lines_cf_nonzero) begin
+ // Dequeue the command from the FIFO
+ cmd_fifo_ready <= 1'b1;
+
+ play_state <= PLAY_WAIT_DATA_READY;
+ end
+ end else if (play_halt) begin
+ // In case we get a HALT after a command has finished
+ play_halt_clear <= 1'b1;
+ end
+ end
+
+ PLAY_WAIT_DATA_READY : begin
+ // Save the maximum size we can read from RAM
+ max_dma_size <= play_full_burst_avail ? MEM_BURST_SIZE : play_buffer_avail;
+
+ // Check if we got a halt command while waiting
+ if (play_halt) begin
+ play_halt_clear <= 1'b1;
+ play_state <= PLAY_IDLE;
+
+ // Wait for output FIFO to empty sufficiently so we can read an
+ // entire burst at once. This may be more space than needed, but we
+ // won't know the exact size until the next state.
+ end else if (play_fifo_space >= MEM_BURST_SIZE) begin
+ play_state <= PLAY_SIZE_CALC;
+ end
+ end
+
+ PLAY_SIZE_CALC : begin
+ // Do some intermediate calculations to determine what the read_count
+ // should be.
+ play_words_remaining_m1 <= play_words_remaining-1;
+ max_dma_size_m1 <= max_dma_size-1;
+ max_dma_size_ok <= play_words_remaining >= max_dma_size;
+ play_state <= PLAY_DMA_REQ;
+ end
+
+ PLAY_DMA_REQ : begin
+ // Load the size of the next read into a register. We try to read the
+ // max amount available (up to the burst size) or however many words
+ // are needed to reach the end of the RAM buffer.
+ //
+ // The read count written to the DMA engine should be 1 less than the
+ // number of words you want to read (not the number of bytes).
+ read_count <= max_dma_size_ok ? max_dma_size_m1 : play_words_remaining_m1;
+
+ // Load the address to read. Note that we don't do an alignment check
+ // since we assume that multiples of MEM_BURST_SIZE meet the
+ // AXI_ALIGNMENT requirement.
+ read_addr <= play_addr;
+
+ // Request the read transaction as soon as DMA interface is ready
+ if (read_ctrl_ready) begin
+ read_ctrl_valid <= 1'b1;
+ play_state <= PLAY_WAIT_DMA_START;
+ end
+ end
+
+ PLAY_WAIT_DMA_START : begin
+ // Wait until DMA interface deasserts ready, indicating it has
+ // started on the request.
+ read_ctrl_valid <= 1'b0;
+ if (!read_ctrl_ready) begin
+ // Update values for next transaction
+ play_addr_0 <= play_addr + ({{(ADDR_WIDTH-COUNT_WIDTH){1'b0}}, read_count} + 1) * WORD_SIZE;
+ play_words_remaining <= play_words_remaining - ({1'b0, read_count} + 1);
+ play_buffer_avail_0 <= play_buffer_avail - ({1'b0, read_count} + 1);
+
+ play_state <= PLAY_WAIT_DMA_COMMIT;
+ end
+ end
+
+ PLAY_WAIT_DMA_COMMIT : begin
+ // Wait for the DMA interface to reassert read_ctrl_ready, which
+ // signals that the DMA engine has received a response for the whole
+ // read transaction.
+ if (read_ctrl_ready) begin
+ // Check if we need to wrap the address for the next transaction
+ if (play_addr_0 >= play_buffer_end) begin
+ play_addr_1 <= play_base_addr_sr;
+ play_buffer_avail <= play_buffer_size_sr / WORD_SIZE;
+ end else begin
+ play_addr_1 <= play_addr_0;
+ play_buffer_avail <= play_buffer_avail_0;
+ end
+
+ play_state <= PLAY_DONE_CHECK;
+ end
+ end
+
+ PLAY_DONE_CHECK : begin
+ play_addr <= play_addr_1;
+
+ // Check if we have more data to transfer for this command
+ if (play_words_remaining) begin
+ play_state <= PLAY_WAIT_DATA_READY;
+
+ // Check if we're chaining
+ end else if (cmd_chain) begin
+ // Check if there's a new command waiting
+ if (cmd_fifo_valid) begin
+ // Load the next command. Note that we don't reset the playback
+ // address when commands are chained together.
+ play_words_remaining <= cmd_num_lines_cf;
+ cmd_num_lines <= cmd_num_lines_cf;
+ cmd_reload <= cmd_reload_cf;
+ cmd_chain <= cmd_chain_cf;
+
+ // Dequeue the command from the FIFO
+ cmd_fifo_ready <= 1'b1;
+
+ // Stop if it's a stop command, otherwise restart
+ if (cmd_stop_cf) begin
+ play_state <= PLAY_IDLE;
+ end else begin
+ play_state <= PLAY_WAIT_DATA_READY;
+ end
+
+ // Check if we need to restart the previous command
+ end else if (cmd_reload) begin
+ play_words_remaining <= cmd_num_lines;
+ play_state <= PLAY_WAIT_DATA_READY;
+ end
+ // Nothing left to do
+ end else begin
+ play_state <= PLAY_IDLE;
+ end
+ end
+ endcase
+
+ end
+ end
+
+ // Connect output of DMA master to playback data FIFO
+ assign play_fifo_i_tdata = read_data;
+ assign play_fifo_i_tvalid = read_data_valid;
+ assign read_data_ready = play_fifo_i_tready;
+
+
+ //---------------------------------------------------------------------------
+ // TLAST Generation
+ //---------------------------------------------------------------------------
+ //
+ // This block monitors the signals to/from the DMA master and generates the
+ // TLAST signal. We assert TLAST at the end of every read transaction and
+ // after every play_max_len_sr words, so that no packets are longer than the
+ // length indicated by the max_len register.
+ //
+ // The timing of this block relies on the fact that read_ctrl_ready is not
+ // reasserted by the DMA master until after TLAST gets asserted.
+ //
+ //---------------------------------------------------------------------------
+
+ reg [COUNT_WIDTH-1:0] read_counter;
+ reg [COUNT_WIDTH-1:0] length_counter;
+ reg play_fifo_i_tlast;
+
+ always @(posedge clk)
+ begin
+ if (rst) begin
+ play_fifo_i_tlast <= 1'b0;
+ end else begin
+ // Check if we're requesting a read transaction
+ if (read_ctrl_valid && read_ctrl_ready) begin
+ // Initialize read_counter for new transaction
+ read_counter <= read_count;
+ length_counter <= play_max_len_sr;
+
+ // If read_count is 0, then the first word is also the last word
+ if (read_count == 0) begin
+ play_fifo_i_tlast <= 1'b1;
+ end
+
+ // Track the number of words read out by DMA master
+ end else if (read_data_valid && read_data_ready) begin
+ read_counter <= read_counter - 1;
+ length_counter <= length_counter - 1;
+
+ // Check if the word currently being output is the last word of a
+ // packet, which means we need to clear tlast.
+ if (play_fifo_i_tlast) begin
+ // But make sure that the next word isn't also the last of a DMA
+ // burst, for which we will need to keep tlast asserted.
+ if (read_counter != 1) begin
+ play_fifo_i_tlast <= 1'b0;
+ end
+
+ // Restart length counter
+ length_counter <= play_max_len_sr;
+
+ // Check if the next word to be output should be the last of a packet.
+ end else if (read_counter == 1 || length_counter == 2) begin
+ play_fifo_i_tlast <= 1'b1;
+ end
+ end
+
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // Playback Output Data FIFO
+ //---------------------------------------------------------------------------
+ //
+ // This FIFO buffers data that has been read out of RAM as part of a playback
+ // operation.
+ //
+ //---------------------------------------------------------------------------
+
+ axi_fifo #(
+ .WIDTH (DATA_WIDTH+1),
+ .SIZE (PLAY_FIFO_ADDR_WIDTH)
+ ) play_axi_fifo (
+ .clk (clk),
+ .reset (rst),
+ .clear (1'b0),
+ //
+ .i_tdata ({play_fifo_i_tlast, play_fifo_i_tdata}),
+ .i_tvalid (play_fifo_i_tvalid),
+ .i_tready (play_fifo_i_tready),
+ //
+ .o_tdata ({o_tlast, o_tdata}),
+ .o_tvalid (o_tvalid),
+ .o_tready (o_tready),
+ //
+ .space (play_fifo_space),
+ .occupied ()
+ );
+
+endmodule \ No newline at end of file