基于 Zynq UltraScale + MPSoC 、 HLS + vivado + vitis 、DOA算法硬化开发

环境

        操作系统 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 HLSvivadovitis
输入算法的C语言RTL IP核HardWare
输出RTL IP核HardWareProgram
xczu4ev-sfvc784-1-i

        zynq    =   arm(PS、CPU)+FPGA(PL)

        含有丰富的资源和接口。PS端是一个小型的电脑,PL端FPGA。本文中将会使用PL端将算法硬件实现,在PS端用C++语言进行嵌入式开发,用一段程序来驱动控制PL端的算法运行。

软件和设备在这里装上

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。

       

开发流程

        tb文件写好后可以进行仿真。C仿真:验证C语言是否实现了算法。C综合:综合后可以产看资源占用情况,计算所用时间、接口综合结果等,是优化计算的参考。Cco仿真可以在vivado中观看波形,要做如下选择,才能调用vivado。

选择查看波形

导出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






1

2
3
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

2. 特别是第8、11、13节http://【【台湾大学×BoLedu】FPGA高层次综合技术HLS于应用加速 (2022秋 赖瑾)】 https://www.bilibili.com/video/BV1RM411a7E8/?share_source=copy_web&vd_source=55af03d44ee61e96dfecec1ce52a092b

 3.http://【跟Xilinx SAE 学HLS系列视频讲座-高亚军】 https://www.bilibili.com/video/BV1bt41187RW/?share_source=copy_web&vd_source=55af03d44ee61e96dfecec1ce52a092b

4. 开发指南、硬件数据手册(赛灵思 UG902 83页)

点点赞赞

  • 27
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值