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
andsim_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:
- Launch the Vitis hardware emulation or Vivado simulation using the standard process and wait for the simulation to start.
- From another terminal(s), launch the external process such as Python or C++.