251 lines
7.7 KiB
VHDL
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;
|