0
0
VhdlHow-ToIntermediate · 4 min read

VHDL Code for Asynchronous FIFO: Syntax and Example

An asynchronous FIFO in VHDL uses separate clock domains for write and read operations, with dual-port memory and gray-coded pointers for safe crossing. The write_clock and read_clock control independent write and read processes, ensuring data integrity across clock domains.
📐

Syntax

An asynchronous FIFO in VHDL typically includes signals for separate write and read clocks, write and read pointers, memory array, and synchronization registers. The write process uses write_clock to store data, while the read process uses read_clock to output data. Gray code counters are used for pointers to safely cross clock domains.

  • write_clock: Clock for writing data
  • read_clock: Clock for reading data
  • write_pointer: Tracks write position in memory
  • read_pointer: Tracks read position in memory
  • memory: Array storing FIFO data
  • gray_code: Used for pointer synchronization
vhdl
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity async_fifo is
  generic(
    DATA_WIDTH : integer := 8;
    FIFO_DEPTH : integer := 16
  );
  port(
    write_clock : in std_logic;
    write_reset : in std_logic;
    write_enable : in std_logic;
    write_data : in std_logic_vector(DATA_WIDTH-1 downto 0);
    read_clock : in std_logic;
    read_reset : in std_logic;
    read_enable : in std_logic;
    read_data : out std_logic_vector(DATA_WIDTH-1 downto 0);
    full : out std_logic;
    empty : out std_logic
  );
end async_fifo;
💻

Example

This example shows a complete asynchronous FIFO with separate write and read clocks, gray-coded pointers, and dual-port memory. It demonstrates safe data transfer between two clock domains.

vhdl
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity async_fifo is
  generic(
    DATA_WIDTH : integer := 8;
    FIFO_DEPTH : integer := 16
  );
  port(
    write_clock : in std_logic;
    write_reset : in std_logic;
    write_enable : in std_logic;
    write_data : in std_logic_vector(DATA_WIDTH-1 downto 0);
    read_clock : in std_logic;
    read_reset : in std_logic;
    read_enable : in std_logic;
    read_data : out std_logic_vector(DATA_WIDTH-1 downto 0);
    full : out std_logic;
    empty : out std_logic
  );
end async_fifo;

architecture rtl of async_fifo is
  constant ADDR_WIDTH : integer := integer(ceil(log2(real(FIFO_DEPTH))));

  type mem_type is array(0 to FIFO_DEPTH-1) of std_logic_vector(DATA_WIDTH-1 downto 0);
  signal memory : mem_type := (others => (others => '0'));

  signal write_pointer_bin, write_pointer_gray : unsigned(ADDR_WIDTH downto 0) := (others => '0');
  signal read_pointer_bin, read_pointer_gray : unsigned(ADDR_WIDTH downto 0) := (others => '0');

  signal write_pointer_gray_sync1, write_pointer_gray_sync2 : unsigned(ADDR_WIDTH downto 0) := (others => '0');
  signal read_pointer_gray_sync1, read_pointer_gray_sync2 : unsigned(ADDR_WIDTH downto 0) := (others => '0');

  function to_gray(bin : unsigned) return unsigned is
  begin
    return bin xor (bin srl 1);
  end function;

  function to_bin(gray : unsigned) return unsigned is
    variable bin : unsigned(gray'range) := (others => '0');
  begin
    bin(gray'high) := gray(gray'high);
    for i in gray'high-1 downto 0 loop
      bin(i) := bin(i+1) xor gray(i);
    end loop;
    return bin;
  end function;

begin
  -- Write process
  process(write_clock)
  begin
    if rising_edge(write_clock) then
      if write_reset = '1' then
        write_pointer_bin <= (others => '0');
        write_pointer_gray <= (others => '0');
      else
        if write_enable = '1' and full = '0' then
          memory(to_integer(write_pointer_bin(ADDR_WIDTH-1 downto 0))) <= write_data;
          write_pointer_bin <= write_pointer_bin + 1;
          write_pointer_gray <= to_gray(write_pointer_bin + 1);
        end if;
      end if;
    end if;
  end process;

  -- Read process
  process(read_clock)
  begin
    if rising_edge(read_clock) then
      if read_reset = '1' then
        read_pointer_bin <= (others => '0');
        read_pointer_gray <= (others => '0');
        read_data <= (others => '0');
      else
        if read_enable = '1' and empty = '0' then
          read_data <= memory(to_integer(read_pointer_bin(ADDR_WIDTH-1 downto 0)));
          read_pointer_bin <= read_pointer_bin + 1;
          read_pointer_gray <= to_gray(read_pointer_bin + 1);
        end if;
      end if;
    end if;
  end process;

  -- Synchronize read pointer to write clock domain
  process(write_clock)
  begin
    if rising_edge(write_clock) then
      read_pointer_gray_sync1 <= read_pointer_gray;
      read_pointer_gray_sync2 <= read_pointer_gray_sync1;
    end if;
  end process;

  -- Synchronize write pointer to read clock domain
  process(read_clock)
  begin
    if rising_edge(read_clock) then
      write_pointer_gray_sync1 <= write_pointer_gray;
      write_pointer_gray_sync2 <= write_pointer_gray_sync1;
    end if;
  end process;

  -- Full flag
  full <= '1' when (write_pointer_gray(ADDR_WIDTH-1 downto 0) = (not read_pointer_gray_sync2(ADDR_WIDTH-1 downto 0))) and
                   (write_pointer_gray(ADDR_WIDTH) /= read_pointer_gray_sync2(ADDR_WIDTH)) else '0';

  -- Empty flag
  empty <= '1' when read_pointer_gray = write_pointer_gray_sync2 else '0';

end rtl;
⚠️

Common Pitfalls

Common mistakes when designing asynchronous FIFOs include:

  • Not using gray code for pointers, which can cause metastability and incorrect pointer synchronization.
  • Failing to synchronize pointers across clock domains properly, leading to data corruption.
  • Incorrectly calculating full and empty flags, causing premature full or empty signals.
  • Not resetting pointers correctly on reset signals.

Always use double flip-flop synchronization for pointers crossing clock domains and gray code counters for safe pointer updates.

vhdl
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

-- WRONG: Using binary counters directly for pointer synchronization
signal write_pointer_bin : unsigned(ADDR_WIDTH downto 0);
signal read_pointer_bin_sync : unsigned(ADDR_WIDTH downto 0);

-- This can cause metastability and incorrect FIFO status.

-- RIGHT: Use gray code counters and double synchronization
signal write_pointer_gray : unsigned(ADDR_WIDTH downto 0);
signal read_pointer_gray_sync1, read_pointer_gray_sync2 : unsigned(ADDR_WIDTH downto 0);

-- Synchronize with two flip-flops in the other clock domain
process(other_clock)
begin
  if rising_edge(other_clock) then
    read_pointer_gray_sync1 <= read_pointer_gray;
    read_pointer_gray_sync2 <= read_pointer_gray_sync1;
  end if;
end process;
📊

Quick Reference

Tips for designing asynchronous FIFOs in VHDL:

  • Use separate clocks for write and read processes.
  • Implement gray code counters for write and read pointers.
  • Synchronize pointers crossing clock domains with double flip-flops.
  • Calculate full and empty flags using synchronized pointers.
  • Reset pointers properly on reset signals.

Key Takeaways

Use gray code counters for write and read pointers to avoid metastability.
Synchronize pointers crossing clock domains with double flip-flops.
Separate write and read clocks control independent FIFO operations.
Calculate full and empty flags using synchronized pointers for correct status.
Reset pointers properly to initialize FIFO state.