redpitaya-puzzlefw/fpga/rtl/dma_write_channel.vhd

251 lines
7.7 KiB
VHDL

--
-- Management of DMA transfers from FPGA to memory.
--
-- The AXI master MUST be configured for 16 beats per transfer.
--
-- Joris van Rantwijk 2024
--
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use work.puzzlefw_pkg.all;
entity dma_write_channel is
generic (
-- Size of the input data queue as 2-log of the number of words.
queue_size_bits: integer range 5 to 16 := 10
);
port (
-- Main clock, active on rising edge.
clk: in std_logic;
-- Reset, active high, synchronous to main clock.
reset: in std_logic;
-- High to enable the channel, low to pause the channel.
-- When channel_en is low, any ongoing transfer will be completed but
-- no new transfer will be started.
channel_en: in std_logic;
-- High to initialize the channel.
--
-- This resets the write pointer to the start of the buffer and deletes
-- all data from the queue.
--
-- This function must be used whenever the buffer address range is changed.
-- This function must not be used while uncompleted DMA transfers
-- are in progress.
channel_init: in std_logic;
-- Start and end address of the DMA buffer.
-- Both addresses are relative to the DMA window base address.
-- The write pointer wraps to the start address when it would
-- have reached the end address.
addr_start: in std_logic_vector(31 downto 7);
addr_end: in std_logic_vector(31 downto 7);
-- When the write pointer reaches the limit address, DMA transfers
-- are paused until software updates the limit.
addr_limit: in std_logic_vector(31 downto 7);
-- An interrupt is triggered when the write pointer equals the
-- interrupt address.
addr_interrupt: in std_logic_vector(31 downto 7);
-- Write pointer.
addr_pointer: out std_logic_vector(31 downto 7);
-- High to enable interrupt on reaching a configured address.
intr_en: in std_logic;
-- Pulsed high to clear a previous interrupt condition.
intr_clear: in std_logic;
-- High if an enabled interrupt condition occurred and the interrupt
-- has not been cleared yet.
intr_out: out std_logic;
-- Input data stream to the channel.
in_valid: in std_logic;
in_ready: out std_logic;
in_data: in dma_data_type;
-- Signals to AXI master.
write_cmd_addr: out dma_address_type;
write_cmd_valid: out std_logic;
write_cmd_ready: in std_logic;
write_data: out dma_data_type;
write_data_ready: in std_logic;
write_finished: in std_logic
);
end entity;
architecture arch of dma_write_channel is
-- Number of beats per DMA transfer.
-- The AXI master must use the same transfer size.
-- Address alignments must match the transfer size.
constant transfer_size: integer := 16;
type state_type is (STATE_IDLE, STATE_START, STATE_FLOW);
type regs_type is record
cmd_valid: std_logic;
cmd_addr: std_logic_vector(31 downto 7);
addr_pointer: std_logic_vector(31 downto 7);
intr_out: std_logic;
state: state_type;
pending_beats: unsigned(4 downto 0);
end record;
constant regs_init: regs_type := (
cmd_valid => '0',
cmd_addr => (others => '0'),
addr_pointer => (others => '0'),
intr_out => '0',
state => STATE_IDLE,
pending_beats => (others => '0')
);
signal r: regs_type := regs_init;
signal rnext: regs_type;
signal s_fifo_reset: std_logic;
signal s_fifo_length: unsigned(queue_size_bits - 1 downto 0);
begin
--
-- Data FIFO.
--
inst_fifo: entity work.simple_fifo
generic map (
data_width => 64,
fifo_depth_bits => queue_size_bits )
port map (
clk => clk,
reset => s_fifo_reset,
in_valid => in_valid,
in_ready => in_ready,
in_data => in_data,
out_valid => open,
out_ready => write_data_ready,
out_data => write_data,
queue_length => s_fifo_length );
-- Drive output ports.
addr_pointer <= r.addr_pointer;
intr_out <= r.intr_out;
write_cmd_addr <= r.cmd_addr & "0000";
write_cmd_valid <= r.cmd_valid;
-- Drive reset signal to FIFO.
s_fifo_reset <= reset or channel_init;
--
-- Combinatorial process.
--
process (all) is
variable v: regs_type;
begin
-- Load current register values.
v := r;
-- State machine.
case r.state is
when STATE_IDLE =>
-- Issue a new transfer when possible.
-- Data for the whole transfer must be ready in the FIFO.
if (channel_en = '1')
and (s_fifo_length >= transfer_size)
and (r.cmd_addr /= addr_limit) then
v.cmd_valid := '1';
v.state := STATE_START;
v.pending_beats := to_unsigned(transfer_size, v.pending_beats'length);
end if;
when STATE_START =>
-- Wait until start of transfer.
if write_cmd_ready = '1' then
v.cmd_valid := '0';
-- Update command address.
v.cmd_addr := std_logic_vector(unsigned(r.cmd_addr) + 1);
if v.cmd_addr = addr_end then
-- Wrap at end of buffer.
v.cmd_addr := addr_start;
end if;
v.state := STATE_FLOW;
end if;
when STATE_FLOW =>
-- Wait until last beat.
if (write_data_ready = '1') and (r.pending_beats = 1) then
v.state := STATE_IDLE;
end if;
end case;
-- Count write beats to find end of transfer.
if write_data_ready = '1' then
v.pending_beats := r.pending_beats - 1;
end if;
-- Update pointer on write completion.
if write_finished = '1' then
v.addr_pointer := std_logic_vector(unsigned(r.addr_pointer) + 1);
if v.addr_pointer = addr_end then
-- Wrap at end of buffer.
v.addr_pointer := addr_start;
end if;
end if;
-- Clear interrupt.
if intr_clear = '1' then
v.intr_out := '0';
end if;
-- Raise interrupt when pointer equals threshold.
if (intr_en = '1') and (r.addr_pointer = addr_interrupt) then
v.intr_out := '1';
end if;
-- Initialize channel.
if channel_init = '1' then
v.cmd_valid := '0';
v.cmd_addr := addr_start;
v.addr_pointer := addr_start;
v.intr_out := '0';
v.state := STATE_IDLE;
end if;
-- Synchronous reset.
if reset = '1' then
v := regs_init;
end if;
-- Drive new register values to synchronous process.
rnext <= v;
end process;
--
-- Synchronous process.
--
process (clk) is
begin
if rising_edge(clk) then
r <= rnext;
end if;
end process;
end architecture;