Using I/O Traffic Generators - 2020.2 English

Vitis Unified Software Platform Documentation: Application Acceleration Development (UG1393)

Document ID
UG1393
Release Date
2021-03-22
Version
2020.2 English

Introduction

Some user applications such as video streaming and Ethernet-based applications make use of I/O ports on the platform to stream data into and out of the platform. For these applications, performing hardware emulation of the design requires a mechanism to mimic the hardware behavior of the I/O port, and to simulate data traffic running through the ports. I/O traffic generators let you model traffic through the I/O ports during hardware emulation in the Vitis application acceleration development flow, or during logic simulation in the Vivado Design Suite.

Adding Traffic Generators to your Design

Xilinx devices have rich I/O interfaces. The Alveo accelerator cards primarily have PCIe and DDR memory interfaces which have their own specific model. However, your platforms could also have other I/Os, for example GT-kernel based generic I/O, Video Streams, and Sensor data. I/O Traffic Generator kernels provide a method for platforms and applications to inject traffic onto the I/O during simulation.

This solution requires both the inclusion of streaming I/O kernels (XO) or IP in your design, and the use of a Python or C++ API provided by Xilinx to inject traffic or to capture output data from the emulation process. The Xilinx provided Python or C++ library can be used to integrate traffic generator code into your application, run it as a separate process, and have it interface with the emulation process. Currently, Xilinx provides a library that enables interfacing at AXI4-Stream level to mimic any Streaming I/O.

The streaming I/O model can be used to emulate streaming traffic on the platform, and also support delay modeling. You can add streaming I/O to your application, or add them to your custom platform design as described below:

  • Streaming I/O kernels can be added to the device binary (XCLBIN) file like any other compiled kernel object (XO) file, using the v++ --link command. The Vitis installation provides kernels for AXI4-Stream interfaces of various data widths. These can be found in the software installation at $XILINX_VITIS/data/emulation/XO.

    Add these to your designs using the following example command:

    v++ -t hw_emu --link $XILINX_VITIS/data/emulation/XO/sim_ipc_axis_master_32.xo $XILINX_VITIS/data/emulation/XO/sim_ipc_axis_slave_32.xo ... 

    In the example above, the sim_ipc_axis_master_32.xo and sim_ipc_axis_slave_32.xo provide 32-bit master and slave kernels that can be linked with the target platform and other kernels in your design to create the .xclbin file for the hardware emulation build.

  • IPC modules can also be added to a platform block design using the Vivado IP integrator feature for Versal and Zynq UltraScale+ MPSoC custom platforms. The tool provides sim_ipc_axis_master_v1_0 and sim_ipc_axis_slave_v1_0 IP to add to your platform design. These can be found in the software installation at $XILINX_VIVADO/data/emulation/hw_em/ip_repo.

    The following is an example Tcl script used to add IPC IP to your platform design, which will enable you to inject data traffic into your simulation from an external process written in Python or C++:

    #Update IP Repository path if required
    set_property  ip_repo_paths $XILINX_VIVADO/data/emulation/hw_em/ip_repo [current_project]
    ## Add AXIS Master
    create_bd_cell -type ip -vlnv xilinx.com:ip:sim_ipc_axis_master:1.0 sim_ipc_axis_master_0
    #Change Model Property if required
    set_property -dict [list CONFIG.C_M00_AXIS_TDATA_WIDTH {64}] [get_bd_cells sim_ipc_axis_master_0]
    
    ##Add AXIS Slave
    create_bd_cell -type ip -vlnv xilinx.com:ip:sim_ipc_axis_slave:1.0 sim_ipc_axis_slave_0
    #Change Model Property if required
    set_property -dict [list CONFIG.C_S00_AXIS_TDATA_WIDTH {64}] [get_bd_cells sim_ipc_axis_slave_0]

Writing Traffic Generators in Python

You must also include a traffic generator process while simulating your application to generate data traffic on the I/O traffic generators, or to capture output data from the emulation process. The Xilinx provided Python or C++ library can be used to create the traffic generator code as described below.

  • For Python, set $PYTHONPATH on the command terminal:
    setenv PYTHONPATH $XILINX_VIVADO/data/emulation/hw_em/lib/python:\
    $XILINX_VIVADO/data/python/xtlm_ipc/xtlm_ipc_v1_0
  • Sample Python code to connect with the gt_master instance would look like the following:
    Blocking Send
    from xilinx_xtlm import ipc_axis_master_util
    from xilinx_xtlm import xtlm_ipc
    import struct
     
    import binascii
      
    #Instantiating AXI Master Utilities
    master_util = ipc_axis_master_util("gt_master")
      
    #Create payload
    payload = xtlm_ipc.axi_stream_packet()
    payload.data = "BINARY_DATA" # One way of getting "BINARY_DATA" from integer can be like payload.data = bytes(bytearray(struct.pack("i", int_number))) More info @ https://docs.python.org/3/library/struct.html
    payload.tlast = True #AXI Stream Fields
    #Optional AXI Stream Parameters
    payload.tuser = "OPTIONAL_BINARY_DATA"
    payload.tkeep = "OPTIONAL_BINARY_DATA"
      
    #Send Transaction
    master_util.b_transport(payload)
    master_util.disconnect() #Disconnect connection between Python & Emulation
  • Sample Python code to connect with the gt_slave instance would look like the following:
    Blocking Receive
    from xilinx_xtlm import ipc_axis_slave_util
    from xilinx_xtlm import xtlm_ipc
      
    #Instantiating AXI Slave Utilities
    slave_util = ipc_axis_slave_util("gt_slave")
      
      
    #Sample payload (Blocking Call)
    payload = slave_util.sample_transaction()
    slave_util.disconnect() #Disconnect connection between Python & Emulation

Writing Traffic Generators in C++

  • For C++ the API is available at $XILINX_VIVADO/data/cpp/xtlm_ipc/xtlm_ipc_v1_0/src/. The C++ API provides both blocking and non-blocking function support. The following snippets show the usage.
    Tip: A sample Makefile is also available to generate the executable.
  • Blocking send:
    #include "xtlm_ipc.h" //Include file
     
    void send_packets()
    {
        //! Instantiate IPC socket with name matching in IPI diagram...
        xtlm_ipc::axis_initiator_socket_util<xtlm_ipc::BLOCKING> socket_util("gt_master");
     
        const unsigned int NUM_TRANSACTIONS = 8;
        xtlm_ipc::axi_stream_packet packet;
     
        std::cout << "Sending " << NUM_TRANSACTIONS << " Packets..." <<std::endl;
        for(int i = 0; i < NUM_TRANSACTIONS; i++) {
            xtlm_ipc::axi_stream_packet packet;
            // generate_data() is your custom code to generate traffic 
            std::vector<char> data = generate_data();
            //! Set packet attributes...
            packet.set_data(data.data(), data.size());
            packet.set_data_length(data.size());
            packet.set_tlast(1);
            //Additional AXIS attributes can be set if required
            socket_util.transport(packet); //Blocking transport API to send the transaction
        }
    }
  • Blocking receive:
    #include "xtlm_ipc.h"
     
    void receive_packets()
    {
        //! Instantiate IPC socket with name matching in IPI diagram...
        xtlm_ipc::axis_target_socket_util<xtlm_ipc::BLOCKING> socket_util("gt_slave");
     
        const unsigned int NUM_TRANSACTIONS = 8;
        unsigned int num_received = 0;
        xtlm_ipc::axi_stream_packet packet;
     
        std::cout << "Receiving " << NUM_TRANSACTIONS << " packets..." <<std::endl;
        while(num_received < NUM_TRANSACTIONS) {
            socket_util.sample_transaction(packet); //API to sample the transaction
            //Process the packet as per requirement.
            num_received += 1;
        }
    }
  • Non-Blocking send:
    #include <algorithm>    // std::generate
    #include "xtlm_ipc.h"
     
    //A sample implementation of generating random data.
    xtlm_ipc::axi_stream_packet generate_packet()
    {
        xtlm_ipc::axi_stream_packet packet;
        // generate_data() is your custom code to generate traffic
        std::vector<char> data = generate_data();
     
        //! Set packet attributes...
        packet.set_data(data.data(), data.size());
        packet.set_data_length(data.size());
        packet.set_tlast(1);
        //packet.set_tlast(std::rand()%2);
        //! Option to set tuser tkeep optional attributes...
     
        return packet;
    }
     
    void send_packets()
    {
        //! Instantiate IPC socket with name matching in IPI diagram...
        xtlm_ipc::axis_initiator_socket_util<xtlm_ipc::NON_BLOCKING> socket_util("gt_master"); 
        // Instantiate Non Blocking specialization
     
        const unsigned int NUM_TRANSACTIONS = 8;
        xtlm_ipc::axi_stream_packet packet;
     
        std::cout << "Sending " << NUM_TRANSACTIONS << " Packets..." <<std::endl;
        for(int i = 0; i < NUM_TRANSACTIONS; i++) {
            packet = generate_packet(); // Or user's test patter / live data etc.
            socket_util.transport(packet);
        }
    }
  • Non-Blocking receive:
    #include <unistd.h>
    #include "xtlm_ipc.h"
     
    void receive_packets()
    {
        //! Instantiate IPC socket with name matching in IPI diagram...
        xtlm_ipc::axis_target_socket_util<xtlm_ipc::NON_BLOCKING> socket_util("gt_slave");
     
        const unsigned int NUM_TRANSACTIONS = 8;
        unsigned int num_received = 0, num_outstanding = 0;
        xtlm_ipc::axi_stream_packet packet;
     
        std::cout << "Receiving " << NUM_TRANSACTIONS << " packets..." <<std::endl;
        while(num_received < NUM_TRANSACTIONS) {
            num_outstanding = socket_util.get_num_transactions();
            num_received += num_outstanding;
             
            if(num_outstanding != 0) {
                std::cout << "Outstanding packets = "<< num_outstanding <<std::endl;
                for(int i = 0; i < num_outstanding; i++) {
                    socket_util.sample_transaction(packet);
                    print(packet);
                }
            }
            usleep(100000); //As transaction is non-blocking we would like to give some delay between consecutive samplings
        }
    }
  • The following is an example Makefile for the blocking receive above:
    GCC=/usr/bin/g++
    IPC_XTLM=$(XILINX_VIVADO)/data/cpp/xtlm_ipc/xtlm_ipc_v1_0/src/
    PROTO_PATH=$(XILINX_VIVADO)/data/simmodels/xsim/2020.2/lnx64/6.2.0/ext/protobuf/
    BOOST=$(XILINX_VIVADO)/tps/boost_1_64_0/
     
    SRC_FILE=b_receive.cpp
    .PHONY: run all
     
    default: all
     
    all : b_receive
     
    b_receive: $(SRC_FILE)
        $(GCC)   $(SRC_FILE) $(IPC_XTLM)xtlm_ipc.pb.cc -I$(IPC_XTLM)/ -I$(PROTO_PATH)/include/ -L$(PROTO_PATH) -lprotobuf -o $@ -lpthread -I$(BOOST)/
    

Running Traffic Generators

After generating an external process binary as shown above using the library found in $XILINX_VIVADO/data/cpp/xtlm_ipc/xtlm_ipc_v1_0/src/, you can run the emulation using the following steps:

  1. Launch the Vitis hardware emulation or Vivado simulation using the standard process and wait for the simulation to start.
  2. From another terminal(s), launch the external process such as Python or C++.