本章节将演示PL(FPGA)端读写PS端DDR的功能,从而实现FPGA端的大量数据缓存,以及PS与PL间大数据量的交互。
在做FPGA项目的时候,我们经常会遇到需要将大量的临时数据进行暂存的情况,数据量较少的时候,我们可以使用片内BRAM的资源来进行暂存,但是片内BRAM资源一般都非常有限,这个时候我们就需要考虑将数据暂存到外部ram,sdram,或者ddr上了,ram和sdram的存储容量通常有限,如果要存储的数据比较多,就该ddr上场了。
一些纯FPGA芯片,比方说artix 或者kintex 等平台上,DDR一般会直接接到FPGA端,这个时候只用在FPGA中生成对应的DDR控制器来对DDR进行读取, 但是在ZYNQ上这种情况就发生变化了。ZYNQ本身PS端自带硬件DDR控制器,并且PS的正常工作通常离不开DDR,所以大部分ZYNQ的板子都只有PS端接DDR,PL端是没有接DDR的, 这时候如果我们FPGA端需要访问DDR,就要通过AXI的方式来实现了。(包括之前提到过的VDMA的方式,其实最终也是通过AXI来访问DDR的)。
PL访问DDR的另一个好处是,可以方便PS和PL之间大数据的交互,PS端与PL端在硬件上是相互独立的,之前曾在 EBAZ4205 第九个工程 ZYNQ端PS 访问 PL端的reg 寄存器,实现PS与PL数据交互 章节中介绍过通过REG方式来实现数据的交互,但这种方式仅适合小数据量的通信,如果数据比较多,那通过将数据存储在DDR来进行分享将是一个很好的数据交互方式。
本文在 vivado2018.3版本上 演示, 其他版本请自行研究
(备注 此章节内容适用于EBAZ4205及扩展板的组合)
下面将进行具体的演示
一. 创建工程
1)打开Vivado 新建一个项目, 新建一个VIVADO 工程,打开软件 选中Create Project, 如下图所示
2)点击NEXT ,在出现的第二个对话框“Project name”中输入工程名;在“Project location”中选择保存路径;勾选“Create project subdirectory”(默认),最后点击“Next” 备注,所有的路径均不能出现中文名称
3)点击 RTL PROJECT 选项,点击NEXT
4) 第四步Add Sources 选项直接留空,NEXT
5)第五步Add Constraints 选项直接留空,NEXT
6)选择芯片型号 板子的芯片型号为 XC7Z020 封装是CLG400 所以型号我们选择 XC7Z010CLG400-1
7)确认所选信息 点击“Finish”,完成vivado的工程创建
之后 工程就新建好了, vivado 进入到开发界面
二、创建一个带AXI 接口的自定义IP
- 重新创建并封装一个带AXI 接口的IP 具体过程如下 ,TOOLS->Create and Package New IP
2.选择封装带AXI4总线的
3、next,填写名称等信息,注意IP保存路径(保存到工程目录下就可)。这里给IP取名 PL_DDR_RW
4、next,选择总线相关信息,这里AXI的 协议选择 FULL, 然后模式选择 Master模式,位宽保留默认的32位,4即代表有四个32bit的寄存器 最小选择是4个
5.选择 下一步 EDIT IP 并点FINISH完成(选择 EDIT IP 我们就可以对生成的IP进行修改)
6.之后系统会自动生成一个该IP的工程。 这里我们双击打开系统默认生成的IP代码
该默认生成的模块实现了PL端通过AXI4协议对PS端的DDR进行读写的测试。 因为我们本次的实验不需要其他功能,所以不需要修改代码直接关闭修改IP的这个工程即可(如果需要自己增加功能,则修改此IP中对应的代码即可)。
三、Vivado 工程设计
1.增加ZYNQ 模块
1)创建一个BLOCK设计
2)搜索并添加ZYNQ7 Processing System,添加ZYNQ7 PROCESSING SYSTEM模块
软件自动生成了一个 zynq的block 如下图所示,接下来要做一些相应的设置,双击下图中的ZYNQ核
3) 依次在弹窗里找到DDR Configuration→DDR Controller Configuration→DDR3,在Memory Part下拉菜单中根据自己板子上的DDR来选择相应的DDR3,本实验所用到型号:MT41K128M16JT 125,数据位宽选择16bit 最后点击“OK”,如下图所示。
4) 移除M AXI GP0 interface 的勾, 改成勾选 S AXI HP0 interface
为什么是HP AXI 由下图就可以看到 HP AXI 可以同时访问 片内OCM 以及片外DDR3的资源。
5) 使能UART 0 并在IO选项里选择EMIO方式
6) 所有的都完成后点选 OK 退出 ZYNQ配置界面
7) 在Block Design 中同样添加我们刚才创建的IP PL_DDR_RW
之后我们便看到PL_DDR_RW IP 已经被添加
简单介绍下前面生成的这个IP模块的功能,模块一开始工作在IDLE状态,当检测到 axi_aresetn信号出现高电平时, 模块将从IDLE进入到 INIT_WRITE 模式对DDR进行写入操作,当写入完成后,模块会进入到INIT_READ,将刚才写入到DDR的数据再次读回来。最后系统进入到INIT_COMPARE 模式,对读写数据进行比较。 随后拉高 axi_txn_done表示操作结束,并在axi_error端口输出对比的结果。如果读写的数据不一致 axi_error信号会输出高电平,代表读写出现错误。
- 1. IDLE (检测axi_aresetn 信号是否出现高电平,是则进入 INIT_WRITE 模式)
- 2. INIT_WRITE (对DDR进行数据写入操作,完成后进入INIT_READ 模式)
- 3. INIT_READ (对DDR进行数据读取的操作,完成后进入INIT_COMPARE 模式)
- 4. INIT_COMPARE (对读写的数据进行对比,完成后拉高axi_txn_done ,并在axi_error输出对比的结果)
8)双击PL_DDR_RW 模块打开配置界面(按图片中进行设置 ,之后点选OK 确定)
将寄存器AXI TARGET SLAVE BASE ADDR的值修改为0x8000000,这个地址将作为我们PL对DDR进行读写的起始地址,这里有个值得注意的地方,用0x8000000而不是0x00000000 是因为0x00000000这个地址属于OCM 片内存储器的, 又因为DDR的前面部分地址需要给程序堆栈使用,所以这里我们才选地址为0x8000000。(另外这里也不能用Smart Zynq 的0X10000000,因为EBAZ4205的板子只有256MB,而0X10000000计算出来就是256MB,用这个作为DDR的读写的基地址就会越界出问题)
9) 之后点选 Run Block Automation 和Run Conection Automation (弹出来的对话框都保持默认即可)
系统会帮我自动连接好走线,还有增加需要的模块
10 ) 因为我们的UART 也是EMIO方式的,所以这里也要对UART信号进行右键 Make External
11) 为了方便系统演示, 所以这里要认为的增加两个LED灯, 来分别连接axi_txn_done 和 axi_error。
分别引出这两个端口信号(右键端口选中 Make External)
将两个信号的名称分别修改为ERROR 和DONE
12) 为了方便系统演示 我们这边还需要增加一个按键连接axi_aresetn 信号,又因为axi_aresetn 检测到高电平即开始读写操作,所以这里我们要增加一个非门,以让按键按下后才开始进行读写操作。
如下图所示 添加utility vector logic 模块,并双击模块进入配置界面
双击模块并将模块修改成1个bit 的非门
连接非门与axi_aresetn信号。
修改信号的名称。
最后得到的结构如下图所示
13) 创建硬件描述,source→Design Source ,右键我们创建的BLOCK工程,点击create HDL wrapper如下图所示(这一步的作用相当于将图纸转换成对应的硬件描述语言的功能)
13)编写好代码后 对代码进行编译
14 ) 添加管脚约束 ,可以在图文界面里设置管脚定义
其中KEY3(转接板正中间的按键)接在KEY[0] ,LED1接在DONE ,LED2接在ERROR。
之后点选保存 会弹出约束的保存窗口,填写要保存的约束文件的名字(自定义)即可。
15 )综合并生成 bit文件
成功后会出现下图对话框,点OK 确认就好
四 、PL部分访问DDR 实机演示
其实工程到这里即使没有SDK部分工程,PL也已经可以正常访问DDR了,下面简单的演示一下
用TYPE C数据线将板子的JTAG口和电脑连接,给板子通上电源后,点击 PROGRAM AND DEBUG 中的OPEN TARGET 下的Auto Connect 来连接板子
如果连接正常会在右侧 硬件栏中显示设备,如下图所示
接下来下载bit文件,右键设备 然后选择 Program Device
默认会出现生成的BIT文件,如果没有的话需要手动添加路径 ,点击PROGRAM 下载
下载之后, 此时两个绿灯都是熄灭状态, 此时按下板子上的KEY3(转接板正中间的按键) PL开始对DDR进行读写操作,之后LED1灯被点亮(程序里定义的DONE 灯,代表读写操作完成),LED2(ERROR)错误提示灯保持熄灭状态, 证明我们的DDR读写实验成功,从DDR读出的数据和写入的数据是一致的。(备注经过大家的反馈知道很多朋友在这里读出来数据结果和我的情况不一致,那大概率是因为DDR需要PS进行初始化导致的,所以如果大家遇到这里不成功,请继续往下面做,把PS对DDR初始化配置部分也添加进去 ——20240529)
五、PS部分工程创建
之前我们PL端的程序已经可以对DDR正常读写了,为什么还需要增加PS部分的代码呢, 这里我说明下PS部分是可以访问访问PL写入DDR地址的数据,本实验通过PS读取DDR数据来验证PL的写入是否成功,同时也为以后我们PS和PL的交互做铺垫。
1)File→Export→Export hardware…,在弹出的对话框中勾选“include bitstream”,点击“OK”确认,如下图所示。
2)File→Lauch SDK,在弹出的对话框中,保存默认,点击“OK”,如下图所示。
系统将自动打开SDK开发环境
3)再创建一个APP 工程,负责PS读取DDR 部分的数据用以验证。
a. 创建一个新的空工程,可以取名叫PL_DDR_TEST
b.右键工程的SRC目录,然后新建一个SOURCE FILE
c.取名 main.c
六、 PS端代码编写
1.在main .c中添加如下代码
#include "stdio.h" #include "xil_cache.h" #include "xil_printf.h" #include "xil_io.h" #define DDR_BASEADDR 0X08000000 int main() { int i; char A; Xil_DCacheDisable(); print("AXI4 PL DDR TEST!\n\r"); print("Please input A to start\n\r"); while(1){ scanf("%c",&A); if(A=='A'||A=='a'){ printf("start\n\r"); for(i=0;i<4096;i=i+4){ printf("The data for the address %x is %d\n\r",DDR_BASEADDR+i,(int)Xil_In32(DDR_BASEADDR+i)); } } } return 0; }
2.下载程序到板子上验证 (备注 如果刚才vivado 里已经做过上文提到过的实验了,那此处需要断电 让DDR回到无序状态,必须是断电,复位键没用)
1) 先提前打开串口助手(这里用vivado SDK 自带的)
2) 打开Run as -> Run Configurations
如果是第一次打开此页面,之前没有debug过,则双击system debugger选项
3 ) 在右侧 的窗口勾选 Reset entire system ,以及Program FPGA, 这样每次debug 的时候都会预先加载并配置FPGA。
4 ) 之后系统就会自动运行了(fpga也会被自动加载) 。 如果修改程序后 再次要debug,就可以直接点绿色箭头,或者选 Run As –>Launch on Hardware (system debugger即可)
5) 程序工作后,会通过串口输出 AXI4 PL DDR TEST ! 以及 Please input A to start 的内容, 此时按下键盘上的A,并按下Send, 向系统发送开始命令
之后,系统通过串口将从DDR读取到的内容发送给PC, 此时读到的DDR的数据都是毫无规律的,无序的数据(因为PL 这个时候并未向DDR发送数据,而上电后DDR的内容本身是乱序的)。(备注如果读到的内容是有序的,那是因为刚才vivado 里已经做过上文提到过的实验后没有断电过DDR已经被PL写入了一遍,那此处必须要重新断电让DDR回到无序状态再重新上述操作否则影响实验的结果,必须是断电,复位键没用)
6) 此时我们按下板子上的KEY3(转接板正中间的按键)让PL对DDR进行读写操作,按下按键之后LED1灯被点亮(程序里定义的DONE 灯,代表读写操作完成),LED2(ERROR)错误提示灯保持熄灭状态, 证明我们的DDR读写实验成功,从DDR读出的数据和写入的数据是一致的。
7) 此时再回到串口界面, 再次发送字符A,可以看到从板子发回的DDR内容,由乱序变成1,2,3,4这样的顺序内容了, 8000000代表 0x08000000地址位,这个地址和PL端设置的相呼应。 PS端从DDR3中读出的数据为1到1024,与PL端IP写入的数据一致,说明实验成功了。(间隔4是因为Xil_In32是以32个字节为单元的)
本文只是抛砖引玉, 如果要修改传输的内容,只需要修改PL端自动生成的AXI IP修改即可,大家自行研究。
以下是本文的完整工程,仅供参考: