环境
操作系统 Windows11
软件:Vivado 2023.1 Vitis HLS 2023.1 Vitis 2023.1
设备:xczu4ev-sfvc784-1-i
时间:2023-10
简介
好像做这样的工作的博客很少,记录一下踩过的坑,
文末参考文献可供参考
DOA算法
DOA(波达方向估计)算法是使用接收信号,对所接收到的信号数据进行一系列处理,从而估计信号源方向角度的算法。本文中将利用xczu4ev-sfvc784-1-i来将一个DOA算法硬件实现。
开发流程
阶段 | 算法C语言实现 | 连线IP集成 | 嵌入式开发驱动 |
工具 | vitis HLS | vivado | vitis |
输入 | 算法的C语言 | RTL IP核 | HardWare |
输出 | RTL IP核 | HardWare | Program |
xczu4ev-sfvc784-1-i
zynq = arm(PS、CPU)+FPGA(PL)
含有丰富的资源和接口。PS端是一个小型的电脑,PL端FPGA。本文中将会使用PL端将算法硬件实现,在PS端用C++语言进行嵌入式开发,用一段程序来驱动控制PL端的算法运行。
![](https://img-blog.csdnimg.cn/b61d305e944e4b3f8388380d3dc515dc.png)
Vitis HLS
HLS可以让用户从硬件描述语言的细节中摆脱出来,专注于算法层面的实现而无需关注硬件的细节行为。vitis HLS将会帮助用户将C语言翻译位对应的Verilog代码,并生成对应的IP核。使用vitis开发的好处:开发更容易、效率更高。可以指定模块(topfunction)的IO接口。例如指定IO接口位AXI接口,这样做可以使用户设计的模块能够更灵活的与PS端进行对接。
写了三个文件 main.h main.cpp tb_main.cpp
头文件:
#ifndef _main_h_
#define _main_h_
#define dim 4
#define spa 180+1
void get_result(float R[dim][dim], float result[spa]);
#endif
在头文件中定义 dim 维度,矩阵的维度也即所用的阵元数。定义扫角的角度数 spa
声明函数get_result
注意:在头文件中不可以定义变量,但可以声明。写头文件主要是用在仿真中,在仿真时,需要include 头文件,才能调用get_result函数
cpp文件
cpp文件中,首先定义和实现了一些函数,这些函数都是几乎做矩阵运算的。然后利用定义好的函数来完成算法的功能。
其中使用较多的是指针。由于C++无法返回一个数组(矩阵)。所以对矩阵的处理需要转换位数组指针的处理。函数的返回类型也大多数都是void(无返回值),选择将输出使用操作指针的方式来给出。指针将会指出一个变量的地址,本算法中的数组的长度是确定的(dim是已知的),所以只需要将数组的第一个元素的指针传入函数中,函数内部对这部分地址做操作赋值即可。实验证明HLS支持这种写法,HLS将能够自动分辨输入和输出(等号左边是输出,右边是输入,两边都有是输入也是输出)。
directive
当打开某个文件的时候可以在右侧添加directive,来指导HLS如何去综合接口、优化for循环。此处对return 输入和输出做了directive 使用axilite接口。(了解一下block level 协议和port level 协议)。directive 和 progam 是用来规定HLS综合方案的。
tb文件
#include "main.h"
int main()
{
float R[4][4]={16,2,3,13,5,11,10,8,9,7,6,12,4,14,15,1};
float result[181]={0};
get_result(R,&result[0]);
return 0;
}
tb文件中初始化了输入和输出,并调用了get_result函数。事实上,这里应当写一段代码来检验硬件计算结果是否符合预期,不过我偷懒了。如果符合预期则返回0,否则返回1。
![](https://img-blog.csdnimg.cn/c80f2ce54368416784fd2302e2affb9f.png)
tb文件写好后可以进行仿真。C仿真:验证C语言是否实现了算法。C综合:综合后可以产看资源占用情况,计算所用时间、接口综合结果等,是优化计算的参考。Cco仿真可以在vivado中观看波形,要做如下选择,才能调用vivado。
![](https://img-blog.csdnimg.cn/321245c0a18e43f58b7b801d9ab46a7b.png)
导出RTL IP
在导出(综合)之前要选则topfunction, topfunction将与PS端交互。导出IP到一个目录(选择使用zip形式),将会到处一个压缩包,其实就是一个IP,它实现了我们的算法。接下来就利用这个IP在vivado中继续开发。
Vivado
导入之前的IP
1.creat block disign 2. 将之前的IP添加到IPcatalog 3.将IP添加到 block disign
连线
需要导入zynq IP,导入后需要re customize 让zynq配置好一些内存接口等,之后在嵌入式开发中才能使用。这一步看zynq设备提供的手册
address editor中定义了我们IP的数据存放读写的地址,这里的地址将与嵌入式开发(第三步)中的一些定义对应起来。
在电路图中添加一个interconnect 转换器IP,它可以连接主机(PS)和从机(PL)然后运行自动连线,就连线好了。
导出hardware启动vitis
导出hardware后等第三步使用。然后启动vitis 。如果没有装(大概率),点help,add tool device,然后装vitis。
Vitis
新建appliaction
新建一个应用,然后 选择新建一个平台,这里选择我们上一步导出的硬件xsa文件。
之前在custom zynq IP 后这边直接使用串口调试,hello world 中print即可向串口发送数据。
控制逻辑
都ctrl_hs 信号来决定发送数据,这里非常单纯只是实现了计算一次,有待改进。
/******************************************************************************
*
* Copyright (C) 2009 - 2014 Xilinx, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* Use of the Software is limited solely to applications:
* (a) running on a Xilinx device, or
* (b) that interact with a Xilinx device through a bus or interconnect.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* XILINX BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Except as contained in this notice, the name of the Xilinx shall not be used
* in advertising or otherwise to promote the sale, use or other dealings in
* this Software without prior written authorization from Xilinx.
*
******************************************************************************/
/*
* helloworld.c: simple test application
*
* This application configures UART 16550 to baud rate 9600.
* PS7 UART (Zynq) is not initialized by this application, since
* bootrom/bsp configures it to baud rate 115200
*
* ------------------------------------------------
* | UART TYPE BAUD RATE |
* ------------------------------------------------
* uartns550 9600
* uartlite Configurable only in HW design
* ps7_uart 115200 (configured by bootrom/bsp)
*/
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xget_result.h"
float R[16];
float result[181];
XGet_result XGet_resultins;
XGet_result init_function_drive()
{
XGet_result XGet_resultins;
XGet_result_Initialize(&XGet_resultins, XPAR_GET_RESULT_0_DEVICE_ID);
return XGet_resultins;
};
int set_start()
{
XGet_result_Start(&XGet_resultins);
return 1;
};
int get_ready()
{
return XGet_result_IsReady(&XGet_resultins);
};
void write_R()
{
//u32 a;
XGet_result_Write_R_Words(&XGet_resultins, 0, (word_type*)&R[0], 16);
};
void read_result()
{
XGet_result_Read_result_Words(&XGet_resultins, 0, (word_type*)&result[0], 181);
};
int main()
{
init_platform();
print("Hello World\n\r");
print("Successfully ran Hello World application");
/****************************************/
R[0]=(float)1.00040749037346 ;
R[1]=(float)-0.997441372504932 ;
R[2]=(float)0.992203566526157 ;
R[3]=(float)-0.984706001621041 ;
R[4]=(float)-0.997441372504932 ;
R[5]=(float)0.996788487809955 ;
R[6]=(float)-0.993865401959696 ;
R[7]=(float)0.988678772327265 ;
R[8]=(float)0.992203566526157 ;
R[9]=(float)-0.993865401959696 ;
R[10]=(float)0.993263693611063 ;
R[11]=(float)-0.990399811880303 ;
R[12]=(float)-0.984706001621041 ;
R[13]=(float)0.988678772327265 ;
R[14]=(float)-0.990399811880303 ;
R[15]=(float)0.989865200586027 ;
XGet_resultins=init_function_drive();
print("1>>\n\r");
write_R();
set_start();print("start>>\n\r");
print("2>>\n\r");
float Rr[16];
XGet_result_Read_R_Words(&XGet_resultins, 0,(word_type*)& Rr[0],16);
print("**********\n\r");
for(int i=0;i<16;i++)
{
char fchars[10]="123456789";//
sprintf (fchars, "%f",R[i]);
print((char*)fchars);
print(", ");
};
print("**********\n\r");
int ready=0;
print("4>>\n\r");
while(ready==0)
{
ready=XGet_result_IsDone(&XGet_resultins);
};
/*
for(int i=0;i<10000;i++)
{
i=i+1;
i=i-1;
};
*/
//等一下再去读数据
print("5>>\n\r");
read_result();
print("6>>\n\r");
for(int i=0;i<100;i++)
{
char fchars[10]="1234567890";//
sprintf (fchars, "%f", result[i]);
print((char*)fchars);
print(", ");
}
for(int i=100;i<181;i++)
{
char fchars[10]="1234567890";//
sprintf (fchars, "%f", result[i]);
print((char*)fchars);
print(", ");
}
print("7>>\n\r");
/******************************************/
cleanup_platform();
return 0;
}
生成的驱动
后面会解释
design flow :
HLS -> vivado -> vitis
c function -> IP RTL hardware -> PSPLcdrive -> application -> program
algorithm IP integrate interface data ctrl run
//宏定义 在parameter中
//* Definitions for driver GET_RESULT
#define XPAR_XGET_RESULT_NUM_INSTANCES 1
//* Definitions for peripheral GET_RESULT_0
#define XPAR_GET_RESULT_0_DEVICE_ID 0
#define XPAR_GET_RESULT_0_S_AXI_CONTROL_BASEADDR 0x80000000
#define XPAR_GET_RESULT_0_S_AXI_CONTROL_HIGHADDR 0x8000FFFF
//* Canonical definitions for peripheral GET_RESULT_0
#define XPAR_XGET_RESULT_0_DEVICE_ID XPAR_GET_RESULT_0_DEVICE_ID
#define XPAR_XGET_RESULT_0_S_AXI_CONTROL_BASEADDR 0x80000000
#define XPAR_XGET_RESULT_0_S_AXI_CONTROL_HIGHADDR 0x8000FFFF
//初始化配置
int XGet_result_Initialize(XGet_result *InstancePtr, u16 DeviceId);
XGet_result_Config* XGet_result_LookupConfig(u16 DeviceId);
int XGet_result_CfgInitialize(XGet_result *InstancePtr, XGet_result_Config *ConfigPtr);
//读写控制方法:在hw function_name.h中
//control
void XGet_result_Start(XGet_result *InstancePtr);
u32 XGet_result_IsDone(XGet_result *InstancePtr);
u32 XGet_result_IsIdle(XGet_result *InstancePtr);
u32 XGet_result_IsReady(XGet_result *InstancePtr);
void XGet_result_Continue(XGet_result *InstancePtr);
void XGet_result_EnableAutoRestart(XGet_result *InstancePtr);
void XGet_result_DisableAutoRestart(XGet_result *InstancePtr);
//data
R:
u32 XGet_result_Get_R_BaseAddress(XGet_result *InstancePtr);
u32 XGet_result_Get_R_HighAddress(XGet_result *InstancePtr);
u32 XGet_result_Get_R_TotalBytes(XGet_result *InstancePtr);
u32 XGet_result_Get_R_BitWidth(XGet_result *InstancePtr);
u32 XGet_result_Get_R_Depth(XGet_result *InstancePtr);
u32 XGet_result_Write_R_Words(XGet_result *InstancePtr, int offset, word_type *data, int length);//***
u32 XGet_result_Read_R_Words(XGet_result *InstancePtr, int offset, word_type *data, int length);
u32 XGet_result_Write_R_Bytes(XGet_result *InstancePtr, int offset, char *data, int length);
u32 XGet_result_Read_R_Bytes(XGet_result *InstancePtr, int offset, char *data, int length);
result:
u32 XGet_result_Get_result_BaseAddress(XGet_result *InstancePtr);
u32 XGet_result_Get_result_HighAddress(XGet_result *InstancePtr);
u32 XGet_result_Get_result_TotalBytes(XGet_result *InstancePtr);
u32 XGet_result_Get_result_BitWidth(XGet_result *InstancePtr);
u32 XGet_result_Get_result_Depth(XGet_result *InstancePtr);
u32 XGet_result_Write_result_Words(XGet_result *InstancePtr, int offset, word_type *data, int length);
u32 XGet_result_Read_result_Words(XGet_result *InstancePtr, int offset, word_type *data, int length);//***
u32 XGet_result_Write_result_Bytes(XGet_result *InstancePtr, int offset, char *data, int length);
u32 XGet_result_Read_result_Bytes(XGet_result *InstancePtr, int offset, char *data, int length);
void XGet_result_InterruptGlobalEnable(XGet_result *InstancePtr);
void XGet_result_InterruptGlobalDisable(XGet_result *InstancePtr);
void XGet_result_InterruptEnable(XGet_result *InstancePtr, u32 Mask);
void XGet_result_InterruptDisable(XGet_result *InstancePtr, u32 Mask);
void XGet_result_InterruptClear(XGet_result *InstancePtr, u32 Mask);
u32 XGet_result_InterruptGetEnabled(XGet_result *InstancePtr);
u32 XGet_result_InterruptGetStatus(XGet_result *InstancePtr);
//硬件地址:在function_name_hw.h中
// ==============================================================
// Vitis HLS - High-Level Synthesis from C, C++ and OpenCL v2023.1 (64-bit)
// Tool Version Limit: 2023.05
// Copyright 1986-2022 Xilinx, Inc. All Rights Reserved.
// Copyright 2022-2023 Advanced Micro Devices, Inc. All Rights Reserved.
//
// ==============================================================
// control
// 0x000 : Control signals
// bit 0 - ap_start (Read/Write/COH)
// bit 1 - ap_done (Read)
// bit 2 - ap_idle (Read)
// bit 3 - ap_ready (Read/COR)
// bit 4 - ap_continue (Read/Write/SC)
// bit 7 - auto_restart (Read/Write)
// bit 9 - interrupt (Read)
// others - reserved
// 0x004 : Global Interrupt Enable Register
// bit 0 - Global Interrupt Enable (Read/Write)
// others - reserved
// 0x008 : IP Interrupt Enable Register (Read/Write)
// bit 0 - enable ap_done interrupt (Read/Write)
// bit 1 - enable ap_ready interrupt (Read/Write)
// others - reserved
// 0x00c : IP Interrupt Status Register (Read/TOW)
// bit 0 - ap_done (Read/TOW)
// bit 1 - ap_ready (Read/TOW)
// others - reserved
// 0x040 ~
// 0x07f : Memory 'R' (16 * 32b)
// Word n : bit [31:0] - R[n]
// 0x400 ~
// 0x7ff : Memory 'result' (181 * 32b)
// Word n : bit [31:0] - result[n]
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)
#define XGET_RESULT_CONTROL_ADDR_AP_CTRL 0x000
#define XGET_RESULT_CONTROL_ADDR_GIE 0x004
#define XGET_RESULT_CONTROL_ADDR_IER 0x008
#define XGET_RESULT_CONTROL_ADDR_ISR 0x00c
#define XGET_RESULT_CONTROL_ADDR_R_BASE 0x040
#define XGET_RESULT_CONTROL_ADDR_R_HIGH 0x07f
#define XGET_RESULT_CONTROL_WIDTH_R 32
#define XGET_RESULT_CONTROL_DEPTH_R 16
#define XGET_RESULT_CONTROL_ADDR_RESULT_BASE 0x400
#define XGET_RESULT_CONTROL_ADDR_RESULT_HIGH 0x7ff
#define XGET_RESULT_CONTROL_WIDTH_RESULT 32
#define XGET_RESULT_CONTROL_DEPTH_RESULT 181
![](https://img-blog.csdnimg.cn/40c97c95ea784dd2853df5413f26b350.png)
![](https://img-blog.csdnimg.cn/5ec19ef5561a4608994fc598e7b0babb.png)
![](https://img-blog.csdnimg.cn/145eb443eab844a5a377c937c8d3e4ac.png)
bulid application
查看这些信息,用xget_result.h中提供的方法,来完成我们的application,在application中向PL端(就当一个外设)输入数据读取数据。
1. 初始化我们的设备,会根据ID号来查找我们设备的地址,先定义一个,再初始化。
2. 读ctrl信号,写R矩阵数据,读result结果。 下图是ap_ctrl_hs 协议的时序逻辑。先要准备好数据,在start。控制的过程就是给相应的寄存器写数据(这里是0x000,看图2),图3的文件中有实现了方法,直接调用就行。
写入R的过程:第二个参数填0,从分配给R的地址第0个开始写。不知道填什么参数可以去分析一下这个函数的功能是怎么实现的。
总结
注意PS端和PL端配合,PS端更灵活,PL端更高速
注意使用AXI接口、ap_ctrl_hs等接口很方便
注意从硬件到驱动到应用的层次。
用到的几乎都是C语言而且是非常基础的C语言,没有类
可以优化的地方还有很多,重点在算法中for循环、数据的传输接口协议、应用功能的丰富等
参考文献、参考课程
1.DOA and Range Estimation Using a Uniform Linear Antenna Array Without A Priori Knowledge of the Source Number
4. 开发指南、硬件数据手册(赛灵思 UG902 83页)
点点赞赞