AXI4-FULL AXI4-LITE and UART INTERFACE SIMULATION in MODELSIM with UVVM LIBRARY

In my last post, I showed how to design a custom AXI4 IP in Vivado, having an AXI4-Full, AXI4-Lite and a UART interface:

https://www.mehmetburakaykenar.com/how-to-create-an-axi4-full-custom-ip-with-axi4-lite-and-uart-interfaces-in-vivado/192/

Now I will show how to verify this IP using UVVM library. In my previous posts, I showed how to verify AXI4-Lite and UART interfaces with UVVM BFM (bus functional model) packages:

https://www.mehmetburakaykenar.com/verification-of-the-high-speed-uart-transciever-with-fifo-cdcs-using-uvvm/150/

https://www.mehmetburakaykenar.com/verification-of-a-custom-axi4-lite-ip-using-uvvm/184/

For this IP, we need to utilize UVVM’s axi_bfm_pkg, axilite_bfm_pkg and uart_bfm_pkg VHDL package files. So our library and package definitions of the code are:

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

library STD;
use std.env.all;

library uvvm_util;
context uvvm_util.uvvm_util_context;

use uvvm_util.axilite_bfm_pkg.all;
use uvvm_util.axi_bfm_pkg.all;
use uvvm_util.uart_bfm_pkg.all;

use std.textio.all;
use std.env.finish;

I defined some constants:

-----------------------------------------------------------------------------
-- CONSTANTS
-----------------------------------------------------------------------------
-- Clock Constants
constant c_clkperiod                : time := 10 ns;
constant c_clkfreq                  : integer := 100_000_000;
constant c_clock_high_percentage    : integer := 50;
-- AXI4-Lite Constants
constant c_axi_addr_width           : integer := 4;
constant c_axi_data_width           : integer := 32;
constant c_reg0_addr                : unsigned (c_axi_addr_width-1 downto 0) := x"0";
constant c_reg1_addr                : unsigned (c_axi_addr_width-1 downto 0) := x"4";
-- AXI4-Full Constants
constant c_axifull_addr_width       : integer := 10;
constant c_axifull_data_width       : integer := 32;
constant c_axifull_id_width         : integer := 1;
constant c_axifull_user_width       : integer := 1;
constant c_axifull_base_addr        : integer := 0;
-- UART constants
constant c_baudrate                 : integer := 115_200;

This is the AXI4-Lite interface signal definition:

signal axilite_if           : t_axilite_if(
    write_address_channel(
        awaddr(c_axi_addr_width-1 downto 0)
        ),
    write_data_channel(
        wdata(c_axi_data_width-1 downto 0),
        wstrb(c_axi_data_width/8-1 downto 0)
        ),
    read_address_channel(
        araddr(c_axi_addr_width-1 downto 0)
    ),
    read_data_channel(
        rdata(c_axi_data_width-1 downto 0)
    )
);

In the axilite_bfm_pkg VHDL package file, awaddr, wdata, wstrb, araddr and rdata ports are defined as “unconstrained”, which means the exact width of these signals are not defined. So, in our case we need to give the width information of these signals.

This condition is also valid for axi_if signal of type t_axi_if:

signal axi_if   : t_axi_if(
    write_address_channel(
        awid(c_axifull_id_width-1 downto 0),
        awaddr(c_axifull_addr_width-1 downto 0),
        awuser(c_axifull_user_width-1 downto 0)
    ),
    write_data_channel(
        wdata(c_axifull_data_width-1 downto 0),
        wstrb(c_axifull_data_width/8-1 downto 0),
        wuser(c_axifull_user_width-1 downto 0)
    ),
    write_response_channel(
        bid(c_axifull_id_width-1 downto 0),
        buser(c_axifull_user_width-1 downto 0)
    ),
    read_address_channel(
        arid(c_axifull_id_width-1 downto 0), 
        araddr(c_axifull_addr_width-1 downto 0),
        aruser(c_axifull_user_width-1 downto 0)
    ),
    read_data_channel(
        rid(c_axifull_id_width-1 downto 0),  
        rdata(c_axifull_data_width-1 downto 0),
        ruser(c_axifull_user_width-1 downto 0)    
    )
);

In DUT instantiation part, for AXI4-Lite signal connections axilite_if signal is used such as:

-- 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,

For the AXI4-Full port connections, axi_if signal is used:

-- Ports of Axi Slave Bus Interface S01_AXI
s01_axi_aclk    => clk,
s01_axi_aresetn => resetn,
-- AXI4-Full write address channel
s01_axi_awid        => axi_if.write_address_channel.awid    ,
s01_axi_awaddr      => axi_if.write_address_channel.awaddr  ,
s01_axi_awlen       => axi_if.write_address_channel.awlen   ,
s01_axi_awsize      => axi_if.write_address_channel.awsize  ,
s01_axi_awburst     => axi_if.write_address_channel.awburst ,
s01_axi_awlock      => axi_if.write_address_channel.awlock  ,
s01_axi_awcache     => axi_if.write_address_channel.awcache ,
s01_axi_awprot      => axi_if.write_address_channel.awprot  ,
s01_axi_awqos       => axi_if.write_address_channel.awqos   ,
s01_axi_awregion    => axi_if.write_address_channel.awregion,
s01_axi_awuser      => axi_if.write_address_channel.awuser  ,
s01_axi_awvalid     => axi_if.write_address_channel.awvalid ,
s01_axi_awready     => axi_if.write_address_channel.awready ,
-- AXI4-Full write data channel
s01_axi_wdata       => axi_if.write_data_channel.wdata      ,
s01_axi_wstrb       => axi_if.write_data_channel.wstrb,
s01_axi_wlast       => axi_if.write_data_channel.wlast,
s01_axi_wuser       => axi_if.write_data_channel.wuser,
s01_axi_wvalid      => axi_if.write_data_channel.wvalid,
s01_axi_wready      => axi_if.write_data_channel.wready,
-- AXI4-Full write response channel
s01_axi_bid         => axi_if.write_response_channel.bid     , 
s01_axi_bresp       => axi_if.write_response_channel.bresp,
s01_axi_buser       => axi_if.write_response_channel.buser,
s01_axi_bvalid      => axi_if.write_response_channel.bvalid,
s01_axi_bready      => axi_if.write_response_channel.bready,
-- AXI4-Full read address channel
s01_axi_arid        => axi_if.read_address_channel.arid     ,
s01_axi_araddr      => axi_if.read_address_channel.araddr   ,
s01_axi_arlen       => axi_if.read_address_channel.arlen    ,
s01_axi_arsize      => axi_if.read_address_channel.arsize   ,
s01_axi_arburst     => axi_if.read_address_channel.arburst  ,
s01_axi_arlock      => axi_if.read_address_channel.arlock   ,
s01_axi_arcache     => axi_if.read_address_channel.arcache  ,
s01_axi_arprot      => axi_if.read_address_channel.arprot   ,
s01_axi_arqos       => axi_if.read_address_channel.arqos    ,
s01_axi_arregion    => axi_if.read_address_channel.arregion,
s01_axi_aruser      => axi_if.read_address_channel.aruser   ,
s01_axi_arvalid     => axi_if.read_address_channel.arvalid  ,
s01_axi_arready     => axi_if.read_address_channel.arready  ,
-- AXI4-Full read data channel
s01_axi_rid         => axi_if.read_data_channel.rid     ,
s01_axi_rdata       => axi_if.read_data_channel.rdata,
s01_axi_rresp       => axi_if.read_data_channel.rresp,
s01_axi_rlast       => axi_if.read_data_channel.rlast,
s01_axi_ruser       => axi_if.read_data_channel.ruser,
s01_axi_rvalid      => axi_if.read_data_channel.rvalid,
s01_axi_rready      => axi_if.read_data_channel.rready

In the main process of the testbench file, UVVM recommends to define local procedures for bfm procedures with some ports are constants, so that it will be easy to call these procedures. Therefore, I defined local procedures inside the process block as UVVM QuickRef guide suggests:

p_main: process
    constant C_SCOPE        : string    := C_TB_SCOPE_DEFAULT;
    variable v_time_stamp   : time      := 0 ns;
    variable v_data         : std_logic_vector(c_axi_data_width-1 downto 0);
    variable v_addr         : unsigned(c_axi_addr_width- 1 downto 0);

    variable v_data_axifull : t_slv_array(0 to 0)(c_axifull_data_width-1 downto 0);
    variable v_addr_axifull : unsigned(c_axifull_addr_width- 1 downto 0);    
    variable v_wstrb_value  : t_slv_array(0 to 0)(3 downto 0);
    variable v_wuser_value  : t_slv_array(0 to 0)(0 downto 0);

    variable v_buser_value : std_logic_vector(c_axifull_user_width-1 downto 0);
    variable v_bresp_value : uvvm_util.axi_bfm_pkg.t_xresp;
    variable v_awlen_value : unsigned(7 downto 0);

    variable v_data_exp    : std_logic_vector(7 downto 0);    

    -----------------------------------------------------------------------------
    -- AXI4-Lite Internal Procedures
    procedure axilite_write (
      constant addr_value : in unsigned;
      constant data_value : in std_logic_vector;
      constant msg        : in string) is
    begin

    axilite_write(
        addr_value,             -- keep as is
        data_value,             -- keep as is
        msg,                    -- keep as is
        clk,                    -- Clock signal
        axilite_if,             -- Signal must be visible in local process scope
        C_SCOPE,                -- Just use the default
        shared_msg_id_panel,    -- Use global, shared msg_id_panel
        axilite_bfm_config      -- Use locally defined configuration or C_AXILITE_BFM_CONFIG_DEFAULT
    );              

    end;    

    -----------------------------------------------------------------------------
    procedure axilite_check (
      constant addr_value : in unsigned;
      constant data_exp   : in std_logic_vector;
      constant msg        : in string) is
    begin

        axilite_check(
            addr_value,             -- keep as is
            data_exp,               -- keep as is
            msg,                    -- keep as is
            clk,                    -- Clock signal
            axilite_if,             -- Signal must be visible in local process scope
            alert_level,            -- alert level
            C_SCOPE,                -- Just use the default
            shared_msg_id_panel,    -- Use global, shared msg_id_panel
            axilite_bfm_config      -- Use locally defined configuration or C_AXILITE_BFM_CONFIG_DEFAULT
        );                          
    end;

    -----------------------------------------------------------------------------
    -- AXI4-Full Internal Procedures
    procedure axi_write (
      constant addr_value : in unsigned;
      constant data_value : in t_slv_array;
      constant msg        : in string) is
    begin       
    
    v_wstrb_value(0)    := "1111";
    v_wuser_value(0)    := (others => '0');
    v_awlen_value       := (others => '0');
    v_buser_value       := "0";

    axi_write(
        awid_value      => "0",       -- Setting a default value
        awaddr_value    => addr_value,  -- keep as is
        awlen_value     => v_awlen_value,       -- Set to length=1
        awsize_value    => 4,           -- Setting a default value
        awburst_value   => INCR,        -- Setting a default value
        awlock_value    => NORMAL,      -- Setting a default value
        awcache_value   => "0000",      -- Setting a default value
        awprot_value    => UNPRIVILEGED_NONSECURE_DATA, -- Setting a default value
        awqos_value     => "0000",      -- Setting a default value
        awregion_value  => "0000",      -- Setting a default value
        awuser_value    => "0",       -- Setting a default value
        wdata_value     => data_value,  -- keep as is
        wstrb_value     => v_wstrb_value,         -- Setting a default value        
        wuser_value     => v_wuser_value,       -- Setting a default value        
        buser_value     => v_buser_value, -- Assigning to a local variable
        bresp_value     => v_bresp_value, -- Assigning to a local variable
        msg             => msg,         -- keep as is
        clk             => clk,         -- Clock signal
        axi_if          => axi_if,      -- Signal must be visible in local process scope
        scope           => C_SCOPE,     -- Setting a default value
        msg_id_panel    => shared_msg_id_panel,     -- Use global, shared msg_id_panel
        config          => C_AXI_BFM_CONFIG_DEFAULT   -- Use locally defined configuration or C_AXI_BFM_CONFIG_DEFAULT
    );              

    end;     

    -----------------------------------------------------------------------------
    -- UART Internal Procedures   
    procedure uart_expect (
        constant data_exp           : in std_logic_vector;
        constant msg                : in string) is 
    begin
        uart_expect (
            data_exp        => data_exp,
            msg             => msg,
            rx              => tx_o,
            terminate_loop  => terminate_loop,
            max_receptions  => 1,
            timeout         => -1 ns,
            alert_level     => ERROR,
            config          => uart_bfm_config,
            scope           => C_SCOPE,
            msg_id_panel    => shared_msg_id_panel
        );
    end; 

Then, for example we can call “uart_expect” procedure within the process block by just entering data_exp and msg parameters instead of all the ports defined in the procedure inside the uart_bfm_pkg VHDL package.

An example usage of axilite_write procedure within the process:

    v_addr  := c_reg0_addr;
    v_data  :=  x"00000100";
    axilite_write(v_addr, v_data, ""); 

At the beginning of the stimuli I initialized the AXI signals with the known functions of BFMs:

    -- initialize AXI signals with functions
    axilite_if  <= init_axilite_if_signals(c_axi_addr_width,c_axi_data_width);
    axi_if      <= init_axi_if_signals(c_axifull_addr_width,c_axifull_data_width,c_axifull_id_width,c_axifull_user_width);

I wrote 256 bytes of data utilizing a for loop through AXI4-Full interface, which will be sent via UART interface:

    -- write 256 bytes to AXI4-Full interface starting from address 0
    for i in 0 to 63 loop
        v_addr_axifull  := to_unsigned((c_axifull_base_addr+i*4), c_axifull_addr_width);
        -- crazy but modelsim gave error when I try concat unsigned parameters !!!
        v_data_axifull(0)  :=   std_logic_vector(to_unsigned(i*4+3,8)) & 
                                std_logic_vector(to_unsigned(i*4+2,8)) &
                                std_logic_vector(to_unsigned(i*4+1,8)) & 
                                std_logic_vector(to_unsigned(i*4+0,8)
                                );
        axi_write(v_addr_axifull,v_data_axifull,"");
        wait for c_clkperiod*4;
    end loop;

As I commented in the code, I had some difficulties when I try to concat 4 bytes of unsigned type signals. That’s the reason for conversion function and type cast.

Then I wrote the length parameter for the IP through AXI4-Lite interface:

    -- write length of the packet
    v_addr  := c_reg0_addr;
    v_data  :=  x"00000100";
    axilite_write(v_addr, v_data, "");    
    wait for c_clkperiod*4;    

Then I trigged and started UART transmission of 256 bytes again by writing to the related register in AXI4-Lite interface:

    -- trigger packet transmission
    v_addr  := c_reg1_addr;
    v_data  :=  x"000000BA";
    axilite_write(v_addr, v_data, "");
    wait for 1 ps;  

I used uart_expect procedure for checking transmitted bytes:

    for i in 0 to 255 loop 
        v_data_exp  := std_logic_vector(to_unsigned(i,8));
        wait for 1 ps;
        uart_expect(v_data_exp,"");
        wait for 1 ps;
    end loop;    

After finishing checking I tried another packet with 8 bytes data:

    -- change first 8 bytes to be transmitted
    v_addr_axifull  := to_unsigned((c_axifull_base_addr), c_axifull_addr_width);    
    v_data_axifull(0)  := x"BA0321F1";
    axi_write(v_addr_axifull,v_data_axifull,"");
    wait for c_clkperiod*4;

    v_addr_axifull  := to_unsigned((c_axifull_base_addr+4), c_axifull_addr_width);    
    v_data_axifull(0)  := x"3129A5BD";
    axi_write(v_addr_axifull,v_data_axifull,"");
    wait for c_clkperiod*4;

This time I called 8 times uart_expect procedure to check transmitted data:

    -- call uart_expect function
    v_data_exp  := x"F1"; wait for 1 ps; uart_expect(v_data_exp,""); wait for 1 ps;    
    v_data_exp  := x"21"; wait for 1 ps; uart_expect(v_data_exp,""); wait for 1 ps;    
    v_data_exp  := x"03"; wait for 1 ps; uart_expect(v_data_exp,""); wait for 1 ps;    
    v_data_exp  := x"BA"; wait for 1 ps; uart_expect(v_data_exp,""); wait for 1 ps;    
    v_data_exp  := x"BD"; wait for 1 ps; uart_expect(v_data_exp,""); wait for 1 ps;    
    v_data_exp  := x"A5"; wait for 1 ps; uart_expect(v_data_exp,""); wait for 1 ps;    
    v_data_exp  := x"29"; wait for 1 ps; uart_expect(v_data_exp,""); wait for 1 ps;    
    v_data_exp  := x"31"; wait for 1 ps; uart_expect(v_data_exp,""); wait for 1 ps;  

Another thing to be aware of it is Vivado creates custom AXI IP with AXI4-Full user signal widths as 0, which generates port signals of std_logic_vector of -1 downto 0, which causes Modelsim to give warnings. I changed the user width values from 0 to 1:

DUT : entity work.uart_axifull_v1_0
generic map(
    -- Users to add parameters here
    -- MBA START
    c_clkfreq   => 100_000_000,
    c_baudrate  => 115_200,
    c_stopbit   => 2,
    -- MBA END
    -- User parameters ends
    -- Do not modify the parameters beyond this line

    -- Parameters of Axi Slave Bus Interface S00_AXI
    C_S00_AXI_DATA_WIDTH    => c_axi_data_width,
    C_S00_AXI_ADDR_WIDTH    => c_axi_addr_width,

    -- Parameters of Axi Slave Bus Interface S01_AXI
    C_S01_AXI_ID_WIDTH      => 1    ,
    C_S01_AXI_DATA_WIDTH    => c_axifull_data_width   ,
    C_S01_AXI_ADDR_WIDTH    => c_axifull_addr_width   ,
    C_S01_AXI_AWUSER_WIDTH  => 1    ,
    C_S01_AXI_ARUSER_WIDTH  => 1    ,
    C_S01_AXI_WUSER_WIDTH   => 1    ,
    C_S01_AXI_RUSER_WIDTH   => 1    ,
    C_S01_AXI_BUSER_WIDTH   => 1
)

Another trick is axi_write procedure of axi_bfm_pkg VHDL package wants t_slv_array record type signals. So I defined variables inside the process with t_slv_array type, which is defined inside axi_bfm_pkg VHDL package:

    variable v_data_axifull : t_slv_array(0 to 0)(c_axifull_data_width-1 downto 0);
    variable v_addr_axifull : unsigned(c_axifull_addr_width- 1 downto 0);    
    variable v_wstrb_value  : t_slv_array(0 to 0)(3 downto 0);
    variable v_wuser_value  : t_slv_array(0 to 0)(0 downto 0);

The simulation finished with no errors:

# UVVM:      ====================================================================================================================================================================
# UVVM:      *** FINAL SUMMARY OF ALL ALERTS ***
# UVVM:      ====================================================================================================================================================================
# UVVM:                                REGARDED   EXPECTED  IGNORED      Comment?
# UVVM:                NOTE         :      0         0         0         ok
# UVVM:                TB_NOTE      :      0         0         0         ok
# UVVM:                WARNING      :      0         0         0         ok
# UVVM:                TB_WARNING   :      1         0         0         *** TB_WARNING ***
# UVVM:                MANUAL_CHECK :      0         0         0         ok
# UVVM:                ERROR        :      0         0         0         ok
# UVVM:                TB_ERROR     :      0         0         0         ok
# UVVM:                FAILURE      :      0         0         0         ok
# UVVM:                TB_FAILURE   :      0         0         0         ok
# UVVM:      ====================================================================================================================================================================
# UVVM:      >> Simulation SUCCESS: No mismatch between counted and expected serious alerts
# UVVM:      ====================================================================================================================================================================

An example waveform view from the second data packet sent:

An example AXI4-Lite write transaction from waveform:

Ax example AXI4-Full write transaction issues from waveform:

You can find the full testbench file from my github page:

https://github.com/mbaykenar/apis_anatolia/tree/main/website/axifull_uvvm

In this post, I showed how to verify a custom IP created in Vivado having an AXI4-Lite interface, an AXI4-Full interface and a UART interface by using axi_bfm_pkg, axilite_bfm_pkg and uart_bfm_pkg of UVVM library. Now I can focus on verifying modules with SPI and I2C interfaces in next posts.

Regards,

Mehmet Burak AYKENAR

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

https://tr.linkedin.com/in/mehmet-burak-aykenar-73326419a

0 thoughts to “AXI4-FULL AXI4-LITE and UART INTERFACE SIMULATION in MODELSIM with UVVM LIBRARY”

  1. Geri bildirim: FPGA Weekly News #002

Bir yanıt yazın

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