概述
同时(并行)启动仿真器和流量生成器 (TG) 即可使用外部流量生成器进行仿真。这些 TG 可以 Python 或 C++ 来编写,并且可使用这两种语言的多线程功能。
以 Python 编写流量生成器
# Mandatory
import os, sys
import multiprocessing as mp
import threading
import struct
from xilinx_xtlm import ipc_axis_master_util
from xilinx_xtlm import ipc_axis_slave_util
from xilinx_xtlm import xtlm_ipc
# Optionnal, just for ease of use
import numpy as np
import logging
mm2s_util = ipc_axis_master_util("DataIn1")
self.s2mm_util = ipc_axis_slave_util("DataOut1")
处理端口的函数(以下示例中的 mm2s
)应作为独立进程来启动:tx = mp.Process(target=mm2s)
tx.start()
函数结束时,应停止该进程:tx.join()
这是一个阻塞函数,它会等待函数结束后再停止进程。pipes
。父进程会声明管道,通信则是使用 send
和 recv
函数来操作的:parent0, child0 = mp.Pipe()
child0.send(Tx_data)
Rx_data = parent0.recv()
self.s2mm_util = ipc_axis_slave_util("DataOut1")
payload
变量实际采用的是包含多个不同字段的结构:-
data_length
是数据的字节数。 -
data
是数据本身。 -
tlast
是 TLAST 标志,设为 true 或 false。
payload = xtlm_ipc.axi_stream_packet()
随后,设置不同字段的值,并使用 b_transpor
方法将其发送至 AI 引擎阵列:mm2s_util.b_transport(payload)
通过流量生成器以 Python 来格式化数据
要对 AXI4-Stream 传输事务进行仿真,AXI Traffic Generator 需将有效载荷数据分割为相应大小的突发。要发送 PLIO 宽度为 32 位(4 字节)的 128 字节数据,就需要 128 字节/4 字节 = 32 项 AXI4-Stream 传输事务。字节阵列与 AXI 传输事务之间的转换可由 Python 来处理。
Python struct
库可提供相应机制以在 Python 数据类型与 C 语言数据类型之间进行转换。尤其是,struct.pack
和 struct.unpack
函数可根据格式字符串实参对字节阵列进行打包和解包。下表显示了常用 C 语言数据类型和 PLIO 宽度的格式字符串。
如需了解更多信息,请访问:https://docs.python.org/3/library/struct.html
数据类型 | PLIO 宽度 | Python 代码片段 |
---|---|---|
cfloat | PLIO32 | 不适用 |
PLIO64 |
rVec =
np.real(data)
|
|
PLIO128 | ||
cint16 | PLIO32 |
rVec =
np.real(data).astype(np.int16)
|
PLIO64 | ||
PLIO128 | ||
int8 | PLIO32 |
intvec =
np.real(data).astype(np.int8)
|
PLIO64 | ||
PLIO128 | ||
int32 | PLIO32 |
intvec =
np.real(data).astype(np.int32)
|
PLIO64 | ||
PLIO128 |
以 C++ 编写流量生成器
# Libraries directories
PROTO_PATH=$(XILINX_VIVADO)/data/simmodels/xsim/2022.1/lnx64/6.2.0/ext/protobuf/
IPC_XTLM= $(XILINX_VIVADO)/data/emulation/ip_utils/xtlm_ipc/xtlm_ipc_v1_0/cpp/src/
IPC_XTLM_INC= $(XILINX_VIVADO)/data/emulation/ip_utils/xtlm_ipc/xtlm_ipc_v1_0/cpp/inc/
LOCAL_IPC= $(IPC_XTLM)../
LD_LIBRARY_PATH:=$(XILINX_VIVADO)/data/simmodels/xsim/2022.1/lnx64/6.2.0/ext/protobuf/:$(XILINX_VIVADO)/lib/lnx64.o/Default:$(XILINX_VIVADO)/lib/lnx64.o/:$(LD_LIBRARY_PATH)
# Kernel directories
PLKERNELS_DIR := ../../pl_kernels
PLKERNELS := $(PLKERNELS_DIR)/polar_clip.cpp
PLHEADERS := $(PLKERNELS_DIR)/polar_clip.hpp $(PLKERNELS_DIR)/s2mm.hpp $(PLKERNELS_DIR)/mm2s.hpp
# XTLM source files
IPC_SRC := $(LOCAL_IPC)/src/axis/*.cpp $(LOCAL_IPC)/src/common/*.cpp $(LOCAL_IPC)/src/common/*.cc
# Compiler/linker flags
INC_FLAGS := -I$(LOCAL_IPC)/inc -I$(LOCAL_IPC)/inc/axis/ -I$(LOCAL_IPC)/inc/common/ -I$(PROTO_PATH)/include/ -I$(PLKERNELS_DIR) -I$(XILINX_HLS)/include
LIB_FLAGS := -L$(PROTO_PATH)/ -lprotobuf -L$(XILINX_VIVADO)/lib/lnx64.o/ -lrdizlib -L$(GCC)/../../lib64/ -lstdc++ -lpthread
# Compilation
compile: main.cpp $(PLHEADERS) $(PLKERNELS)
$(GCC) -g main.cpp $(PLKERNELS) $(IPC_SRC) $(INC_FLAGS) $(LIB_FLAGS) -o chain
适用于处理这些库的头文件包括:# For the traffic generator
#include "xtlm_ipc.h"
#include <thread>
# Transmitter Traffic Generator
using b_init_socket = xtlm_ipc::axis_initiator_socket_util<xtlm_ipc::BLOCKING>;
# Receiver Traffic Generator
using b_targ_socket = xtlm_ipc::axis_target_socket_util<xtlm_ipc::BLOCKING>;
class mm2s
{
std::thread m_thread;
std::unique_ptr<b_init_socket> m_socket_ptr;
int count;
void sock_data_handler()
{
m_socket_ptr = std::make_unique<b_init_socket>(m_sock_name);
std::vector<char> data_to_send;
while (count<512)
{
// Create a data to send ot the AI Engine Arra (vector of bytes)
data_to_send = ...;
m_socket_ptr->transport(data_to_send,count%128==127?true:false); // transport(data, tlast), 128 sample frame
count++;
}
}
protected :
// Name of the socket
const std::string m_sock_name;
public:
mm2s(const std::string sock_name) :
m_sock_name(sock_name), m_socket_ptr(nullptr),count(0)
{}
void run()
{
m_thread = std::thread(&mm2s::sock_data_handler, this);
}
// This function allows the user to check for the end of the transmission
int dataTransferred()
{
return(count);
}
// The destructor ends the thread
virtual ~mm2s()
{
std::cout << this->m_sock_name << " before join " << std::endl;
if(m_thread.joinable())
m_thread.join();
std::cout << this->m_sock_name << " after join " << std::endl;
}
};
main
函数很简单,它仅用于启动流量生成器的各组件,同时在这些组件之间插入部分延迟,以使系统能够轻松完成初始化:int main(int argc, char *argv[])
{
mm2s chain_1_mm2s("DataIn1");
polar_clip chain_1_pc ("clip_in", "clip_out");
s2mm_chain_1_s2mm("DataOut1");
using namespace std::chrono_literals;
chain_1_mm2s.run();
std::cout << "Started mm2s " << std::endl;
std::this_thread::sleep_for(500ms);
chain_1_pc.run();
std::cout << "Started polar_clip " << std::endl;
std::this_thread::sleep_for(400ms);
chain_1_s2mm.run();
std::cout << "Started s2mm " << std::endl;
# Waits for the end of the simulation (1024 samples received from S2MM block)
while(chain_1_s2mm.dataTransferred()!=1024)
{
// Waits 2s before retesting
std::this_thread::sleep_for(2s);
}
return(0)
}
C++ 流量生成器的有趣之处在于,HLS 内核一旦创建完成,即可立即使用和测试,无需在 .xo
文件内进行综合。这样您无需重新创建 .xclbin
文件即可给自己的仿真添加更多真实性和灵活性。