Creating an AXI4-Stream IP for use in Xilinx Vivado

In this post, I will show you how to:

  • Design an ultra-compact FIFO based on SRL32 shift-register LUTs
  • Create a wrapper file, adapting the SRL32 FIFO to be used as an AXI4-Stream FIFO
  • Import the AXI4-Stream FIFO into the Vivado IP Integrator library

2016-03-30 22_19_05-edit_ip_project - [c__proj_fpga_ip_tiny_fifo_syn_tiny_fifo.tmp_edit_ip_project.x

Follow the rules of channel design!

A FIFO is a perfect example of a data stream IP with a sink and a source interface, and which should follow the rules of channel design that I have outlined earlier in my article Principles of FPGA IP Interconnect. If you follow these rules – then adapting our FIFO to AXI4-Stream later is a no-brainer! The channel consists of:

  • The information to be transferred (data, sof, eof, etc)
  • The VALID signal driven by the source
  • The READY signal driven by the sink

Design rules for the source interface

  • As soon as information is available, it should be presented on the source interface along with the VALID signal, without waiting for a READY signal from the sink. Otherwise – a deadlock situation may occur.
  • After the information is presented along with the VALID signal, information and VALID must remain asserted until the handshake with the sink has taken place.
  • The handshake (transfer of information from source to sink) takes place when both VALID and READY are asserted.

Design rules for the sink interface

  • The sink may assert or de-assert the READY signal any time it wants.
  • The handshake (transfer of information from source to sink) takes place when both VALID and READY are asserted.

By allowing the sink to freely control the READY signal, information need not to be registered on the sink input, but rather transferred directly to an arbitrary destination register within the IP after some arbitrary delay. This may save FPGA register resources.

The ultra-compact SRL32 FIFO

One of the basic building blocks within an FPGA is the LUT (Look-Up Table). The LUT is the basic element which supports building of combinatorial expressions within an FPGA user design. However – in a modern Xilinx FPGA, a LUT may have 2 alternate modes of operation:

  • As a small RAM (Random Access Memory)
  • As a 32-bit Shift Register LUT (SRL32) with a dynamic tap

In this example, we want to build our FIFO by inferring SRL32’s. The Vivado synthesis tool will do this for us automatically, provided we do not write code that exceeds the capabilities of the SRL32. So – in our code, we must make sure that:

  • There is no reset input to the shift register.
  • The depth of the shift register should be 32 bits or less.

Write self documenting VHDL code

The attached code is my design for the FIFO which infers SRL32s during synthesis. All signals are given self-explaining descriptive names:

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

entity tiny_fifo is
  generic (
    GC_WIDTH : integer :=  8;  -- FIFO data width
    GC_DEPTH : integer := 32); -- FIFO data depth, <= 32
  port (
    clk            : in std_logic;
    -- FIFO data input
    fifo_in_data   : in  std_logic_vector(GC_WIDTH-1 downto 0);
    fifo_in_valid  : in  std_logic;
    fifo_in_ready  : out std_logic := '0';

    -- FIFO data output
    fifo_out_data  : out std_logic_vector(GC_WIDTH-1 downto 0) := (others => '0');
    fifo_out_valid : out std_logic := '0';
    fifo_out_ready : in  std_logic;
    -- status signals
    fifo_index     : out signed(5 downto 0));
end entity;

architecture arch of tiny_fifo is
  type ram_type is array (GC_DEPTH-1 downto 0) of std_logic_vector (GC_WIDTH-1 downto 0);
  signal fifo            : ram_type := (others => (others => '0'));
  signal fifo_index_i    : signed (5 downto 0) := to_signed(-1, 6);
  signal fifo_empty      : boolean;
  signal fifo_full       : boolean;
  signal fifo_in_enable  : boolean;
  signal fifo_out_enable : boolean;

  fifo_full       <= (fifo_index_i = GC_DEPTH-1);  
  fifo_empty      <= (fifo_index_i = -1);
  fifo_in_ready   <= '1' when (not  fifo_full) else '0';
  fifo_out_valid  <= '1' when (not fifo_empty) else '0';

  fifo_in_enable  <= (fifo_in_valid  = '1') and (not fifo_full );
  fifo_out_enable <= (fifo_out_ready = '1') and (not fifo_empty);
  fifo_out_data   <= fifo(to_integer(unsigned(fifo_index_i(4 downto 0))));  
  fifo_index      <= fifo_index_i;
  process (clk)
    if rising_edge(clk) then
      if fifo_in_enable then
        fifo(GC_DEPTH-1 downto 1) <= fifo(GC_DEPTH-2 downto 0);
        fifo(0)                   <= fifo_in_data;
        if not fifo_out_enable then fifo_index_i <= fifo_index_i + 1; end if;
      elsif fifo_out_enable then fifo_index_i <= fifo_index_i - 1;
      end if;
    end if;  
  end process;
end architecture;

Given that we follow the rules of channel design, would you think that any additional documentation of this module is nesessary?

Resource usage

To analyze the resource usage after synthesis, include the out_of_context synthesis option to disallow synthesis of I/O buffers and clock buffers:

2016-03-30 21_40_55-Project Settings

After synthesis, run Report Utilization. It should show:

  • LUTs : 11 + data width (generic parameter GC_WIDTH). Defalt data width in our code is 8, so total LUTs is 11 + 8 = 19.
  • Registers: 6.

Now – isn’t that compact for a 8-bit wide and 32-word deep FIFO? The schematics for the synthesized design now looks like this:

2016-03-30 21_51_27-tiny_fifo - [C__proj_fpga_ip_tiny_fifo_syn_tiny_fifo.xpr] - Vivado 2015.4

Experiment in your design by changing GC_WIDTH and GC_DEPTH to some other values.

Adapting the SRL32 FIFO to AXI4-Stream

To make our FIFO compatible with AXI4-Stream, all we have to do is create a wrapper around our generic SRL32 FIFO. This wrapper contains only wiring and no logic! Our wrapper file looks like this:

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

entity axis_tiny_fifo is
  generic (
    GC_WIDTH : integer :=  8; 
    GC_DEPTH : integer := 32);
  port (
    aclk          : in std_logic;

    -- axi4 stream slave (data input)
    s_axis_tdata  : in  std_logic_vector(GC_WIDTH-1 downto 0);
    s_axis_tvalid : in  std_logic;
    s_axis_tready : out std_logic;

    -- axi4 stream master (data output)
    m_axis_tdata  : out std_logic_vector(GC_WIDTH-1 downto 0);
    m_axis_tvalid : out std_logic;
    m_axis_tready : in  std_logic;
    -- status signals
    index         : out signed(5 downto 0));
end entity;

architecture arch of axis_tiny_fifo is

  tiny_fifo_i : entity work.tiny_fifo
    generic map (
    port map (
      clk            => aclk,
      fifo_in_data   => s_axis_tdata, 
      fifo_in_valid  => s_axis_tvalid, 
      fifo_in_ready  => s_axis_tready,
      fifo_out_data  => m_axis_tdata, 
      fifo_out_valid => m_axis_tvalid,
      fifo_out_ready => m_axis_tready,
      fifo_index     => index);

end architecture;

Synthesize this design and observe that the FPGA resource usage is identical to above!

Adding optional AXI4 stream signals

If you want, you may now include some of the optional AXI4-Stream signals (TKEEP, TSTRB, TUSER, etc) in your wrapper. All you have to do is:

  • Make the information (data) channel in the SRL32 FIFO wider to accommodate for the extra signals in your AXI4-Stream interface.
  • Wire all the AXI4-Stream signals to data interface on the SRL32 FIFO. The details of that operation is left up to you!

Import your IP for use in Vivado IP Integrator

If you have followed the signal naming convention for AXI4-Stream signals, the Vivado Import IP Wizard will be able to create an IP Integrator block component for you automatically. In Vivado, chose menu item Tools->Create and Package IP:

2016-03-30 22_07_09-Create and Package New IP

Then choose a directory where you have only stored the SRL32 FIFO and its AXI4-Stream wrapper file:

2016-03-30 22_10_14-Create and Package New IP

An IP Packager Project will now be created for you. Just keep everything as default values, and then press Package IP in the Project Manager (Flow Navigator). You’re done!

Create a new block design

In the IP Integrator menu  (Flow Navigator), press Create Block Design. Then – find and add the IP you just created:

2016-03-30 22_19_05-edit_ip_project - [c__proj_fpga_ip_tiny_fifo_syn_tiny_fifo.tmp_edit_ip_project.x

You may also double-click your new IP and change its generic parameters:

2016-03-30 22_20_28-Re-customize IP

Also – notice that your IP is now also available in your IP catalog:

2016-03-30 22_21_14-edit_ip_project - [c__proj_fpga_ip_tiny_fifo_syn_tiny_fifo.tmp_edit_ip_project.x



You have now created a super-compact FIFO that you may now use in your future AXI4-Stream designs!

  • Peri Dural

    Vivado does not want to consider your custom-built axi interface when building the whole system. You have to plug each wire of your axi interface and it does not want to consider it into the Address Editor…

  • Pingback: UVVM Tutorial - QUE()

  • Peter Gorman

    Haven’t tried this yet but it appears to demystify a huge volume of xilinx and ARM documentation into a very simple get-started-quickly example. Thank you.