241 lines
9.0 KiB
VHDL
241 lines
9.0 KiB
VHDL
library ieee;
|
|
use ieee.std_logic_1164.all;
|
|
use ieee.numeric_std.all;
|
|
use work.wishbone_package.all;
|
|
|
|
-- Assumption: wishbone_data_width >= wishbone_address_Width
|
|
entity dma is
|
|
generic(
|
|
-- Value 0 cannot stream
|
|
-- Value 1 only slaves with async ACK can stream
|
|
-- Value 2 only slaves with combined latency = 2 can stream
|
|
-- Value 3 only slaves with combined latency = 6 can stream
|
|
-- Value 4 only slaves with combined latency = 14 can stream
|
|
-- ....
|
|
logRingLen : integer := 4
|
|
);
|
|
port(
|
|
-- Common wishbone signals
|
|
clk : in std_logic;
|
|
rst : in std_logic;
|
|
-- Slave control port
|
|
slave_i : in wishbone_slave_in;
|
|
slave_o : out wishbone_slave_out;
|
|
-- Master reader port
|
|
r_master_i : in wishbone_master_in;
|
|
r_master_o : out wishbone_master_out;
|
|
-- Master writer port
|
|
w_master_i : in wishbone_master_in;
|
|
w_master_o : out wishbone_master_out;
|
|
-- Pulsed high completion signal
|
|
interrupt : out std_logic
|
|
);
|
|
end dma;
|
|
|
|
architecture rtl of dma is
|
|
constant ringLen : integer := 2**logRingLen;
|
|
type ring_t is array (ringLen-1 downto 0) of wishbone_data;
|
|
|
|
-- Ring buffer for shipping data from read master to write master
|
|
signal ring : ring_t;
|
|
|
|
-- State registers (pointer into the ring)
|
|
-- Invariant: read_issue_offset >= read_result_offset >= write_issue_offset >= write_result_offset
|
|
-- read_issue_offset - write_result_offset <= ringLen (*NOT* strict '<')
|
|
signal read_issue_offset : unsigned(logRingLen downto 0);
|
|
signal read_result_offset : unsigned(logRingLen downto 0);
|
|
signal write_issue_offset : unsigned(logRingLen downto 0);
|
|
signal write_result_offset : unsigned(logRingLen downto 0);
|
|
|
|
-- DMA control registers
|
|
signal read_issue_address : wishbone_address;
|
|
signal write_issue_address : wishbone_address;
|
|
signal read_stride : wishbone_address;
|
|
signal write_stride : wishbone_address;
|
|
signal transfer_count : wishbone_address;
|
|
-- result status: fail/ok ?
|
|
|
|
-- Registered wishbone control signals
|
|
signal r_master_o_CYC : std_logic;
|
|
signal w_master_o_CYC : std_logic;
|
|
signal r_master_o_STB : std_logic;
|
|
signal w_master_o_STB : std_logic;
|
|
signal slave_o_ACK : std_logic;
|
|
signal slave_o_DAT : wishbone_data;
|
|
|
|
function active_high(x : boolean)
|
|
return std_logic is
|
|
begin
|
|
if (x) then
|
|
return '1';
|
|
else
|
|
return '0';
|
|
end if;
|
|
end active_high;
|
|
|
|
function index(x : unsigned(logRingLen downto 0))
|
|
return integer is
|
|
begin
|
|
if logRingLen > 0 then
|
|
return to_integer(x(logRingLen-1 downto 0));
|
|
else
|
|
return 0;
|
|
end if;
|
|
end index;
|
|
|
|
procedure update(signal o : out wishbone_address) is
|
|
begin
|
|
for i in (wishbone_data_width/8)-1 downto 0 loop
|
|
if slave_i.SEL(i) = '1' then
|
|
o(i*8+7 downto i*8) <= slave_i.DAT(i*8+7 downto i*8);
|
|
end if;
|
|
end loop;
|
|
end update;
|
|
|
|
begin
|
|
-- Hard-wired slave pins
|
|
slave_o.ACK <= slave_o_ACK;
|
|
slave_o.ERR <= '0';
|
|
slave_o.RTY <= '0';
|
|
slave_o.STALL <= '0';
|
|
slave_o.DAT <= slave_o_DAT;
|
|
|
|
-- Hard-wired master pins
|
|
r_master_o.CYC <= r_master_o_CYC;
|
|
w_master_o.CYC <= w_master_o_CYC;
|
|
r_master_o.STB <= r_master_o_STB;
|
|
w_master_o.STB <= w_master_o_STB;
|
|
r_master_o.ADR <= read_issue_address;
|
|
w_master_o.ADR <= write_issue_address;
|
|
r_master_o.SEL <= (others => '1');
|
|
w_master_o.SEL <= (others => '1');
|
|
r_master_o.WE <= '0';
|
|
w_master_o.WE <= '1';
|
|
r_master_o.DAT <= (others => '0');
|
|
w_master_o.DAT <= ring(index(write_issue_offset));
|
|
|
|
main : process(clk)
|
|
variable read_issue_progress : boolean;
|
|
variable read_result_progress : boolean;
|
|
variable write_issue_progress : boolean;
|
|
variable write_result_progress : boolean;
|
|
|
|
variable new_read_issue_offset : unsigned(logRingLen downto 0);
|
|
variable new_read_result_offset : unsigned(logRingLen downto 0);
|
|
variable new_write_issue_offset : unsigned(logRingLen downto 0);
|
|
variable new_write_result_offset : unsigned(logRingLen downto 0);
|
|
variable new_transfer_count : wishbone_address;
|
|
|
|
variable ring_boundary : boolean;
|
|
variable ring_high : boolean;
|
|
variable ring_full : boolean;
|
|
variable ring_empty : boolean;
|
|
variable done_transfer : boolean;
|
|
begin
|
|
if (rising_edge(clk)) then
|
|
if (rst = '1') then
|
|
read_issue_offset <= (others => '0');
|
|
read_result_offset <= (others => '0');
|
|
write_issue_offset <= (others => '0');
|
|
write_result_offset <= (others => '0');
|
|
|
|
read_issue_address <= (others => '0');
|
|
write_issue_address <= (others => '0');
|
|
read_stride <= (others => '0');
|
|
write_stride <= (others => '0');
|
|
transfer_count <= (others => '0');
|
|
|
|
r_master_o_CYC <= '0';
|
|
w_master_o_CYC <= '0';
|
|
r_master_o_STB <= '0';
|
|
w_master_o_STB <= '0';
|
|
slave_o_ACK <= '0';
|
|
slave_o_DAT <= (others => '0');
|
|
interrupt <= '0';
|
|
else
|
|
-- Output any read the user requests
|
|
case to_integer(unsigned(slave_i.ADR(4 downto 2))) is
|
|
when 0 => slave_o_DAT <= read_issue_address;
|
|
when 1 => slave_o_DAT <= write_issue_address;
|
|
when 2 => slave_o_DAT <= read_stride;
|
|
when 3 => slave_o_DAT <= write_stride;
|
|
when 4 => slave_o_DAT <= transfer_count;
|
|
when others => slave_o_DAT <= (others => '0');
|
|
end case;
|
|
|
|
-- Detect bus progress
|
|
read_issue_progress := r_master_o_STB = '1' and r_master_i.STALL = '0';
|
|
write_issue_progress := w_master_o_STB = '1' and w_master_i.STALL = '0';
|
|
read_result_progress := r_master_o_CYC = '1' and (r_master_i.ACK = '1' or r_master_i.ERR = '1' or r_master_i.RTY = '1');
|
|
write_result_progress := w_master_o_CYC = '1' and (w_master_i.ACK = '1' or w_master_i.ERR = '1' or w_master_i.RTY = '1');
|
|
|
|
-- Advance read pointers
|
|
if read_issue_progress then
|
|
read_issue_address <= std_logic_vector(unsigned(read_issue_address) + unsigned(read_stride));
|
|
new_read_issue_offset := read_issue_offset + 1;
|
|
new_transfer_count := std_logic_vector(unsigned(transfer_count) - 1);
|
|
else
|
|
new_read_issue_offset := read_issue_offset;
|
|
new_transfer_count := transfer_count;
|
|
end if;
|
|
if read_result_progress then
|
|
ring(index(read_result_offset)) <= r_master_i.DAT;
|
|
new_read_result_offset := read_result_offset + 1;
|
|
else
|
|
new_read_result_offset := read_result_offset;
|
|
end if;
|
|
|
|
-- Advance write pointers
|
|
if write_issue_progress then
|
|
write_issue_address <= std_logic_vector(unsigned(write_issue_address) + unsigned(write_stride));
|
|
new_write_issue_offset := write_issue_offset + 1;
|
|
else
|
|
new_write_issue_offset := write_issue_offset;
|
|
end if;
|
|
if write_result_progress then
|
|
new_write_result_offset := write_result_offset + 1;
|
|
else
|
|
new_write_result_offset := write_result_offset;
|
|
end if;
|
|
|
|
ring_boundary := index(new_read_issue_offset) = index(new_write_result_offset);
|
|
ring_high := new_read_issue_offset(logRingLen) /= new_write_result_offset(logRingLen);
|
|
ring_full := ring_boundary and ring_high;
|
|
ring_empty := ring_boundary and not ring_high;
|
|
|
|
-- Shorten the critical path by comparing to the undecremented value
|
|
--done_transfer := unsigned(new_transfer_count) = 0;
|
|
done_transfer := unsigned(transfer_count(wishbone_address_width-1 downto 1)) = 0
|
|
and (read_issue_progress or transfer_count(0) = '0');
|
|
|
|
r_master_o_STB <= active_high (not ring_full and not done_transfer);
|
|
r_master_o_CYC <= active_high((not ring_full and not done_transfer) or
|
|
(new_read_result_offset /= new_read_issue_offset));
|
|
w_master_o_STB <= active_high (new_write_issue_offset /= new_read_result_offset);
|
|
w_master_o_CYC <= active_high (new_write_result_offset /= new_read_result_offset);
|
|
interrupt <= active_high (write_result_progress and done_transfer and ring_empty);
|
|
|
|
transfer_count <= new_transfer_count;
|
|
read_issue_offset <= new_read_issue_offset;
|
|
read_result_offset <= new_read_result_offset;
|
|
write_issue_offset <= new_write_issue_offset;
|
|
write_result_offset <= new_write_result_offset;
|
|
|
|
-- Control logic
|
|
if (slave_i.CYC = '1' and slave_i.STB = '1' and slave_i.WE = '1') then
|
|
case to_integer(unsigned(slave_i.ADR(4 downto 2))) is
|
|
when 0 => update(read_issue_address);
|
|
when 1 => update(write_issue_address);
|
|
when 2 => update(read_stride);
|
|
when 3 => update(write_stride);
|
|
when 4 => update(transfer_count);
|
|
when others => null;
|
|
end case;
|
|
end if;
|
|
|
|
slave_o_ACK <= slave_i.CYC and slave_i.STB;
|
|
end if;
|
|
end if;
|
|
end process;
|
|
end rtl;
|