开发板:ZYNQ-7000系列 裸板开发
开发环境:vivado hls、vivado、sdk
https://blog.csdn.net/weixin_36474809/article/details/85111550
https://www.csdn.net/link?target_url=http%3A%2F%2Fwww.eefocus.com%2Fantaur%2Fblog%2F17-08%2F423773_0818c.html&id=97229926&token=187366a068bb5613952e707d19e33bfa
https://blog.csdn.net/yqq654101/article/details/80373971
很多时候FPGA上的资源是很有限的,有时候需要PL端存储和读取大量的数据,光靠BRAM是不够的,因此需要与PS共享DDR作为PL端的Global memory。用DDR作为PL端的全局内存有两大好处:1 缓解PL端缓存压力 2 作为共享内存实现PL端和PS端通信
个人课题为FPGA加速CNN,因此目的如下:
1 能够将CNN的权重放在DDR上,PL端需要权重的时候从DDR上读取。即PS写DDR,PL读DDR数据。
2 PL将卷积结果写回到DDR,PS端对结果进行后续处理。即PL写DDR,PS读。
测试用的IPCore在vivado hls中编写,代码如下:
void pl_ddr_RW(volatile int*din,volatile int*dout,int add)
{
#pragma HLS INTERFACE s_axilite port=return bundle=CONTROL
#pragma HLS INTERFACE s_axilite port=add bundle=CONTROL
#pragma HLS INTERFACE m_axi depth=4 port=dout offset=slave bundle=DATA_OUT
#pragma HLS INTERFACE m_axi depth=4 port=din offset=slave bundle=DATA_IN
for(int i = 0; i < 10;i++)
{
#pragma HLS PIPELINE II=1
dout[i] = din[i] + add;
}
核心功能很简单,从DDR某个地址开始连续读10个数,加上一个偏置ADD,写回到DDR的另一个地址开始连续写10个数。从而测试PL的读写功能。
将din和dout设置为m_axi总线用于向DDR读写数据,offset = slave指定其起始地址的偏置由ps端设定,return和add是少量数据,适合于用s_axilite 总线来读写,并绑定为CONTROL总线。
因为程序比较简单因此省略了testBench和cosimulation,具体设计过程的hls参考ug902,最后输出export RTL在vivado中使用。
选择part,添加和编辑IPCore等等一些常规操作后搭建的硬件系统如下:
别忘了检查address,在zynq中ps和pl的地址映射关系是一致的,因此可不用特别设置。
地址设置
generate output product和create HDL wrapper,并且生成bitstream,file 中 export hardware 要include bitstream,最后再vivado中lauch SDK,进行软件程序的编写。
#include
#include "platform.h"
#include "xil_printf.h"
#include "xpl_ddr_rw.h"
XPl_ddr_rw pl_ddr_rw_instance;
int main()
{
init_platform();
printf("begin...\n\r");
volatile int *read_ptr = 0x30000000;
volatile int *write_ptr = 0x30000000+sizeof(int)*10;
for(int i =0; i < 10;i++) *(read_ptr+i) = i;
for(int i =0; i < 10;i++) *(write_ptr+i) = 0;
XPl_ddr_rw_Initialize(&pl_ddr_rw_instance,XPAR_PL_DDR_RW_0_DEVICE_ID);
XPl_ddr_rw_InterruptGlobalDisable(&pl_ddr_rw_instance);
XPl_ddr_rw_DisableAutoRestart(&pl_ddr_rw_instance);
XPl_ddr_rw_Set_din(&pl_ddr_rw_instance,read_ptr);
XPl_ddr_rw_Set_dout(&pl_ddr_rw_instance,write_ptr);
XPl_ddr_rw_Set_add(&pl_ddr_rw_instance,10);
XPl_ddr_rw_Start(&pl_ddr_rw_instance);
while(!XPl_ddr_rw_IsDone(&pl_ddr_rw_instance))
printf("working...\n\r");
for(int i =0; i < 10;i++)
{
printf("loc: %d val: %d \n\r",i,*(write_ptr+i));
}
printf("end...\n\r");
cleanup_platform();
return 0;
}
注意:新建的application是用的memory test模板,和hello world模板不同的是其init_platform()函数中有Xil_DCacheDisable()函数,能够声明不使用DCache。
如果用的是helloworld模板,Dcache还是在使用的。(猜测,未证实)由于Dcache的存在,会导致PL写DDR的时候,写到了一个特殊的内存中,等待main函数结束后才会从该特殊内存复制到DDR中指定的地址,因此在同一个程序中PS无法从DDR指定地址中获得希望的结果。证明方法:在不reset的情况下,debug模式运行程序,会发现DDR中指定地址的数据是希望得到的结果。
因此,在没弄清楚原理的时候,Dcache记得先disable。
上述SDK程序在串口窗口中打印的结果如下:
串口打印
综合SDK程序,成功测试了PL的对DDR的读写和PS对DDR的读写功能。
该测试实验中,关闭Dcache是比较重要的操作。另外,读写的数据直接选择了0x30000000是比较危险的做法,因为如果该位置有指令的话会直接覆盖指令从而造成应用程序的崩溃。但是看一些分析指令集中在0x00100000~0x00000000的位置,因此不会崩溃。从lscript.ld链接文件也能看出,对于裸机开发来说指令空间是很小的,在较低的地址空间,选较高的地址位不会出现崩溃的问题。