AFON // радиотехник affonya@mail.ru
← Проекты на VHDL

Проект 23. Кольцевой буфер FIFO

Очередь «первым пришёл — первым вышел» согласует блоки с разной скоростью работы. Два указателя и счётчик заполнения дают признаки пустоты и переполнения.

Задача

Организовать очередь данных между двумя блоками схемы.

Тип схемы: Синхронная память с логикой указателей.
Модуль: fifo. Среда: Quartus, Vivado, ISE или свободный GHDL для моделирования. Теорию по языку смотрите в статье VHDL — язык описания аппаратуры, по самим микросхемам — на странице ПЛИС.

Алгоритм

  1. Завести массив-память и два указателя: записи и чтения.
  2. При записи класть данные по указателю записи и сдвигать его.
  3. При чтении отдавать данные по указателю чтения и сдвигать его.
  4. Вести счётчик заполнения и формировать признаки пустоты и заполнения.

Код на 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.