Verification of a Custom AXI-Stream IP with VHDL using UVVM Library

In my latest post, I showed how to create a custom AXI-Stream IP in Vivado. This basic IP was an arithmetic coprocessor accelerator, performs addition, subtraction or multiplication according to the configuration:

In that post, I just showed how to create the IP in Vivado and write the RTL code, but did not verify it. Now in this post, I will verify the functionality of the IP with the help of UVVM (Universal VHDL Verification Methodology).

UVVM is an open-source methodology and library for creating structural VHDL-based testbenches. It is maintained on github:

There is also UVVM_light repo, where bus functional model based VHDL packages and some utility packages are included. As far, I used UVVM_light to verify my other designs in my website. Here is the link for UVVM_light:

Previously, I verified modules having UART, AXI4-Lite, AXI4-Full and SPI interfaces and shared as tutorials in my webpage. All the source codes are also included:

Verifying a module with AXI-Stream interface is no difference. We will utilize AXI-Stream BFM package of UVVM, sent and receive packets. In our module, we also have an AXI4-lite interface, so we will need AXI4-Lite BFM package also. I will tell step-by-step what I have done in the verification code. I used Intel Modelsim free version for simulation. You need a simulator tool that supports VHDL-2008.

In order to use structs, functions and procedures to simulate AXI4-lite and AXI-Stream transactions, we need to include related UVVM packages:

use uvvm_util.axilite_bfm_pkg.all;
use uvvm_util.axistream_bfm_pkg.all

Testbench module only has generic parameters related to AXI data and address widths:

entity tb_axis_coprocessor_v1_0 is
generic (
-- Parameters of Axi Slave Bus Interface S00_AXI
C_S00_AXI_DATA_WIDTH    : integer   := 32;
C_S00_AXI_ADDR_WIDTH    : integer   := 4;
-- Parameters of Axi Slave Bus Interface S00_AXIS
C_S00_AXIS_TDATA_WIDTH  : integer   := 64;
-- Parameters of Axi Master Bus Interface M00_AXIS
C_M00_AXIS_TDATA_WIDTH  : integer   := 64
end tb_axis_coprocessor_v1_0;

UVVM provides new types, t_axilite_if and t_axistream_if VHDL record structures to define AXI interfaces. t_axilite_if is defined in axilite_bfm_pkg.vhd and t_axistream_if is defined in axistream_bfm_pkg.vhd files. We will define new signals in our verification code to interface with the DUT’s AXI ports. We also need to configure AXI interface properties and for this purpose another types are defined in UVVM, t_axilite_bfm_config and t_axistream_bfm_config. I have to say that, once you are comfortable with using UVVM for an interface, the structure is very similar for other interfaces, which eases the process of creating new testbenches. I created new subtype for AXI-Stream as suggested in the UVVM example documentation.

-- AXILITE_BFM signals
signal axilite_if           : t_axilite_if(
        awaddr(c_axi_addr_width-1 downto 0)
        wdata(c_axi_data_width-1 downto 0),
        wstrb(4-1 downto 0)
        araddr(c_axi_addr_width-1 downto 0)
        rdata(c_axi_data_width-1 downto 0)
signal axilite_bfm_config   : t_axilite_bfm_config := C_AXILITE_BFM_CONFIG_DEFAULT;

subtype t_axis is t_axistream_if(
tdata(C_S00_AXIS_TDATA_WIDTH-1 downto 0),
tkeep(C_S00_AXIS_TDATA_WIDTH/8-1 downto 0),
tuser(0 downto 0), tstrb(C_S00_AXIS_TDATA_WIDTH/8-1 downto 0),
tid(0 downto 0), tdest( 0 downto 0)
signal m_axis                   : t_axis;
signal s_axis                   : t_axis;
signal axistream_bfm_config     : t_axistream_bfm_config := C_AXISTREAM_BFM_CONFIG_DEFAULT;

-- SIGNALS for transfer data between processes
-- need to find a way to transmit specific amount of bytes, so no need to define multiple variables!
signal exp_data_array       : t_slv_array(0 to 0)(c_axistream_data_width-1 downto 0);
signal exp_data_array10     : t_slv_array(0 to 9)(c_axistream_data_width-1 downto 0);
signal axis_transfer_cnt    : integer range 0 to 255    := 0;
signal axis_transfer_start  : std_logic                 := '0';

Here is the DUT instantiation:

DUT : entity work.axis_coprocessor_v1_0 
generic map(
-- Parameters of Axi Slave Bus Interface S00_AXI
-- Parameters of Axi Slave Bus Interface S00_AXIS
-- Parameters of Axi Master Bus Interface M00_AXIS
port map(
-- Ports of Axi Slave Bus Interface S00_AXI
s00_axi_aclk    => clk,
s00_axi_aresetn => resetn,
-- AXI4 write address channel
s00_axi_awaddr  => axilite_if.write_address_channel.awaddr,
s00_axi_awprot  => axilite_if.write_address_channel.awprot,
s00_axi_awvalid => axilite_if.write_address_channel.awvalid,
s00_axi_awready => axilite_if.write_address_channel.awready,
-- AXI4 write data channel
s00_axi_wdata   => axilite_if.write_data_channel.wdata,
s00_axi_wstrb   => axilite_if.write_data_channel.wstrb,
s00_axi_wvalid  => axilite_if.write_data_channel.wvalid,
s00_axi_wready  => axilite_if.write_data_channel.wready,
-- AXI4 write response channel
s00_axi_bresp   => axilite_if.write_response_channel.bresp,
s00_axi_bvalid  => axilite_if.write_response_channel.bvalid,
s00_axi_bready  => axilite_if.write_response_channel.bready,
-- AXI4 read address channel
s00_axi_araddr  => axilite_if.read_address_channel.araddr,
s00_axi_arprot  => axilite_if.read_address_channel.arprot,
s00_axi_arvalid => axilite_if.read_address_channel.arvalid,
s00_axi_arready => axilite_if.read_address_channel.arready,
-- AXI4 read data channel
s00_axi_rdata   => axilite_if.read_data_channel.rdata,
s00_axi_rresp   => axilite_if.read_data_channel.rresp,
s00_axi_rvalid  => axilite_if.read_data_channel.rvalid,
s00_axi_rready  => axilite_if.read_data_channel.rready,
-- Ports of Axi Slave Bus Interface S00_AXIS
s00_axis_aclk       => clk,
s00_axis_aresetn    => resetn,
s00_axis_tready     => m_axis.tready,
s00_axis_tdata      => m_axis.tdata,
s00_axis_tlast      => m_axis.tlast,
s00_axis_tvalid     => m_axis.tvalid,
-- Ports of Axi Master Bus Interface M00_AXIS
m00_axis_aclk       => clk,
m00_axis_aresetn    => resetn,
m00_axis_tvalid     => s_axis.tvalid,
m00_axis_tdata      => s_axis.tdata,
m00_axis_tlast      => s_axis.tlast,
m00_axis_tready     => s_axis.tready

I created two processes: p_main, where I handled AXI4-Lite and AXI-Stream master transactions, p_receive, where I check AXI-Stream slave transactions if they are correct or not. In p_main process, I used axilite_write, axilite_check and axistream_transmit procedures of the UVVM, in p_receive process, I used axistream_expect procedure.

Since I published the testbench code in my github page, I won’t go into every line and detail, but just show how to transmit and check AXI transactions with the given procedures

Here is the example of AXI4-Lite write and check procedure utilizations:

-- write 0x01 to reg0 register, add:0x00, sub:0x01, mult:0x02
v_addr  := c_reg0_addr;
v_data  :=  x"00000001";
axilite_write(v_addr, v_data, "0x01 is written");    
wait for c_clkperiod*10;

-- read reg0 register value and check
v_addr  := c_reg0_addr;
v_data  :=  x"00000001";
axilite_check(v_addr, v_data, "0x01 expected");
wait for c_clkperiod*10;

Here is writing to AXI-Stream slave port with one data word transfer:

-- write AXI-Stream packet to slave port
v_data_array(0)     := (x"0000000200000003");
axis_transfer_cnt   <= 1;
axis_transfer_start <= '1';
exp_data_array(0)   <= std_logic_vector(unsigned(v_data_array(0)(c_axistream_data_width-1 downto c_axistream_data_width/2)) * 
                                        unsigned(v_data_array(0)(c_axistream_data_width/2-1 downto 0)));
axis_transfer_start <= '0';
wait for c_clkperiod*10;

Here is writing to AXI-Stream slave port with ten random data words transfer:

-- write AXI-Stream packet to slave port
for i in 0 to 9 loop
    v_data_array10(i)       := random(64); 
    exp_data_array10(i)     <= std_logic_vector(unsigned(v_data_array10(i)(c_axistream_data_width-1 downto c_axistream_data_width/2)) * 
                                unsigned(v_data_array10(i)(c_axistream_data_width/2-1 downto 0)));
end loop;
axis_transfer_cnt   <= 10;
axis_transfer_start <= '1';
axis_transfer_start <= '0';
wait for c_clkperiod*10;

And for checking of AXI-Stream master transactions in p_receive process I used a while loop:

while (sim_done = 0) loop
    wait until rising_edge(axis_transfer_start);
        if axis_transfer_cnt=1 then
            v_exp_data_array(0) := exp_data_array(0);
            axistream_expect(v_exp_data_array,"Single data word transfer");    
            v_exp_data_array10 := exp_data_array10;
            axistream_expect(v_exp_data_array10,"Multiple data word transfer");            
        end if;
end loop;

Here is the complete waveform for full testbench:

Here is zoomed image for a transaction:

In this post, I showed how to verify an IP with AXI-Stream interfaces utilizing UVVM library. In the next post, I plan to use this custom AXI-Stream IP and create a project in Vivado, run in Zedboard. You can find VHDL code in my github repo:


Mehmet Burak AYKENAR

You can connect me via LinkedIn: Just sent me an invitation

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir