VERIFICATION of A CUSTOM AXI4 LITE IP USING UVVM

If you are using or plan to use Xilinx Zynq (or any other SoC), most probably you will encounter creating your own IP modules and the poppular way to connect your IP to Processing System is via AXI4 protocol.

AMBA AXI is a communication interface created by ARM. AMBA first defined in 1996 with APB (Advanced Peripheral Bus) and in 2010 AXI4 (Advanced eXtensible Interface) is introduced. AXI4 is royalty-free and its specification is available. If you want to learn more about what is AXI, what are AXI signals and channels, and how they behave during read or write, you can look into its specification page:

https://developer.arm.com/documentation/ihi0022/e/AMBA-AXI3-and-AXI4-Protocol-Specification

In my last posts, I showed how to compile UVVM for modelsim and verified a UART transmitter and a UART transciever system including CDC. You can read them through the links below:

https://www.mehmetburakaykenar.com/an-introductory-modelsim-tutorial-for-vivado-xilinx-users/116/

https://www.mehmetburakaykenar.com/a-uvvm-example-uart-transmitter-testbench-simulation-on-modelsim/128/

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

In my YouTube channel, I started uploading video courses about Zynq SoC HW/SW design and I showed how to create a custom IP having AXI4 Lite interface. I created a simple IP, which reads 8-bit switch value and writes to a AXI4 register and writes the value of the second register to 8-bit leds. The CPU runs a ‘C’ code, which reads the register, which has the switch value, and writes the content of the register to the second register, which the leds are driven with this value.

Now in this post, I will verify if this simple IP having AXI4 Lite interface works properly. Of course again I will use UVVM library. UVVM has a BFM package for AXI4 Lite, so it will be easy as verifying the UART in my latest posts.

NOTE: I suggest you to look at this great post of “Luca Colombini”, which helped me a lot. He also show how to use UVVM for AXI4 Lite verification. He used subtype approach for to use t_axilite_if type of UVVM while I gave constraints (sizes of std_logic_vectors) during signal definition phase.

https://lukipedio.medium.com/journey-into-open-source-vhdl-verification-frameworks-part-2-b92c4dc07aaa

Different than the UART verification, I add axilite_bfm_package:

use uvvm_util.axilite_bfm_pkg.all;

I defined some constants:

-- CONSTANTS
constant c_clkperiod                : time := 10 ns;
constant c_clkfreq                  : integer := 100_000_000;
constant c_clock_high_percentage    : integer := 50;
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";

I created axilite_if signal with t_axilite_if type. During definition of this signal, I gave the sizes of unconstrained std_logic_vector signal definitions inside axilite_bfm_pkg.vhd UVVM package file.

-- axilite_bfm signals
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(4-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)
    )
);

I also created configuration and alert level signals:

signal axilite_bfm_config   : t_axilite_bfm_config := C_AXILITE_BFM_CONFIG_DEFAULT;
signal alert_level : t_alert_level := error;

The instantiation of DUT and signal connections:

DUT : entity work.mba_ledsw_v1_0 
generic map(
-- 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
)
port map(
-- Users to add ports here
sw_i    => sw_i,
led_o   => led_o,
-- 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
);

Clock generation process is easy with UVVM procedures:

-----------------------------------------------------------------------------
-- Clock Generator
-----------------------------------------------------------------------------
clock_generator(clk, c_clkperiod, c_clock_high_percentage);

I defined two variables and two procedures inside the main verificaiton process. This is the recommendation of UVVM axilite bfm quickreference document:

    variable v_data         : std_logic_vector(c_axi_data_width-1 downto 0);
    variable v_addr         : unsigned(c_axi_addr_width- 1 downto 0);

    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;

You can enable or disable messages of UVVM (Espen [main UVVM contributor] always give a comment on my posts on LinkedIn to disable the messages 🙂

    enable_log_msg(ALL_MESSAGES);
    --disable_log_msg(ALL_MESSAGES);

I release the active-low AXI4 reset signal at the beginning:

    -- release active-low resetn signal 
    resetn  <= '0';
    wait for c_clkperiod*10;
    resetn  <= '1';
    wait for c_clkperiod*10;

You can initialize AXI4 Lite signals via “init_axilite_if_signals” function defined in axilite_bfm_pkg package file:

    -- initialize axi lite signals
    axilite_if <= init_axilite_if_signals(c_axi_addr_width,c_axi_data_width);
    wait for c_clkperiod;

If you want to read a register in AXI4 Lite interface and check the value, “axilite_check” procedure is the way to go and using it very easy with the variables and local procedure we defined inside the process:

    -- read the switch value and check, which is the reg0 of axi lite registers
    v_addr  := c_reg0_addr;
    v_data  :=  x"0000003C";
    axilite_check(v_addr, v_data, "0x3C expected");
    wait for c_clkperiod*10;

You can use “axilite_write” procedure to write a value to an address in AXI4 Lite:

    -- write switch value to leds register, which is reg1 of axi lite registers
    v_addr  := c_reg1_addr;
    v_data  :=  x"0000003C";
    axilite_write(v_addr, v_data, "0x3C is written");    
    wait for c_clkperiod*10;

Finally, I checked the led_o output signal of the module by using “check_value” procedure:

    -- check led value, which must be 0x3C
    check_value(led_o, x"3C", ERROR, "");

If you enable the messages you get these results:

# UVVM: ID_LOG_HDR                         0.0 ns  TB seq.                        Start Simulation of TB for custom axi lite IP
# UVVM: -------------------------------------------------------------------------------------------------------------------------------------------------------------------------
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '1'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '1'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '1'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '1'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_POS_ACK                       362.5 ns  TB seq.                        check_value() => OK, for std_logic '0'. '0x3C expected'
# UVVM: ID_BFM                           362.5 ns  TB seq.                        axilite_check(A:x"0", x"0000003C")=> OK, received data = x"3C". '0x3C expected'
# UVVM: ID_BFM                           512.5 ns  TB seq.                        axilite_write(A:x"4", x"0000003C") completed. '0x3C is written'
# UVVM: ID_POS_ACK                       612.5 ns  TB seq.                        check_value() => OK, for slv x"3C"'.

If you don’t want to see these bitwise details, you can disable messsages as always Espen suggests 😊

You can find the custom IP and the testbench code from my github page:

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

You can also visualize the waveform from Modelsim:

I will continue to elaborate on UVVM by testing an SPI master module that I designed before in the next post.

Regards,

Mehmet Burak AYKENAR

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

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

Bir yanıt yazın

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