本篇中使用HLS进行IP核生成,使用AXI4Stream接口,完成50个int类型数的输入输出。IP核内部将输入的50个数与5相加后输出。
目录
1.1 - HLS工程建立
1.2 - 接口定义
1.3 - HLS优化
1.4 - 联合仿真查看波形
1.5 - IP导出
2 - VIVADO工程
3 - SDK工程测试
新建HLS工程,添加TOP函数。这里A数组为输入数组,B数组为输出数组。
TOP函数表示,在这个IP核中,将A数组的每个成员加5后赋值给B数组。
#include "ap_axi_sdata.h"
void example(ap_axis<32,1,1,1> A[50], ap_axis<32,1,1,1> B[50]){
int i;
add_loop:
for(i = 0; i < 50; i++){
B[i].data = A[i].data.to_int() + 5;
B[i].keep = A[i].keep;
B[i].strb = A[i].strb;
B[i].user = A[i].user;
B[i].last = A[i].last;
B[i].id = A[i].id;
B[i].dest = A[i].dest;
}
}
这里输入A和输出B都使用了 ap_axis 数据类型。在这里的主要目的是为了后续定义为AXI4Stream接口后,显式的定义好TLAST信号,若只使用 int 类型来进行定义,则会导致后续定义为AXI4Stream接口后没有TLAST信号。
添加完TOP函数后,为了让HLS综合能够良好的评估TOP函数综合后的效果,Test Bench 是必不可少的,我们在Test Bench添加以下代码。
#include
#include "ap_axi_sdata.h"
void example(ap_axis<32,1,1,1> A[50], ap_axis<32,1,1,1> B[50]);
int main()
{
ap_axis<32,1,1,1> A[50];
ap_axis<32,1,1,1> B[50];
for(int i=0; i < 50; i++){
A[i].data = i;
A[i].keep = 1;
A[i].strb = 1;
A[i].user = 1;
A[i].last = 0;
A[i].id = 0;
A[i].dest = 1;
}
example(A,B);
for(int i=0; i < 50; i++){
if(B[i].data.to_int() != (i+5)){
printf("ERROR: HW and SW results mismatch\n");
return 1;
}
}
printf("Success: HW and SW results match\n");
return 0;
}
testbench代码比较简单,首先其定义了三个数组A、B、C,对数组A的每个元素的data成员填充了测试数据 0~50。
接着,其调用我们TOP函数 “example” ,将数组A作为输入,数组B接收TOP函数的输出。
我们还需要进行比较,看是否TOP函数正确的完成了我们的算法,这里我们直接用软件将数组C填充为数组A对应成员加5,然后和通过TOP函数输出的数组B进行比较,查看TOP函数是否正确的完成了计算。
我们期望的是,输入数组A和输入数组B在PL上能通过AXI4Stream接口进行输入输出,控制端口通过AXI4-Lite总线进行控制,这里我们需要对接口进行定义约束。
切换回 Source->example.cpp 我们的TOP函数上。
打开Directive指令侧栏,开始接口定义。
我们首先定义控制端口是axilite总线,双击Directive侧栏中的函数名,选择 INTERFACE,模式选择为 s_axilite
接着同样的方式定义输入数组A,选择INTERFACE ,模式选择为 axis,对数组B也进行同A一样的操作。
name可选项可以设置你想要的端口名称,这里给A设为"INPUT_ARR" B设为"OUTPUT_ARR"
我们运行综合,查看报告
从报告可以看出,我们当前的设计,其输入至输出的延迟latency有151个时钟周期,前次输入输出完毕到下次输入输出的间隔则是151个时钟周期。
我们看接口方面,IP的控制信号被定义为了AXI-Lite类型,而两个数据流A和B则被定义为了AXI4-Stream类型。
这里如果没有像前文所述使用 ap_axis 类型进行输入输出数组的定义,则会在综合后,输入输出的axis通道丢失部分信号,从而导致在PL上使用时无法成功的通过TLAST信号线向DMA发送信号。
打开Directive指令侧栏,我们进行优化。
首先对 add_loop进行优化,这里我们可以使用 UNROOL 解循环优化以及 PIPELINE 流水线优化
对于HLS不太了解的可以先看一下这个视频:跟Xilinx SAE 学HLS系列视频讲座-高亚军
UNROOL 解循环优化可以看做使用 x 倍的空间生成能够同时进行操作的功能块,同时对 x 个连续的数组成员进行操作
PIPELINE 则是将原本一条流水线复制多条进行同时处理。
首先对数组进行 UNROOL 解循环优化,双击 add_loop,如下进行设置
接着添加PIPELINE优化
添加UNROOL和PIPELINE优化后如图所示
我们再运行一次综合,查看结果
可以很明显的看到我们的延迟和间隔时间降低了,但是取而代之的,我们的资源占用提升了,我们查看一下报告比较
我们点击 Run-C/RTL cosimulation 运行联合仿真
这里 Dump Trace 选择 all 以查看全部端口以及信号的波形。
仿真完毕后我们可以查看联合仿真报告
我们点击 Open Wave Viewer ,将会在新打开的vivado中查看仿真波形
点击OK,等待导出完成。导出完成后,在当前的solution解决方案下会多出一个 impl文件夹,其下的 ip 文件夹内的压缩包便是我们的IP核。
由于我们使用了AXI-Lite总线,伴随着我们IP核生成的,还有 drivers 文件夹下的相关驱动代码。
我们将生成的IP放到事先准备好的VIVADO工程目录下,添加这个IP
这里我们使用这个IP搭建了一个简单的测试环境。使用的是PS -> AXIDMA -> HLS_IP -> AXIDMA -> PS环路
AXI-DMA这里关闭了SG模式,将吞吐调到最大。
ZYNQ 的 AXI_HP0 如下设置
综合VIVADO工程,生成bitstream比特流文件,导出到HDL文件,加载SDK。
打开SDK后,我们先新建BSP,File -> New -> Board Support Package
新建好BSP后,我们看打开的mss文件,直接载入axidma的环路测试样例,在其上稍作修改进行我们的IP测试
在打开的样例程序中,我们主要修改以下几个部分
/***************************** Include Files *********************************/
#include "xaxidma.h"
#include "xparameters.h"
#include "xdebug.h"
#include "xexample.h"
#if defined(XPAR_UARTNS550_0_BASEADDR)
#include "xuartns550_l.h" /* to use uartns550 */
#endif
/******************** Constant Definitions **********************************/
/*
* Device hardware build related constants.
*/
#define DMA_DEV_ID XPAR_AXIDMA_0_DEVICE_ID
#ifdef XPAR_AXI_7SDDR_0_S_AXI_BASEADDR
#define DDR_BASE_ADDR XPAR_AXI_7SDDR_0_S_AXI_BASEADDR
#elif XPAR_MIG7SERIES_0_BASEADDR
#define DDR_BASE_ADDR XPAR_MIG7SERIES_0_BASEADDR
#elif XPAR_MIG_0_BASEADDR
#define DDR_BASE_ADDR XPAR_MIG_0_BASEADDR
#elif XPAR_PSU_DDR_0_S_AXI_BASEADDR
#define DDR_BASE_ADDR XPAR_PSU_DDR_0_S_AXI_BASEADDR
#endif
#ifndef DDR_BASE_ADDR
#warning CHECK FOR THE VALID DDR ADDRESS IN XPARAMETERS.H, \
DEFAULT SET TO 0x01000000
#define MEM_BASE_ADDR 0x01000000
#else
#define MEM_BASE_ADDR (DDR_BASE_ADDR + 0x1000000)
#endif
#define TX_BUFFER_BASE (MEM_BASE_ADDR + 0x00100000)
#define RX_BUFFER_BASE (MEM_BASE_ADDR + 0x00300000)
#define RX_BUFFER_HIGH (MEM_BASE_ADDR + 0x004FFFFF)
#define MAX_PKT_LEN 50
#define NUMBER_OF_TRANSFERS 10
/**************************** Type Definitions *******************************/
/***************** Macros (Inline Functions) Definitions *********************/
/************************** Function Prototypes ******************************/
#if (!defined(DEBUG))
extern void xil_printf(const char *format, ...);
#endif
int XAxiDma_SimplePollExample(u16 DeviceId);
static int CheckData(void);
/************************** Variable Definitions *****************************/
/*
* Device instance definitions
*/
XAxiDma AxiDma;
XExample gExample;
int *TxBufferPtr;
int *RxBufferPtr;
/*****************************************************************************/
/**
* The entry point for this example. It invokes the example function,
* and reports the execution status.
*
* @param None.
*
* @return
* - XST_SUCCESS if example finishes successfully
* - XST_FAILURE if example fails.
*
* @note None.
*
******************************************************************************/
int main()
{
int Status;
xil_printf("\r\n--- Entering main() --- \r\n");
//init HLS IP
int _status = XExample_Initialize(&gExample,XPAR_EXAMPLE_0_DEVICE_ID);
if(_status != XST_SUCCESS)
{
xil_printf("XExample_initialize failed\n");
return XST_FAILURE;
}
//start HLS IP
XExample_EnableAutoRestart(&gExample);
XExample_Start(&gExample);
/* Run the poll example for simple transfer */
Status = XAxiDma_SimplePollExample(DMA_DEV_ID);
if (Status != XST_SUCCESS) {
xil_printf("XAxiDma_SimplePoll Example Failed\r\n");
return XST_FAILURE;
}
xil_printf("Successfully ran XAxiDma_SimplePoll Example\r\n");
xil_printf("--- Exiting main() --- \r\n");
return XST_SUCCESS;
}
#if defined(XPAR_UARTNS550_0_BASEADDR)
/*****************************************************************************/
/*
*
* Uart16550 setup routine, need to set baudrate to 9600, and data bits to 8
*
* @param None.
*
* @return None
*
* @note None.
*
******************************************************************************/
static void Uart550_Setup(void)
{
/* Set the baudrate to be predictable
*/
XUartNs550_SetBaud(XPAR_UARTNS550_0_BASEADDR,
XPAR_XUARTNS550_CLOCK_HZ, 9600);
XUartNs550_SetLineControlReg(XPAR_UARTNS550_0_BASEADDR,
XUN_LCR_8_DATA_BITS);
}
#endif
/*****************************************************************************/
/**
* The example to do the simple transfer through polling. The constant
* NUMBER_OF_TRANSFERS defines how many times a simple transfer is repeated.
*
* @param DeviceId is the Device Id of the XAxiDma instance
*
* @return
* - XST_SUCCESS if example finishes successfully
* - XST_FAILURE if error occurs
*
* @note None
*
*
******************************************************************************/
int XAxiDma_SimplePollExample(u16 DeviceId)
{
XAxiDma_Config *CfgPtr;
int Status;
int Index;
TxBufferPtr = (int *)TX_BUFFER_BASE ;
RxBufferPtr = (int *)RX_BUFFER_BASE;
/* Initialize the XAxiDma device.
*/
CfgPtr = XAxiDma_LookupConfig(DeviceId);
if (!CfgPtr) {
xil_printf("No config found for %d\r\n", DeviceId);
return XST_FAILURE;
}
Status = XAxiDma_CfgInitialize(&AxiDma, CfgPtr);
if (Status != XST_SUCCESS) {
xil_printf("Initialization failed %d\r\n", Status);
return XST_FAILURE;
}
if(XAxiDma_HasSg(&AxiDma)){
xil_printf("Device configured as SG mode \r\n");
return XST_FAILURE;
}
/* Disable interrupts, we use polling mode
*/
XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DEVICE_TO_DMA);
XAxiDma_IntrDisable(&AxiDma, XAXIDMA_IRQ_ALL_MASK,
XAXIDMA_DMA_TO_DEVICE);
//disable the cache
Xil_DCacheDisable();
for(Index = 0; Index < NUMBER_OF_TRANSFERS; Index ++) {
//write to the TxBuffer and RxBuffer default value
for(int i = 0; i < MAX_PKT_LEN; i++)
{
TxBufferPtr[i] = i + Index;
RxBufferPtr[i] = 0;
}
Status = XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) RxBufferPtr,
MAX_PKT_LEN*sizeof(int), XAXIDMA_DEVICE_TO_DMA);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
Status = XAxiDma_SimpleTransfer(&AxiDma,(UINTPTR) TxBufferPtr,
MAX_PKT_LEN*sizeof(int), XAXIDMA_DMA_TO_DEVICE);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
while ((XAxiDma_Busy(&AxiDma,XAXIDMA_DEVICE_TO_DMA)) ||
(XAxiDma_Busy(&AxiDma,XAXIDMA_DMA_TO_DEVICE))) {
/* Wait */
}
Status = CheckData();
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
}
/* Test finishes successfully
*/
return XST_SUCCESS;
}
/*****************************************************************************/
/*
*
* This function checks data buffer after the DMA transfer is finished.
*
* @param None
*
* @return
* - XST_SUCCESS if validation is successful.
* - XST_FAILURE otherwise.
*
* @note None.
*
******************************************************************************/
static int CheckData(void)
{
xil_printf("\nThe TxData is\n");
for(int _index=0;_index < MAX_PKT_LEN;_index++)
{
xil_printf("%d ",TxBufferPtr[_index]);
}
xil_printf("\nThe RxData is\n");
for(int _index=0;_index < MAX_PKT_LEN;_index++)
{
xil_printf("%d ",RxBufferPtr[_index]);
}
xil_printf("\n\n");
return XST_SUCCESS;
}
不要忘记,我们的IP核需要通过AXI-Lite总线对其进行start触发信号操作。由于生成IP核时同样的生成了相关的驱动代码,这里我们直接调用驱动代码即可。
打开SDK Terminal,运行Debug,我们可以看到串口输出
可见,我们的初始的数据,在通过AXIDMA传输到我们使用HLS生成的IP核进行运算,传输回来后各个项已经被自增了5。
这个HLS IP虽然被调用多次,但是由于之前开启了自动开始功能,故不需要每次传输前发送start指令,这一技巧可以参考xilinx官方HLS手册 UG902