Задача
Организовать очередь данных между двумя блоками схемы.
Тип схемы: Синхронная память с логикой указателей.
Модуль: fifo. Среда: Quartus, Vivado, ISE или свободный GHDL для моделирования.
Теорию по языку смотрите в статье VHDL — язык описания аппаратуры,
по самим микросхемам — на странице ПЛИС.
Алгоритм
- Завести массив-память и два указателя: записи и чтения.
- При записи класть данные по указателю записи и сдвигать его.
- При чтении отдавать данные по указателю чтения и сдвигать его.
- Вести счётчик заполнения и формировать признаки пустоты и заполнения.
Код на VHDL
Код проверен компилятором GHDL: проходит анализ и элаборацию без ошибок и предупреждений. Скачать: v23.vhd
--=====================================================
-- Проект 23. Кольцевой буфер FIFO
-- Очередь «первым пришёл — первым вышел» на двух
-- указателях с признаками пустоты и заполнения.
--=====================================================
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity fifo is
generic (
ADDR_W : integer := 4; -- глубина 2^ADDR_W
DATA_W : integer := 8
);
port (
clk : in std_logic;
rst : in std_logic;
wr_en : in std_logic;
rd_en : in std_logic;
din : in std_logic_vector(DATA_W-1 downto 0);
dout : out std_logic_vector(DATA_W-1 downto 0);
empty : out std_logic;
full : out std_logic
);
end entity fifo;
architecture rtl of fifo is
type mem_t is array (0 to 2**ADDR_W - 1) of std_logic_vector(DATA_W-1 downto 0);
signal mem : mem_t := (others => (others => '0'));
signal wr_ptr : unsigned(ADDR_W-1 downto 0) := (others => '0');
signal rd_ptr : unsigned(ADDR_W-1 downto 0) := (others => '0');
signal count : unsigned(ADDR_W downto 0) := (others => '0'); -- на разряд шире
begin
process (clk, rst)
begin
if rst = '1' then
wr_ptr <= (others => '0');
rd_ptr <= (others => '0');
count <= (others => '0');
elsif rising_edge(clk) then
-- запись, если есть место
if wr_en = '1' and count < 2**ADDR_W then
mem(to_integer(wr_ptr)) <= din;
wr_ptr <= wr_ptr + 1;
end if;
-- чтение, если есть данные
if rd_en = '1' and count > 0 then
dout <= mem(to_integer(rd_ptr));
rd_ptr <= rd_ptr + 1;
end if;
-- счётчик заполнения меняется только при несовпадении операций
if wr_en = '1' and count < 2**ADDR_W and not (rd_en = '1' and count > 0) then
count <= count + 1;
elsif rd_en = '1' and count > 0 and not (wr_en = '1' and count < 2**ADDR_W) then
count <= count - 1;
end if;
end if;
end process;
empty <= '1' when count = 0 else '0';
full <= '1' when count = 2**ADDR_W else '0';
end architecture rtl;
Пояснения
- Указатели сами замыкаются в кольцо при переполнении разрядности — отдельная проверка не нужна.
- Счётчик заполнения объявлен на разряд шире адреса: иначе полное и пустое состояния неразличимы.
- Запись при заполненном буфере и чтение из пустого заблокированы — без этой защиты данные молча теряются.
Что дальше
Следующие проекты — в разделе проекты на VHDL. Сравнить подход с языком Verilog можно в статье Verilog, а разобраться в устройстве самих кристаллов — в статье ПЛИС: FPGA и CPLD.