admin管理员组

文章数量:1532440

本文中,在SDK中先采用helloworld模板来检查整个工程是否创建成功,得到正确的结果后,再加入PS操作DDR的代码(用的是ZCU102)。本文主要描述整个设计的流程,过程容易忘记,所以记录,也方便自己之后查看,如有不对,请各位提出宝贵建议!

文章目录

    • 一、Vitis hls部分
    • 二、vivado部分
    • 三、Xilinx Vitis部分

一、Vitis hls部分

1.创建工程
指定工程名和存放位置

然后添加设计文件(包含所有的子函数),且指定顶层函数的名称(这里需要注意可能与设计文件名称不一样):
(设计文件和测试文件代码在第一点的最后!!!)

然后添加测试文件t,即testbench文件:

然后修改part selection为本文需要的板卡ZCU102;Flow Target选项有两种: 可以是Vivado IP Flow Target,也可以是VitisKernel Flow Target。
前者最终导出来的是VivadoIP,用于支持Vivado IP 设计流程;
后者用于Vitis应用加速流程,此时,Vitis HLS会自动推断接口,无需在代码里通过Pragma或Directive的方式定义Interface,最终会输出.xo文件。(这里选择Vivado IP Flow Target,因为设计的IP要导出到vivado中进一步使用):


最终创建好的项目结构:

HLS代码:

int share_dram_core(int write_nums,int read_nums,
					volatile float * data_ptr,
					int location_idx,int write_loop_idx,int read_loop_idx,
					int read_sum){
#pragma HLS INTERFACE m_axi depth=75 port=data_ptr offset=slave
#pragma HLS INTERFACE s_axilite port=return register
#pragma HLS INTERFACE s_axilite port=write_nums register
#pragma HLS INTERFACE s_axilite port=read_nums register
#pragma HLS INTERFACE s_axilite port=location_idx register
#pragma HLS INTERFACE s_axilite port=write_loop_idx register
#pragma HLS INTERFACE s_axilite port=read_loop_idx register
#pragma HLS INTERFACE s_axilite port=read_sum register

	location_idx=0;
	write_loop_idx=0;
	read_loop_idx=0;
	read_sum=0;

	for(int read_loc=0;read_loc<read_nums;read_loc++){
		read_sum+=data_ptr[read_loc];
		read_loop_idx++;
	}
	location_idx=1;//Done read process

	volatile float *write_ptr=&data_ptr[read_nums];

	for(int write_loc=0;write_loc<write_nums;write_loc++){
		write_ptr[write_loc]=write_loc;
		write_loop_idx++;
	}
	location_idx=2;//done write process

	return 1; //return=1 means done
}

2.项目验证
这个阶段主要进行 C仿真,C综合,C和RTL协同仿真,打包导出IP:

(1)首先是C仿真:

(2)然后是C综合:(注:有些代码和结构是不可被Vitis HLS综合的,包括动态分配存储空间、与操作系统相关操作等)

在测试文件中出现了malloc动态内存分配,但是可以综合成功。这是因为我们指定了要综合的顶层函数的名称(指定不是测试文件,而是设计文件,因此动态内存分配不用考虑,只是测试),此处填写的待综合的函数名称为“share_dram_core”:

(3)接下来进行C RTL的协同仿真:
这里选择vivado自带的仿真器:Vivado XSIM,也可以选择其它的,但是需要指定其它仿真器的位置。其次,对于Dump Trace,选择port才可以输出的波形(如果默认好像是不会输出波形):

最终协同仿真成功的界面如下:

可以点击左上角的波形图标即可查看输出波形:

注:补充:下面这种情况会导致在C仿真和C综合阶段无误,但是协同仿真的时候会报错:

报错的原因是:
对于m_axi类型的接口,其depth深度的设置要和开辟的内存的大小相同:(但是试验测试的时候发现大一点也是可以的,比如这里开辟50个内存单元,如果depth设置为50-100得到的协同仿真结果都正确,但是设置为200就会报错)。

(4)以上三步成功后就可以将设计好的IP导出:
这里可以不需要设置,直接导出即可:

注:一般导出都会报错:

解决方案是:
到这个网址下载一个补丁文件,然后将y2k22_patch这个文件夹拷贝到安装Vivado的文件夹:

在上图的位置下打开WIndows PowerShell ,执行
python .\y2k22_patch\patch.py命令,等待执行完成即可,如下图所示:

再次导出,出现下图所示即导出成功:

在vivado中调用该IP:

将上图中所显示的路径下的压缩包复制到vivado项目下的专门存储IP核的文件下(这个文件是自己定义,如下图所示):

最终在Vivado中进行调用即可:
调用之前需要指定专门存储IP的文件的路径:

二、vivado部分

1.创建工程项目


选择板卡:
在这里插入图片描述

注:如果有些板卡没有,可以试着升级vivado的版本,点击下图红色框进行升级:
项目创建成功:

2.搭建系统框图:

完成之后的系统框图:(这里实现的是PS与PL实现共享DDR)
这里需要引入HLS开发的IP,因此需要在Vivado中的IP set界面指定管理这些IP的文件的位置(前面有提)。
系统框图一:

系统框图二:

仔细对比在vivado中搭建的两个系统框图,有不同的地方,如ZYNQ的从接口类型从S_AXI_HP0_FPD变为S_AXI_LPD;ZYNQ的主接口M_AXI_HPM0_FPD变为M_AXI_HPM0_LPD;此外最关键的一个改变是复位那里,这样改的原因还不是算很清楚,后续补充!这里采用第二种框图(第一种可能会导致内存有错误)。

注:对于ZYNQ ultraScale+ Mpsoc设置(双击即可打开下面的界面)需要注意:不用的外设可以进行裁剪,但所以在使能或者失能一些功能外设的时候需要留意。比如在SDK/Vitis中用到串口打印,那么必须在上图中使能并且正确配置uart0,否则会报错。
此外,对于DDR的配置极为重要,如果出错,那么在SDK中会有很多问题(后面会提到)!!!

搭建完成之后:进行下一步。
3.生产bit流文件,然后导出到Vitis中:
首先生成.v文件:

然后直接点击Generate Bitstream,会自动进行仿真、综合、实现、生成比特流文件:

完成之后,点击file下面的export,将其导出:
如下图,导出后的是.xsa文件(该文件会在Vitis创建硬件平台工程platform工程时使用),其路径如下图所示:

之后在vivado中启动vitis进行PS端的开发:

三、Xilinx Vitis部分

指定工作空间路径:

1、创建硬件平台工程(platform工程):

对硬件平台工程(platform工程)命名:

选择vivado导出的xsa文件:

点击finish即可:

创建好的硬件平台工程如图红色方框内:该platform工程下还有两个工程,分别是FSBL工程、standalone on psu_cortexa53_0工程。其中的FSBL工程是上一张图中的“generate boot components”的复选框选中而产生的

注:这一点和之前的SDK不同,SDK中需要手动创建FSBL工程,如果不创建,程序可能无法正确运行。结合FSBL的一个功能,即如果镜像中有bitstream文件部分,则用它配置PL部分。所以通过实验,我的理解是,如果用到了PL部分,但没有创建FSBL工程,那么运行就会报错。)


2、创建软件工程:
选择上一步创建好的硬件平台:

给软件工程命名:

实验过程中发现,如果直接创建DDR工程,可能同时会出现很多问题 ,所以建议先选择创建一个helloworld工程,先验证工程的创建是否正确,验证串口的打印输出是否正确,验证单步调试是否正确等:


这一步默认即可,点击next:

选择hello world模板:

创建好的软件工程如下图所示:

3、硬件调试(验证hello world工程)
因为代码中有print,要在控制台查看打印输出的内容,这里使用Vitis自带的串口调试工具,因此需要调出terminal窗口:

配置串口和波特率:

串口驱动安装的补充:
下载一个串口驱动
通过window更新完成驱动安装

接着编译hello world软件工程(build project),编译方法和结果如下图所示:
(注:还有一点是Vitis和SDK不同的地方,就是工程的编译,之前的SDK在保存项目的同时就会自动进行编译;但是Vitis不会,需要单独进行build project,编译之后才会生成.elf文件)

开启板卡,连接好JTAG和USB等接口,然后选择“helloworld”,右键,选择“Run as”,把程序运行起来,“Run as”里又有很对选项,这里选择第一个“Launch on Hardware(Single Application Debug)”,使用系统调试,直接运行程序
(注:在上电之前将开发板的启动模式设置到 JTAG 模式,对于ZCU102,就是将SW6调到0000即on on on on):

运行的结果如下图所示:

可以发现没有成功输出print中的内容!!!
不改变任何代码和软件配置,再次运行,会报错:

(上面整个错误,好像可以暂时不管,再运行依次就不会报这个错误了)。
连续运行代码数次,输出的结果如下图所示,发现偶尔会成功输出print中的内容。

那么单步调试一下,查看代码是否有运行:设置断点进行调试,会发现程序无法进入main函数的入口,因此调试的机会都没有。尝试多次debug,(类似于运行时偶尔会得到print的结果),偶尔也可以进入main函数入口,进行调试,因此这个问题必须解决,才能正常开发之后的代码。

解决方案如下:

注:在这里,导致这个现象的原因可能是vivado中的配置出现错误
这里有两种方案,第一种是对时钟进行修改,第二种是对DDR重新配置(其实第一种并没有彻底解决,第二种是可行的。但是第一种会出现很神奇的现象,所以写在这里):
方案一:
在Vivado中将PS端时钟频率从33.33Mhz改为50Mhz,每次调试都可以停到main()函数入口处;(每次运行后控制台也会出现内容)

但是无论是运行代码还是调试代码,又会出现一个新的问题:打印出来的是乱码:

出现乱码的原因还未弄明白!

方案二:如下图所示,重新修改DDR的配置后,便可以成功解决上面的错误:

如下图所示,可以正确输出结果:

这里对代码的运行和调试做一点补充:
为了保证系统的可靠调试, 最好是右键“Run As -> Run Configuration…”,
可以看一下里面的配置,其中 Reset entire system 是默认选中的,这是跟以前的 Vitis软件不同的。如果系统中还有 PL 设计,还必须选择“Program FPGA”

注:如果把Reset entire system和Program FPGA这两个√取消掉的话,再次Debug的时候,它只会加载.elf程序,不会重新Program FPGA,这的确能节省一些加载Debug的时间,但这么做的话PL端的有些IP核没有复位,再次执行程序的时候就可能会出错!)
因此需要一种解决办法,能够在Debug程序开始前就只Program一遍FPGA,而后只需要relaunch SDK工程。参考方法,该方法是通过在Vivado中增加FCLK_RESET0_N信号,在程序main()函数刚开始的时候就对PL端用程序发出FCLK_RESET0_N信号进行复位。


既然可以正确创建helloworld工程,也可以得到正确的结果,下面对SDK自带的memory模板进行测试,也可以得到正确的结果。所以接下来就可以在SDK中编写代码对DDR进行操作(在下一篇文章中):

当对helloworld模板和memory模板都测试成功后,因此下面在memory模板的基础上,开始编写PS与PL共享DDR的代码:
代码如下:

//This is the SDK code to test share DRAM
//Write through PS to DDR
//Run PL : read from DDR to PL and write from PL to DDR
//Then read from DDR to PS

#include <stdio.h>
#include <xparameters.h>
#include <stdlib.h>
#include "platform.h" 
#include <xil_cache.h>
#include "xshare_dram_core.h"
#include "platform.h"
XShare_dram_core XShare_dram_core_instance;

int main()
{
	init_platform();
	print("\n --------------program start------------- \n");

	//read and write param
	int ps_wirte_size=25;
	int ps_read_size=25;
	int core_location_idx=100;
	int core_write_loop_idx=100;
	int core_read_loop_idx=100;
	int core_read_sum=100;
	int core_return_value=100;
	long int dataptr=0;
	volatile float * ps_write_ptr=NULL;
	volatile float * ps_read_ptr=NULL;

	//pointer intialize
//	ps_write_ptr=(volatile float *)malloc((ps_wirte_size+ps_read_size)*sizeof(float));
	ps_write_ptr= (volatile float *)0x10000000;
	ps_read_ptr=&ps_write_ptr[ps_wirte_size];
	if(ps_write_ptr==NULL)print("Malloc ps_write_ptr failure \n");
	if(ps_read_ptr==NULL)print("Malloc ps_read_ptr failure \n");
	memset((void*)ps_write_ptr,0,ps_wirte_size*sizeof(float));
	memset((void*)ps_read_ptr,0,ps_read_size*sizeof(float));

	print("Initialize ps_read_ptr and ps_write_ptr SUCCESS!\n");
	printf("ps_read_ptr is %x \n",(unsigned int)(long)ps_read_ptr);
	printf("ps_write_ptr is %x \n",(unsigned int)(long)ps_write_ptr);

//	for(int cur_print_loc=0;cur_print_loc<ps_read_size;cur_print_loc++){
//		printf("location %d, value %d \n",cur_print_loc,(int)ps_read_ptr[cur_print_loc]);
		printf("location %d \r",cur_print_loc);
		printf("value %f \n",(float)ps_read_ptr[cur_print_loc]);
//	}

	//initialize IPcore
	if(XShare_dram_core_Initialize(&XShare_dram_core_instance, XPAR_XSHARE_DRAM_CORE_0_DEVICE_ID)!=XST_SUCCESS){
		printf("XShare_dram_core_Initialize filed!\n");
		return -1;
	}
	printf("XShare_dram_core_Initialize SUCCESS!\n");

	//get and printf values
	core_location_idx=XShare_dram_core_Get_location_idx(&XShare_dram_core_instance);
	core_write_loop_idx=XShare_dram_core_Get_write_loop_idx(&XShare_dram_core_instance);
	core_read_loop_idx=XShare_dram_core_Get_read_loop_idx(&XShare_dram_core_instance);
	core_read_sum=XShare_dram_core_Get_read_sum(&XShare_dram_core_instance);
	core_return_value=XShare_dram_core_Get_return(&XShare_dram_core_instance);
	printf("core_location_idx=%d \n",core_location_idx);
	printf("core_write_loop_idx=%d \n",core_write_loop_idx);
	printf("core_read_loop_idx=%d \n",core_read_loop_idx);
	printf("core_read_sum=%d \n",core_read_sum);
	printf("core_return_value=%d \n",core_return_value);

	//initialize IPcore value
	XShare_dram_core_Set_write_nums(&XShare_dram_core_instance, ps_read_size);
	XShare_dram_core_Set_read_nums(&XShare_dram_core_instance, ps_wirte_size);
	XShare_dram_core_Set_data_ptr(&XShare_dram_core_instance, ps_write_ptr);

	printf("-------------Core value set SUCCESS! \n");

	//get and printf values
	dataptr=XShare_dram_core_Get_data_ptr(&XShare_dram_core_instance);
	printf("data_ptr=%ld \n",dataptr);
	core_location_idx=XShare_dram_core_Get_location_idx(&XShare_dram_core_instance);
	core_write_loop_idx=XShare_dram_core_Get_write_loop_idx(&XShare_dram_core_instance);
	core_read_loop_idx=XShare_dram_core_Get_read_loop_idx(&XShare_dram_core_instance);
	core_read_sum=XShare_dram_core_Get_read_sum(&XShare_dram_core_instance);
	core_return_value=XShare_dram_core_Get_return(&XShare_dram_core_instance);
	printf("core_location_idx=%d \n",core_location_idx);
	printf("core_write_loop_idx=%d \n",core_write_loop_idx);
	printf("core_read_loop_idx=%d \n",core_read_loop_idx);
	printf("core_read_sum=%d \n",core_read_sum);
	printf("core_return_value=%d \n",core_return_value);

	//IPcore start
	XShare_dram_core_Start(&XShare_dram_core_instance);
	printf("-------------IPCore start SUCCESS! \n");

	//get and printf values
	core_location_idx=XShare_dram_core_Get_location_idx(&XShare_dram_core_instance);
	core_write_loop_idx=XShare_dram_core_Get_write_loop_idx(&XShare_dram_core_instance);
	core_read_loop_idx=XShare_dram_core_Get_read_loop_idx(&XShare_dram_core_instance);
	core_read_sum=XShare_dram_core_Get_read_sum(&XShare_dram_core_instance);
	core_return_value=XShare_dram_core_Get_return(&XShare_dram_core_instance);
	printf("core_location_idx=%d \n",core_location_idx);
	printf("core_write_loop_idx=%d \n",core_write_loop_idx);
	printf("core_read_loop_idx=%d \n",core_read_loop_idx);
	printf("core_read_sum=%d \n",core_read_sum);
	printf("core_return_value=%d \n",core_return_value);

	while(!XShare_dram_core_IsDone(&XShare_dram_core_instance)){
		printf("Calculating...\n");
	}
	printf("IsDone done SUCCESS!\n");

	//get and printf values
	core_location_idx=XShare_dram_core_Get_location_idx(&XShare_dram_core_instance);
	core_write_loop_idx=XShare_dram_core_Get_write_loop_idx(&XShare_dram_core_instance);
	core_read_loop_idx=XShare_dram_core_Get_read_loop_idx(&XShare_dram_core_instance);
	core_read_sum=XShare_dram_core_Get_read_sum(&XShare_dram_core_instance);
	core_return_value=XShare_dram_core_Get_return(&XShare_dram_core_instance);
	printf("core_location_idx=%d \n",core_location_idx);
	printf("core_write_loop_idx=%d \n",core_write_loop_idx);
	printf("core_read_loop_idx=%d \n",core_read_loop_idx);
	printf("core_read_sum=%d \n",core_read_sum);
	printf("core_return_value=%d \n",core_return_value);

	for(int cur_print_loc=0;cur_print_loc<ps_read_size;cur_print_loc++){
		printf("location %3d, value %d \n",cur_print_loc,(int)ps_read_ptr[cur_print_loc]);
	}
	printf("-----------Program end SUCCESS!- \n\n");
//	if(ps_read_ptr != NULL) {
//		free(ps_read_ptr);ps_read_ptr=NULL;
//	}
//	if(ps_write_ptr != NULL){
//		free(ps_write_ptr);ps_write_ptr=NULL;
//	}
	return 0;
}

vivado vitis得到的最终结果如下图所示:

补充:
vivado中的地址分配需要注意:偏移地址必须与范围字段对齐。
对其规则:便宜地址的最低有效位包含N个0,N值取决于range字段即range=2^N,因此并不是可以随意设定地址。

在address editor中,

vitis中的lscript.ld文件的内存分配:

!!!!!!!!!!!!!友情提示!!!!!!!!!!!!!!!
Xilinx Vitis 、Vivado、Vitis HLS这三个软件的启动文件不是.exe,当删除快捷方式后,通过以下方法打开:
这里我当时以为exe就是启动文件,结果打不开,所以重装了一次vivado,浪费了时间:
(1)Xilinx Vitis 启动文件:

(2)Vivado

(3)Vitis HLS

本文标签: psPLDDR