Rework DMA to support single-beat transfers

This commit is contained in:
Joris van Rantwijk 2024-08-24 23:04:35 +02:00
parent c50dd84011
commit 4abc2ee165
5 changed files with 305 additions and 86 deletions

View File

@ -1,6 +1,13 @@
-- --
-- AXI3 master for multi-channel DMA controller. -- AXI3 master for multi-channel DMA controller.
-- --
-- Note: DMA transfers may not cross a 4 kB address boundary.
--
-- It is recommended to use only so-called regular transactions.
-- A transaction is regular if its length is 1, 2, 4, 8 or 16 beats,
-- and its address is aligned to the burst length.
-- Regular transactions never cross a 4 kB boundary.
--
-- Joris van Rantwijk 2024 -- Joris van Rantwijk 2024
-- --
@ -14,9 +21,6 @@ use work.puzzlefw_pkg.all;
entity dma_axi_master is entity dma_axi_master is
generic ( generic (
-- Number of beats per transfer.
transfer_size: integer range 1 to 16 := 16;
-- Number of read channels. -- Number of read channels.
num_read_channels: integer range 0 to 16; num_read_channels: integer range 0 to 16;
@ -57,10 +61,11 @@ entity dma_axi_master is
-- Read channels. -- Read channels.
-- The client passes a read command for a specified address via valid/ready handshake. -- The client passes a read command for a specified address via valid/ready handshake.
-- Some time later, the controller pushes (transfer_size) data words out to the channel. -- Some time later, the controller pushes (read_cmd_length + 1) data words out to the channel.
-- The client must be ready to accept all data words. -- The client must be ready to accept all data words.
-- Multiple transfers can be in flight for the channel. -- Multiple transfers can be in flight for the channel.
read_cmd_addr: in dma_address_array(0 to num_read_channels-1); read_cmd_addr: in dma_address_array(0 to num_read_channels-1);
read_cmd_length: in dma_burst_length_array(0 to num_read_channels-1);
read_cmd_valid: in std_logic_vector(num_read_channels-1 downto 0); read_cmd_valid: in std_logic_vector(num_read_channels-1 downto 0);
read_cmd_ready: out std_logic_vector(num_read_channels-1 downto 0); read_cmd_ready: out std_logic_vector(num_read_channels-1 downto 0);
read_data: out dma_data_array(0 to num_read_channels-1); read_data: out dma_data_array(0 to num_read_channels-1);
@ -68,11 +73,12 @@ entity dma_axi_master is
-- Write channels. -- Write channels.
-- The client passes a write command for a specified address via valid/ready handshake. -- The client passes a write command for a specified address via valid/ready handshake.
-- Some time later, the controller pulls (transfer_size) data words from the channel. -- Some time later, the controller pulls (write_cmd_length + 1) data words from the channel.
-- The client must supply these data words promptly. -- The client must supply these data words promptly.
-- Some more time later, the controller pulses write_finished to indicate that the write has finished. -- Some more time later, the controller pulses write_finished to indicate that the write has finished.
-- Multiple transfers can be in flight for the channel. -- Multiple transfers can be in flight for the channel.
write_cmd_addr: in dma_address_array(0 to num_write_channels-1); write_cmd_addr: in dma_address_array(0 to num_write_channels-1);
write_cmd_length: in dma_burst_length_array(0 to num_write_channels-1);
write_cmd_valid: in std_logic_vector(num_write_channels-1 downto 0); write_cmd_valid: in std_logic_vector(num_write_channels-1 downto 0);
write_cmd_ready: out std_logic_vector(num_write_channels-1 downto 0); write_cmd_ready: out std_logic_vector(num_write_channels-1 downto 0);
write_data: in dma_data_array(0 to num_write_channels-1); write_data: in dma_data_array(0 to num_write_channels-1);
@ -131,11 +137,13 @@ architecture arch of dma_axi_master is
-- Registered output signals to AXI bus. -- Registered output signals to AXI bus.
awaddr: std_logic_vector(31 downto 3); awaddr: std_logic_vector(31 downto 3);
awlen: std_logic_vector(3 downto 0);
awvalid: std_logic; awvalid: std_logic;
wdata: std_logic_vector(63 downto 0); wdata: std_logic_vector(63 downto 0);
wlast: std_logic; wlast: std_logic;
wvalid: std_logic; wvalid: std_logic;
araddr: std_logic_vector(31 downto 3); araddr: std_logic_vector(31 downto 3);
arlen: std_logic_vector(3 downto 0);
arvalid: std_logic; arvalid: std_logic;
-- Registered output signals to read channels. -- Registered output signals to read channels.
@ -171,11 +179,13 @@ architecture arch of dma_axi_master is
constant regs_init: regs_type := ( constant regs_init: regs_type := (
awaddr => (others => '0'), awaddr => (others => '0'),
awlen => (others => '0'),
awvalid => '0', awvalid => '0',
wdata => (others => '0'), wdata => (others => '0'),
wlast => '0', wlast => '0',
wvalid => '0', wvalid => '0',
araddr => (others => '0'), araddr => (others => '0'),
arlen => (others => '0'),
arvalid => '0', arvalid => '0',
read_cmd_ready => (others => '0'), read_cmd_ready => (others => '0'),
read_data => (others => '0'), read_data => (others => '0'),
@ -204,11 +214,12 @@ architecture arch of dma_axi_master is
-- Check that the DMA transfer fits inside the address window if it starts at the specified address offset -- Check that the DMA transfer fits inside the address window if it starts at the specified address offset
function is_valid_dma_address(addr: std_logic_vector(31 downto 3); function is_valid_dma_address(addr: std_logic_vector(31 downto 3);
len: std_logic_vector(3 downto 0);
limit: std_logic_vector(31 downto 12)) limit: std_logic_vector(31 downto 12))
return boolean return boolean
is begin is begin
return (unsigned(limit) /= 0) return (unsigned(limit) /= 0)
and (unsigned(addr) <= shift_left(resize(unsigned(limit), 29), 9) - transfer_size); and (unsigned(addr) < shift_left(resize(unsigned(limit), 29), 9) - unsigned(len));
end function; end function;
-- Calculate tha AXI address for a DMA transfer by adding the address offset from the client -- Calculate tha AXI address for a DMA transfer by adding the address offset from the client
@ -224,8 +235,6 @@ architecture arch of dma_axi_master is
begin begin
-- Drive fixed output signals to AXI bus. -- Drive fixed output signals to AXI bus.
m_axi_awlen <= std_logic_vector(to_unsigned(transfer_size - 1, 4)); -- use fixed burst length
m_axi_arlen <= std_logic_vector(to_unsigned(transfer_size - 1, 4)); -- use fixed burst length
m_axi_awsize <= "011"; -- always use 64-bit transfers m_axi_awsize <= "011"; -- always use 64-bit transfers
m_axi_arsize <= "011"; -- always use 64-bit transfers m_axi_arsize <= "011"; -- always use 64-bit transfers
m_axi_awburst <= "01"; -- always use incrementing burst m_axi_awburst <= "01"; -- always use incrementing burst
@ -245,6 +254,7 @@ begin
-- Drive variable output signals to AXI bus. -- Drive variable output signals to AXI bus.
m_axi_awid <= std_logic_vector(to_unsigned(r.write_channel, 6)); m_axi_awid <= std_logic_vector(to_unsigned(r.write_channel, 6));
m_axi_awaddr <= r.awaddr & "000"; -- addresses are 8-byte aligned m_axi_awaddr <= r.awaddr & "000"; -- addresses are 8-byte aligned
m_axi_awlen <= r.awlen;
m_axi_awvalid <= r.awvalid; m_axi_awvalid <= r.awvalid;
m_axi_wid <= std_logic_vector(to_unsigned(r.write_channel, 6)); m_axi_wid <= std_logic_vector(to_unsigned(r.write_channel, 6));
m_axi_wdata <= r.wdata; m_axi_wdata <= r.wdata;
@ -252,6 +262,7 @@ begin
m_axi_wvalid <= r.wvalid; m_axi_wvalid <= r.wvalid;
m_axi_arid <= std_logic_vector(to_unsigned(r.read_channel, 6)); m_axi_arid <= std_logic_vector(to_unsigned(r.read_channel, 6));
m_axi_araddr <= r.araddr & "000"; -- addresses are 8-byte aligned m_axi_araddr <= r.araddr & "000"; -- addresses are 8-byte aligned
m_axi_arlen <= r.arlen;
m_axi_arvalid <= r.arvalid; m_axi_arvalid <= r.arvalid;
-- Drive output signals to read channels. -- Drive output signals to read channels.
@ -340,23 +351,26 @@ begin
when WRITE_STATE_START => when WRITE_STATE_START =>
-- Calculate the AXI address by adding the client address offset to the window base address. -- Calculate the AXI address by adding the client address offset to the window base address.
v.awaddr := calc_dma_address(write_cmd_addr(r.write_channel), window_base_addr); v.awaddr := calc_dma_address(write_cmd_addr(r.write_channel), window_base_addr);
v.awlen := write_cmd_length(r.write_channel);
-- Setup first data word. -- Setup first data word.
v.wdata := write_data(r.write_channel); v.wdata := write_data(r.write_channel);
v.cnt_write_beat := (others => '0'); v.cnt_write_beat := unsigned(write_cmd_length(r.write_channel));
if transfer_size = 1 then if unsigned(write_cmd_length(r.write_channel)) = 0 then
v.wlast := '1'; v.wlast := '1';
else else
v.wlast := '0'; v.wlast := '0';
end if; end if;
-- Check address. -- Check address.
if is_valid_dma_address(write_cmd_addr(r.write_channel), window_size) then if is_valid_dma_address(write_cmd_addr(r.write_channel),
write_cmd_length(r.write_channel),
window_size) then
-- Setup AXI write burst. -- Setup AXI write burst.
v.awvalid := '1'; v.awvalid := '1';
v.wvalid := '1'; v.wvalid := '1';
v.cnt_write_start := r.cnt_write_start + 1; v.cnt_write_start := r.cnt_write_start + 1;
if transfer_size = 1 then if unsigned(write_cmd_length(r.write_channel)) = 0 then
v.write_state := WRITE_STATE_WAIT; v.write_state := WRITE_STATE_WAIT;
else else
v.write_state := WRITE_STATE_FLOW; v.write_state := WRITE_STATE_FLOW;
@ -385,14 +399,14 @@ begin
-- Push data words to interconnect. -- Push data words to interconnect.
if m_axi_wready = '1' then if m_axi_wready = '1' then
v.wdata := write_data(r.write_channel); v.wdata := write_data(r.write_channel);
if r.cnt_write_beat = transfer_size - 2 then if r.cnt_write_beat = 1 then
-- This will be the last beat of the transfer. -- This will be the last beat of the transfer.
v.wlast := '1'; v.wlast := '1';
v.write_state := WRITE_STATE_WAIT; v.write_state := WRITE_STATE_WAIT;
else else
v.wlast := '0'; v.wlast := '0';
end if; end if;
v.cnt_write_beat := r.cnt_write_beat + 1; v.cnt_write_beat := r.cnt_write_beat - 1;
end if; end if;
when WRITE_STATE_WAIT => when WRITE_STATE_WAIT =>
@ -470,9 +484,12 @@ begin
-- Calculate the AXI address by adding the client address offset to the window base address. -- Calculate the AXI address by adding the client address offset to the window base address.
v.araddr := calc_dma_address(read_cmd_addr(r.read_channel), window_base_addr); v.araddr := calc_dma_address(read_cmd_addr(r.read_channel), window_base_addr);
v.arlen := read_cmd_length(r.read_channel);
-- Check address. -- Check address.
if is_valid_dma_address(read_cmd_addr(r.read_channel), window_size) then if is_valid_dma_address(read_cmd_addr(r.read_channel),
read_cmd_length(r.read_channel),
window_size) then
-- Setup AXI read burst. -- Setup AXI read burst.
v.arvalid := '1'; v.arvalid := '1';
v.read_state := READ_STATE_WAIT; v.read_state := READ_STATE_WAIT;

View File

@ -1,8 +1,6 @@
-- --
-- Management of DMA transfers from FPGA to memory. -- Management of DMA transfers from FPGA to memory.
-- --
-- The AXI master MUST be configured for 16 beats per transfer.
--
-- Joris van Rantwijk 2024 -- Joris van Rantwijk 2024
-- --
@ -16,8 +14,16 @@ use work.puzzlefw_pkg.all;
entity dma_write_channel is entity dma_write_channel is
generic ( generic (
-- Preferred DMA burst length as the 2-log of the number of beats.
-- Transactions will use the preferred burst length unless there
-- are fewer words available in the buffer.
transfer_size_bits: integer range 0 to 4 := 4;
-- Size of the input data queue as 2-log of the number of words. -- Size of the input data queue as 2-log of the number of words.
queue_size_bits: integer range 5 to 16 := 10 queue_size_bits: integer range 5 to 16 := 10;
-- Number of clock cycles to wait before starting a small DMA burst.
idle_timeout: natural := 256
); );
port ( port (
@ -32,6 +38,10 @@ entity dma_write_channel is
-- no new transfer will be started. -- no new transfer will be started.
channel_en: in std_logic; channel_en: in std_logic;
-- High if there uncompleted DMA transfers are in progress.
-- If a DMA error occurs, the channel may get stuck in a busy state.
channel_busy: out std_logic;
-- High to initialize the channel. -- High to initialize the channel.
-- --
-- This resets the write pointer to the start of the buffer and deletes -- This resets the write pointer to the start of the buffer and deletes
@ -39,7 +49,7 @@ entity dma_write_channel is
-- --
-- This function must be used whenever the buffer address range is changed. -- This function must be used whenever the buffer address range is changed.
-- This function must not be used while uncompleted DMA transfers -- This function must not be used while uncompleted DMA transfers
-- are in progress. -- are in progress, except to recover from an error.
channel_init: in std_logic; channel_init: in std_logic;
-- Start and end address of the DMA buffer. -- Start and end address of the DMA buffer.
@ -55,10 +65,10 @@ entity dma_write_channel is
-- An interrupt is triggered when the write pointer equals the -- An interrupt is triggered when the write pointer equals the
-- interrupt address. -- interrupt address.
addr_interrupt: in std_logic_vector(31 downto 7); addr_interrupt: in std_logic_vector(31 downto 3);
-- Write pointer. -- Write pointer.
addr_pointer: out std_logic_vector(31 downto 7); addr_pointer: out std_logic_vector(31 downto 3);
-- High to enable interrupt on reaching a configured address. -- High to enable interrupt on reaching a configured address.
intr_en: in std_logic; intr_en: in std_logic;
@ -77,6 +87,7 @@ entity dma_write_channel is
-- Signals to AXI master. -- Signals to AXI master.
write_cmd_addr: out dma_address_type; write_cmd_addr: out dma_address_type;
write_cmd_length: out dma_burst_length_type;
write_cmd_valid: out std_logic; write_cmd_valid: out std_logic;
write_cmd_ready: in std_logic; write_cmd_ready: in std_logic;
write_data: out dma_data_type; write_data: out dma_data_type;
@ -88,37 +99,56 @@ end entity;
architecture arch of dma_write_channel is architecture arch of dma_write_channel is
-- Number of beats per DMA transfer. constant transfer_size: integer range 1 to 16 := 2**transfer_size_bits;
-- 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 state_type is (STATE_SINGLE_IDLE, STATE_SINGLE_START, STATE_SINGLE_DATA,
STATE_BURST_PREPARE,
STATE_BURST_IDLE, STATE_BURST_START, STATE_BURST_DATA);
type regs_type is record type regs_type is record
cmd_valid: std_logic; cmd_valid: std_logic;
cmd_addr: std_logic_vector(31 downto 7); cmd_addr: std_logic_vector(31 downto 3);
addr_pointer: std_logic_vector(31 downto 7); cmd_full_burst: std_logic;
addr_pointer: std_logic_vector(31 downto 3);
intr_out: std_logic; intr_out: std_logic;
channel_busy: std_logic;
state: state_type; state: state_type;
pending_beats: unsigned(4 downto 0); pending_beats: unsigned(3 downto 0);
pending_transfers: unsigned(3 downto 0);
idle_timer: integer range 0 to idle_timeout - 1;
end record; end record;
constant regs_init: regs_type := ( constant regs_init: regs_type := (
cmd_valid => '0', cmd_valid => '0',
cmd_addr => (others => '0'), cmd_addr => (others => '0'),
cmd_full_burst => '0',
addr_pointer => (others => '0'), addr_pointer => (others => '0'),
intr_out => '0', intr_out => '0',
state => STATE_IDLE, channel_busy => '0',
pending_beats => (others => '0') state => STATE_SINGLE_IDLE,
pending_beats => (others => '0'),
pending_transfers => (others => '0'),
idle_timer => 0
); );
signal r: regs_type := regs_init; signal r: regs_type := regs_init;
signal rnext: regs_type; signal rnext: regs_type;
signal s_fifo_reset: std_logic; signal s_fifo_reset: std_logic;
signal s_fifo_valid: std_logic;
signal s_fifo_length: unsigned(queue_size_bits - 1 downto 0); signal s_fifo_length: unsigned(queue_size_bits - 1 downto 0);
-- Return true if address is aligned to a full-size burst.
function is_address_aligned(addr: std_logic_vector(31 downto 3))
return boolean
is begin
if transfer_size = 1 then
return true;
else
return (unsigned(addr(transfer_size_bits + 2 downto 3)) = 0);
end if;
end function;
begin begin
-- --
@ -134,15 +164,18 @@ begin
in_valid => in_valid, in_valid => in_valid,
in_ready => in_ready, in_ready => in_ready,
in_data => in_data, in_data => in_data,
out_valid => open, out_valid => s_fifo_valid,
out_ready => write_data_ready, out_ready => write_data_ready,
out_data => write_data, out_data => write_data,
queue_length => s_fifo_length ); queue_length => s_fifo_length );
-- Drive output ports. -- Drive output ports.
channel_busy <= r.channel_busy;
addr_pointer <= r.addr_pointer; addr_pointer <= r.addr_pointer;
intr_out <= r.intr_out; intr_out <= r.intr_out;
write_cmd_addr <= r.cmd_addr & "0000"; write_cmd_addr <= r.cmd_addr;
write_cmd_length <= "0000" when (r.cmd_full_burst = '0') else
std_logic_vector(to_unsigned(transfer_size - 1, 4));
write_cmd_valid <= r.cmd_valid; write_cmd_valid <= r.cmd_valid;
-- Drive reset signal to FIFO. -- Drive reset signal to FIFO.
@ -160,36 +193,137 @@ begin
-- State machine. -- State machine.
case r.state is case r.state is
when STATE_IDLE => when STATE_SINGLE_IDLE =>
-- Issue a new transfer when possible.
-- Data for the whole transfer must be ready in the FIFO. v.cmd_full_burst := '0';
if (channel_en = '1')
and (s_fifo_length >= transfer_size) -- Clear busy flag.
and (r.cmd_addr /= addr_limit) then if r.pending_transfers = 0 then
v.cmd_valid := '1'; v.channel_busy := '0';
v.state := STATE_START;
v.pending_beats := to_unsigned(transfer_size, v.pending_beats'length);
end if; end if;
when STATE_START => -- Update idle timer.
if (s_fifo_valid = '1') and (r.idle_timer /= idle_timeout - 1) then
v.idle_timer := r.idle_timer + 1;
end if;
-- Switch to burst mode if the FIFO has enough data,
-- and the address is aligned to a full burst.
if (transfer_size > 1)
and (s_fifo_length >= transfer_size)
and is_address_aligned(r.cmd_addr) then
v.state := STATE_BURST_PREPARE;
-- Otherwise, start a single-beat transfer when possible.
elsif (channel_en = '1')
and (s_fifo_valid = '1')
and (r.cmd_addr(31 downto 7) /= addr_limit)
and (r.pending_transfers /= 15)
and ((transfer_size = 1)
or (not is_address_aligned(r.cmd_addr))
or (r.idle_timer = idle_timeout - 1)) then
v.cmd_valid := '1';
v.channel_busy := '1';
v.pending_beats := to_unsigned(0, v.pending_beats'length);
v.state := STATE_SINGLE_START;
end if;
when STATE_SINGLE_START =>
-- Wait until start of transfer.
if write_cmd_ready = '1' then
v.cmd_valid := '0';
-- Update pending transfers.
v.pending_transfers := r.pending_transfers + 1;
-- Update command address.
v.cmd_addr := std_logic_vector(unsigned(r.cmd_addr) + 1);
if v.cmd_addr(31 downto 7) = addr_end then
-- Wrap at end of buffer.
v.cmd_addr := addr_start & "0000";
end if;
if write_data_ready = '1' then
v.state := STATE_SINGLE_IDLE;
else
v.state := STATE_SINGLE_DATA;
end if;
end if;
-- Reset idle counter.
v.idle_timer := 0;
when STATE_SINGLE_DATA =>
-- Wait until data accepted.
if (write_data_ready = '1') and (r.pending_beats = 0) then
v.state := STATE_SINGLE_IDLE;
end if;
when STATE_BURST_PREPARE =>
-- Wait until pending transfers are completed.
if r.pending_transfers = 0 then
v.state := STATE_BURST_IDLE;
end if;
-- Reset idle counter.
v.idle_timer := 0;
when STATE_BURST_IDLE =>
v.cmd_full_burst := '1';
-- Clear busy flag.
if r.pending_transfers = 0 then
v.channel_busy := '0';
end if;
-- Switch to single transfer mode if FIFO has insufficient data.
if (s_fifo_length < transfer_size)
and (r.pending_transfers = 0) then
v.state := STATE_SINGLE_IDLE;
end if;
-- Start a burst transfer if possible.
if (channel_en = '1')
and (s_fifo_length >= transfer_size)
and (r.cmd_addr(31 downto 7) /= addr_limit)
and (r.pending_transfers /= 15) then
v.cmd_valid := '1';
v.channel_busy := '1';
v.pending_beats := to_unsigned(transfer_size - 1, v.pending_beats'length);
v.state := STATE_BURST_START;
end if;
when STATE_BURST_START =>
-- Wait until start of transfer. -- Wait until start of transfer.
if write_cmd_ready = '1' then if write_cmd_ready = '1' then
v.cmd_valid := '0'; v.cmd_valid := '0';
-- Update pending transfers.
v.pending_transfers := r.pending_transfers + 1;
-- Update command address. -- Update command address.
v.cmd_addr := std_logic_vector(unsigned(r.cmd_addr) + 1); v.cmd_addr := std_logic_vector(unsigned(r.cmd_addr) + transfer_size);
if v.cmd_addr = addr_end then if v.cmd_addr(31 downto 7) = addr_end then
-- Wrap at end of buffer. -- Wrap at end of buffer.
v.cmd_addr := addr_start; v.cmd_addr := addr_start & "0000";
end if; end if;
v.state := STATE_FLOW; v.state := STATE_BURST_DATA;
end if; end if;
when STATE_FLOW => when STATE_BURST_DATA =>
-- Wait until last beat. -- Wait until last beat.
if (write_data_ready = '1') and (r.pending_beats = 1) then if (write_data_ready = '1') and (r.pending_beats = 0) then
v.state := STATE_IDLE; v.state := STATE_BURST_IDLE;
end if; end if;
end case; end case;
@ -200,17 +334,29 @@ begin
end if; end if;
-- Update pointer on write completion. -- Update pointer on write completion.
if write_finished = '1' then if (write_finished = '1') and (r.pending_transfers /= 0) then
v.addr_pointer := std_logic_vector(unsigned(r.addr_pointer) + 1); v.pending_transfers := v.pending_transfers - 1;
if v.addr_pointer = addr_end then
-- Wrap at end of buffer. if r.cmd_full_burst = '1' then
v.addr_pointer := addr_start; -- Completion of a full burst.
v.addr_pointer := std_logic_vector(unsigned(r.addr_pointer) + transfer_size);
-- Raise interrupt when pointer passes threshold.
if (intr_en = '1')
and (r.addr_pointer(31 downto 3 + transfer_size_bits)
= addr_interrupt(31 downto 3 + transfer_size_bits)) then
v.intr_out := '1';
end if; end if;
else
-- Completion of single transfer.
v.addr_pointer := std_logic_vector(unsigned(r.addr_pointer) + 1);
end if;
-- Wrap at end of buffer.
if v.addr_pointer(31 downto 7) = addr_end then
v.addr_pointer := addr_start & "0000";
end if; end if;
-- Clear interrupt.
if intr_clear = '1' then
v.intr_out := '0';
end if; end if;
-- Raise interrupt when pointer equals threshold. -- Raise interrupt when pointer equals threshold.
@ -218,13 +364,18 @@ begin
v.intr_out := '1'; v.intr_out := '1';
end if; end if;
-- Initialize channel. -- Clear interrupt.
if channel_init = '1' then if intr_clear = '1' then
v.cmd_valid := '0';
v.cmd_addr := addr_start;
v.addr_pointer := addr_start;
v.intr_out := '0'; v.intr_out := '0';
v.state := STATE_IDLE; end if;
-- Initialize pointers.
if channel_init = '1' then
v.cmd_addr := addr_start & "0000";
v.addr_pointer := addr_start & "0000";
v.state := STATE_SINGLE_IDLE;
v.pending_transfers := (others => '0');
v.idle_timer := 0;
end if; end if;
-- Synchronous reset. -- Synchronous reset.

View File

@ -15,10 +15,27 @@ package puzzlefw_pkg is
subtype dma_address_type is std_logic_vector(31 downto 3); subtype dma_address_type is std_logic_vector(31 downto 3);
type dma_address_array is array(natural range <>) of dma_address_type; type dma_address_array is array(natural range <>) of dma_address_type;
-- Burst length for DMA on AXI bus as number of beats minus 1.
subtype dma_burst_length_type is std_logic_vector(3 downto 0);
type dma_burst_length_array is array (natural range <>) of dma_burst_length_type;
-- 64-bit data for DMA on AXI bus. -- 64-bit data for DMA on AXI bus.
subtype dma_data_type is std_logic_vector(63 downto 0); subtype dma_data_type is std_logic_vector(63 downto 0);
type dma_data_array is array(natural range <>) of dma_data_type; type dma_data_array is array(natural range <>) of dma_data_type;
-- 14-bit ADC data.
constant adc_data_bits: integer := 14;
subtype adc_data_type is std_logic_vector(adc_data_bits - 1 downto 0);
type adc_data_array is array(natural range <>) of adc_data_type;
-- 24-bit averaged data.
constant sample_data_bits: integer := 24;
subtype sample_data_type is std_logic_vector(sample_data_bits - 1 downto 0);
type sample_data_array is array(natural range <>) of sample_data_type;
-- 48-bit timestamp.
constant timestamp_bits: integer := 48;
-- Register addresses. -- Register addresses.
constant reg_addr_mask: std_logic_vector(31 downto 0) := x"0010fffc"; constant reg_addr_mask: std_logic_vector(31 downto 0) := x"0010fffc";
constant reg_info: natural := 16#000000#; constant reg_info: natural := 16#000000#;
@ -35,6 +52,7 @@ package puzzlefw_pkg is
constant reg_acq_channel_ctrl: natural := 16#000214#; constant reg_acq_channel_ctrl: natural := 16#000214#;
constant reg_acq_intr_ctrl: natural := 16#000218#; constant reg_acq_intr_ctrl: natural := 16#000218#;
constant reg_test_led: natural := 16#000404#; constant reg_test_led: natural := 16#000404#;
constant reg_test_divider: natural := 16#000408#;
constant reg_dma_buf_addr: natural := 16#100000#; constant reg_dma_buf_addr: natural := 16#100000#;
constant reg_dma_buf_size: natural := 16#100004#; constant reg_dma_buf_size: natural := 16#100004#;
@ -48,6 +66,11 @@ package puzzlefw_pkg is
& std_logic_vector(to_unsigned(fw_version_major, 8)) & std_logic_vector(to_unsigned(fw_version_major, 8))
& std_logic_vector(to_unsigned(fw_version_minor, 8)); & std_logic_vector(to_unsigned(fw_version_minor, 8));
-- Data stream.
constant msg_adc_data: std_logic_vector(7 downto 0) := x"01";
constant msg_trigger: std_logic_vector(7 downto 0) := x"02";
constant msg_overflow: std_logic_vector(7 downto 0) := x"10";
-- ADC input port type. -- ADC input port type.
type adc_data_input_type is array(0 to 1) of std_logic_vector(15 downto 0); type adc_data_input_type is array(0 to 1) of std_logic_vector(15 downto 0);
@ -55,11 +78,12 @@ package puzzlefw_pkg is
type registers_control is record type registers_control is record
irq_enable: std_logic; irq_enable: std_logic;
test_led: std_logic_vector(7 downto 0); test_led: std_logic_vector(7 downto 0);
test_divider: std_logic_vector(15 downto 0);
dma_en: std_logic; dma_en: std_logic;
acq_addr_start: std_logic_vector(31 downto 7); acq_addr_start: std_logic_vector(31 downto 7);
acq_addr_end: std_logic_vector(31 downto 7); acq_addr_end: std_logic_vector(31 downto 7);
acq_addr_limit: std_logic_vector(31 downto 7); acq_addr_limit: std_logic_vector(31 downto 7);
acq_addr_intr: std_logic_vector(31 downto 7); acq_addr_intr: std_logic_vector(31 downto 3);
acq_channel_en: std_logic; acq_channel_en: std_logic;
acq_intr_en: std_logic; acq_intr_en: std_logic;
dma_buf_addr: std_logic_vector(31 downto 12); dma_buf_addr: std_logic_vector(31 downto 12);
@ -74,7 +98,8 @@ package puzzlefw_pkg is
dma_err_write: std_logic; dma_err_write: std_logic;
dma_err_address: std_logic; dma_err_address: std_logic;
dma_err_any: std_logic; dma_err_any: std_logic;
acq_addr_ptr: std_logic_vector(31 downto 7); acq_addr_ptr: std_logic_vector(31 downto 3);
acq_channel_busy: std_logic;
end record; end record;
-- Trigger registers: write-only access by processor, single-cycle pulse signals to FPGA. -- Trigger registers: write-only access by processor, single-cycle pulse signals to FPGA.
@ -87,6 +112,7 @@ package puzzlefw_pkg is
constant registers_control_init: registers_control := ( constant registers_control_init: registers_control := (
irq_enable => '0', irq_enable => '0',
test_led => (others => '0'), test_led => (others => '0'),
test_divider => (others => '0'),
dma_en => '0', dma_en => '0',
acq_addr_start => (others => '0'), acq_addr_start => (others => '0'),
acq_addr_end => (others => '0'), acq_addr_end => (others => '0'),

View File

@ -137,6 +137,7 @@ architecture arch of puzzlefw_top is
signal s_reg_trigger: registers_trigger; signal s_reg_trigger: registers_trigger;
signal s_dma_write_cmd_addr: dma_address_array(0 downto 0); signal s_dma_write_cmd_addr: dma_address_array(0 downto 0);
signal s_dma_write_cmd_length: dma_burst_length_array(0 to 0);
signal s_dma_write_cmd_valid: std_logic_vector(0 downto 0); signal s_dma_write_cmd_valid: std_logic_vector(0 downto 0);
signal s_dma_write_cmd_ready: std_logic_vector(0 downto 0); signal s_dma_write_cmd_ready: std_logic_vector(0 downto 0);
signal s_dma_write_data: dma_data_array(0 downto 0); signal s_dma_write_data: dma_data_array(0 downto 0);
@ -150,6 +151,7 @@ architecture arch of puzzlefw_top is
signal r_test_prefetch: std_logic; signal r_test_prefetch: std_logic;
signal r_test_raddr: std_logic_vector(9 downto 0); signal r_test_raddr: std_logic_vector(9 downto 0);
signal r_test_waddr: std_logic_vector(9 downto 0); signal r_test_waddr: std_logic_vector(9 downto 0);
signal r_test_cnt: unsigned(15 downto 0);
signal s_test_dout: std_logic_vector(63 downto 0); signal s_test_dout: std_logic_vector(63 downto 0);
signal s_test_ren: std_logic; signal s_test_ren: std_logic;
@ -281,7 +283,6 @@ begin
-- AXI master. -- AXI master.
inst_axi_master: entity work.dma_axi_master inst_axi_master: entity work.dma_axi_master
generic map ( generic map (
transfer_size => 16,
num_read_channels => 0, num_read_channels => 0,
num_write_channels => 1 ) num_write_channels => 1 )
port map ( port map (
@ -297,11 +298,13 @@ begin
err_any => s_reg_status.dma_err_any, err_any => s_reg_status.dma_err_any,
clear_errors => s_reg_trigger.dma_clear, clear_errors => s_reg_trigger.dma_clear,
read_cmd_addr => (others => (others => '0')), read_cmd_addr => (others => (others => '0')),
read_cmd_length => (others => (others => '0')),
read_cmd_valid => (others => '0'), read_cmd_valid => (others => '0'),
read_cmd_ready => open, read_cmd_ready => open,
read_data => open, read_data => open,
read_data_valid => open, read_data_valid => open,
write_cmd_addr => s_dma_write_cmd_addr, write_cmd_addr => s_dma_write_cmd_addr,
write_cmd_length => s_dma_write_cmd_length,
write_cmd_valid => s_dma_write_cmd_valid, write_cmd_valid => s_dma_write_cmd_valid,
write_cmd_ready => s_dma_write_cmd_ready, write_cmd_ready => s_dma_write_cmd_ready,
write_data => s_dma_write_data, write_data => s_dma_write_data,
@ -350,11 +353,14 @@ begin
-- DMA Write Channel -- DMA Write Channel
inst_write_channel: entity work.dma_write_channel inst_write_channel: entity work.dma_write_channel
generic map ( generic map (
queue_size_bits => 10 ) transfer_size_bits => 4,
queue_size_bits => 10,
idle_timeout => 256 )
port map ( port map (
clk => clk_adc, clk => clk_adc,
reset => s_reset, reset => s_reset,
channel_en => s_reg_control.acq_channel_en, channel_en => s_reg_control.acq_channel_en,
channel_busy => s_reg_status.acq_channel_busy,
channel_init => s_reg_trigger.acq_channel_init, channel_init => s_reg_trigger.acq_channel_init,
addr_start => s_reg_control.acq_addr_start, addr_start => s_reg_control.acq_addr_start,
addr_end => s_reg_control.acq_addr_end, addr_end => s_reg_control.acq_addr_end,
@ -368,6 +374,7 @@ begin
in_ready => s_dma_in_ready, in_ready => s_dma_in_ready,
in_data => s_dma_in_data, in_data => s_dma_in_data,
write_cmd_addr => s_dma_write_cmd_addr(0), write_cmd_addr => s_dma_write_cmd_addr(0),
write_cmd_length => s_dma_write_cmd_length(0),
write_cmd_valid => s_dma_write_cmd_valid(0), write_cmd_valid => s_dma_write_cmd_valid(0),
write_cmd_ready => s_dma_write_cmd_ready(0), write_cmd_ready => s_dma_write_cmd_ready(0),
write_data => s_dma_write_data(0), write_data => s_dma_write_data(0),
@ -384,10 +391,24 @@ begin
process (clk_adc) is process (clk_adc) is
begin begin
if rising_edge(clk_adc) then if rising_edge(clk_adc) then
if s_reg_control.test_divider = x"ffff" then
s_dma_in_valid <= '1'; s_dma_in_valid <= '1';
if s_dma_in_ready = '1' then if s_dma_in_ready = '1' then
s_dma_in_data(15 downto 0) <= std_logic_vector(unsigned(s_dma_in_data(15 downto 0)) + 1); s_dma_in_data(47 downto 0) <= std_logic_vector(unsigned(s_dma_in_data(47 downto 0)) + 1);
s_dma_in_data(63 downto 16) <= std_logic_vector(unsigned(s_dma_in_data(63 downto 16)) - 1); s_dma_in_data(63 downto 48) <= (others => '0');
end if;
else
if s_dma_in_ready = '1' then
s_dma_in_valid <= '0';
end if;
if r_test_cnt >= unsigned(s_reg_control.test_divider) then
r_test_cnt <= (others => '0');
s_dma_in_valid <= '1';
s_dma_in_data(47 downto 0) <= std_logic_vector(unsigned(s_dma_in_data(47 downto 0)) + 1);
s_dma_in_data(63 downto 48) <= (others => '0');
else
r_test_cnt <= r_test_cnt + 1;
end if;
end if; end if;
end if; end if;
end process; end process;

View File

@ -101,11 +101,14 @@ begin
when reg_acq_addr_start => v.prdata(31 downto 7) := r.reg_control.acq_addr_start; when reg_acq_addr_start => v.prdata(31 downto 7) := r.reg_control.acq_addr_start;
when reg_acq_addr_end => v.prdata(31 downto 7) := r.reg_control.acq_addr_end; when reg_acq_addr_end => v.prdata(31 downto 7) := r.reg_control.acq_addr_end;
when reg_acq_addr_limit => v.prdata(31 downto 7) := r.reg_control.acq_addr_limit; when reg_acq_addr_limit => v.prdata(31 downto 7) := r.reg_control.acq_addr_limit;
when reg_acq_addr_intr => v.prdata(31 downto 7) := r.reg_control.acq_addr_intr; when reg_acq_addr_intr => v.prdata(31 downto 3) := r.reg_control.acq_addr_intr;
when reg_acq_addr_ptr => v.prdata(31 downto 7) := reg_status.acq_addr_ptr; when reg_acq_addr_ptr => v.prdata(31 downto 3) := reg_status.acq_addr_ptr;
when reg_acq_channel_ctrl => v.prdata(0) := r.reg_control.acq_channel_en; when reg_acq_channel_ctrl =>
v.prdata(0) := r.reg_control.acq_channel_en;
v.prdata(8) := reg_status.acq_channel_busy;
when reg_acq_intr_ctrl => v.prdata(0) := r.reg_control.acq_intr_en; when reg_acq_intr_ctrl => v.prdata(0) := r.reg_control.acq_intr_en;
when reg_test_led => v.prdata(7 downto 0) := r.reg_control.test_led; when reg_test_led => v.prdata(7 downto 0) := r.reg_control.test_led;
when reg_test_divider => v.prdata(15 downto 0) := r.reg_control.test_divider;
when reg_dma_buf_addr => v.prdata(31 downto 12) := r.reg_control.dma_buf_addr; when reg_dma_buf_addr => v.prdata(31 downto 12) := r.reg_control.dma_buf_addr;
when reg_dma_buf_size => v.prdata(31 downto 12) := r.reg_control.dma_buf_size; when reg_dma_buf_size => v.prdata(31 downto 12) := r.reg_control.dma_buf_size;
when others => null; when others => null;
@ -122,7 +125,7 @@ begin
when reg_acq_addr_start => v.reg_control.acq_addr_start := apb_pwdata(31 downto 7); when reg_acq_addr_start => v.reg_control.acq_addr_start := apb_pwdata(31 downto 7);
when reg_acq_addr_end => v.reg_control.acq_addr_end := apb_pwdata(31 downto 7); when reg_acq_addr_end => v.reg_control.acq_addr_end := apb_pwdata(31 downto 7);
when reg_acq_addr_limit => v.reg_control.acq_addr_limit := apb_pwdata(31 downto 7); when reg_acq_addr_limit => v.reg_control.acq_addr_limit := apb_pwdata(31 downto 7);
when reg_acq_addr_intr => v.reg_control.acq_addr_intr := apb_pwdata(31 downto 7); when reg_acq_addr_intr => v.reg_control.acq_addr_intr := apb_pwdata(31 downto 3);
when reg_acq_channel_ctrl => when reg_acq_channel_ctrl =>
v.reg_control.acq_channel_en := apb_pwdata(0); v.reg_control.acq_channel_en := apb_pwdata(0);
v.reg_trigger.acq_channel_init := apb_pwdata(1); v.reg_trigger.acq_channel_init := apb_pwdata(1);
@ -130,6 +133,7 @@ begin
v.reg_control.acq_intr_en := apb_pwdata(0); v.reg_control.acq_intr_en := apb_pwdata(0);
v.reg_trigger.acq_intr_clear := apb_pwdata(1); v.reg_trigger.acq_intr_clear := apb_pwdata(1);
when reg_test_led => v.reg_control.test_led := apb_pwdata(7 downto 0); when reg_test_led => v.reg_control.test_led := apb_pwdata(7 downto 0);
when reg_test_divider => v.reg_control.test_divider := apb_pwdata(15 downto 0);
when reg_dma_buf_addr => v.reg_control.dma_buf_addr := apb_pwdata(31 downto 12); when reg_dma_buf_addr => v.reg_control.dma_buf_addr := apb_pwdata(31 downto 12);
when reg_dma_buf_size => v.reg_control.dma_buf_size := apb_pwdata(31 downto 12); when reg_dma_buf_size => v.reg_control.dma_buf_size := apb_pwdata(31 downto 12);
when others => null; when others => null;