EBAZ4205 第二十七个工程 PL(FPGA) 读写 PS端DDR的实验

本章节将演示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, 如下图所示

此图片的alt属性为空;文件名为Create-Project.png

2)点击NEXT ,在出现的第二个对话框“Project name”中输入工程名;在“Project location”中选择保存路径;勾选“Create project subdirectory”(默认),最后点击“Next” 备注,所有的路径均不能出现中文名称

3)点击 RTL PROJECT 选项,点击NEXT

此图片的alt属性为空;文件名为image.png

4) 第四步Add Sources 选项直接留空,NEXT

5)第五步Add Constraints 选项直接留空,NEXT

6)选择芯片型号  板子的芯片型号为 XC7Z020 封装是CLG400 所以型号我们选择 XC7Z010CLG400-1 

7)确认所选信息 点击“Finish”,完成vivado的工程创建

之后 工程就新建好了, vivado 进入到开发界面

此图片的alt属性为空;文件名为image-172-1024x738.png

二、创建一个带AXI 接口的自定义IP

  1. 重新创建并封装一个带AXI 接口的IP 具体过程如下 ,TOOLS->Create and Package New IP
此图片的alt属性为空;文件名为image.png

2.选择封装带AXI4总线的

此图片的alt属性为空;文件名为image-2.png

3、next,填写名称等信息,注意IP保存路径(保存到工程目录下就可)。这里给IP取名 PL_DDR_RW


4、next,选择总线相关信息,这里AXI的 协议选择 FULL, 然后模式选择 Master模式,位宽保留默认的32位,4即代表有四个32bit的寄存器 最小选择是4个

此图片的alt属性为空;文件名为image-2-1024x578.png

5.选择 下一步 EDIT IP 并点FINISH完成(选择 EDIT IP 我们就可以对生成的IP进行修改)

此图片的alt属性为空;文件名为image-13.png

6.之后系统会自动生成一个该IP的工程。 这里我们双击打开系统默认生成的IP代码

此图片的alt属性为空;文件名为image-12.png

该默认生成的模块实现了PL端通过AXI4协议对PS端的DDR进行读写的测试。 因为我们本次的实验不需要其他功能,所以不需要修改代码直接关闭修改IP的这个工程即可(如果需要自己增加功能,则修改此IP中对应的代码即可)。



三、Vivado 工程设计

1.增加ZYNQ 模块

1)创建一个BLOCK设计

此图片的alt属性为空;文件名为image-173.png

2)搜索并添加ZYNQ7 Processing System,添加ZYNQ7 PROCESSING SYSTEM模块

此图片的alt属性为空;文件名为image-174.png

软件自动生成了一个 zynq的block 如下图所示,接下来要做一些相应的设置,双击下图中的ZYNQ核

此图片的alt属性为空;文件名为image-175.png

3) 依次在弹窗里找到DDR Configuration→DDR Controller Configuration→DDR3,在Memory Part下拉菜单中根据自己板子上的DDR来选择相应的DDR3,本实验所用到型号:MT41K128M16JT 125,数据位宽选择16bit 最后点击“OK”,如下图所示。

4) 移除M AXI GP0 interface 的勾, 改成勾选 S AXI HP0 interface

此图片的alt属性为空;文件名为image-6.png

为什么是HP AXI 由下图就可以看到 HP AXI 可以同时访问 片内OCM 以及片外DDR3的资源。

此图片的alt属性为空;文件名为image-7-1024x561.png

5) 使能UART 0 并在IO选项里选择EMIO方式

此图片的alt属性为空;文件名为image-118.png

6) 所有的都完成后点选 OK 退出 ZYNQ配置界面

此图片的alt属性为空;文件名为image-8.png

7) 在Block Design 中同样添加我们刚才创建的IP PL_DDR_RW

此图片的alt属性为空;文件名为image-10-1024x609.png

之后我们便看到PL_DDR_RW IP 已经被添加

此图片的alt属性为空;文件名为image-11.png

简单介绍下前面生成的这个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的读写的基地址就会越界出问题)

此图片的alt属性为空;文件名为image-17-1024x595.png

9) 之后点选 Run Block Automation 和Run Conection Automation (弹出来的对话框都保持默认即可)

此图片的alt属性为空;文件名为image-14.png

系统会帮我自动连接好走线,还有增加需要的模块

10 ) 因为我们的UART 也是EMIO方式的,所以这里也要对UART信号进行右键 Make External

此图片的alt属性为空;文件名为image-21.png
此图片的alt属性为空;文件名为image-15-1024x268.png

11) 为了方便系统演示, 所以这里要认为的增加两个LED灯, 来分别连接axi_txn_done 和 axi_error。

分别引出这两个端口信号(右键端口选中 Make External)

此图片的alt属性为空;文件名为image-19.png
此图片的alt属性为空;文件名为image-20.png

将两个信号的名称分别修改为ERROR 和DONE

此图片的alt属性为空;文件名为image-31-1024x338.png



12) 为了方便系统演示 我们这边还需要增加一个按键连接axi_aresetn 信号,又因为axi_aresetn 检测到高电平即开始读写操作,所以这里我们要增加一个非门,以让按键按下后才开始进行读写操作。

如下图所示 添加utility vector logic 模块,并双击模块进入配置界面

此图片的alt属性为空;文件名为image-25.png

双击模块并将模块修改成1个bit 的非门

此图片的alt属性为空;文件名为image-26.png

连接非门与axi_aresetn信号。

此图片的alt属性为空;文件名为image-27.png
此图片的alt属性为空;文件名为image-28.png

修改信号的名称。

此图片的alt属性为空;文件名为image-29.png

最后得到的结构如下图所示

此图片的alt属性为空;文件名为image-34-1024x371.png

13) 创建硬件描述,source→Design Source ,右键我们创建的BLOCK工程,点击create HDL wrapper如下图所示(这一步的作用相当于将图纸转换成对应的硬件描述语言的功能)

此图片的alt属性为空;文件名为image-138.png

13)编写好代码后 对代码进行编译

此图片的alt属性为空;文件名为image-10.png

14 ) 添加管脚约束 ,可以在图文界面里设置管脚定义

其中KEY3(转接板正中间的按键)接在KEY[0] ,LED1接在DONE ,LED2接在ERROR。

之后点选保存 会弹出约束的保存窗口,填写要保存的约束文件的名字(自定义)即可。

此图片的alt属性为空;文件名为image-24-1024x670.png

15 )综合并生成 bit文件

此图片的alt属性为空;文件名为image-27.png

成功后会出现下图对话框,点OK 确认就好

此图片的alt属性为空;文件名为image-21.png

四 、PL部分访问DDR 实机演示

其实工程到这里即使没有SDK部分工程,PL也已经可以正常访问DDR了,下面简单的演示一下

用TYPE C数据线将板子的JTAG口和电脑连接,给板子通上电源后,点击 PROGRAM AND DEBUG 中的OPEN TARGET 下的Auto Connect 来连接板子

如果连接正常会在右侧 硬件栏中显示设备,如下图所示

此图片的alt属性为空;文件名为image-19.png

接下来下载bit文件,右键设备 然后选择 Program Device

此图片的alt属性为空;文件名为image-51.png

默认会出现生成的BIT文件,如果没有的话需要手动添加路径 ,点击PROGRAM 下载

此图片的alt属性为空;文件名为image-35-1024x467.png

下载之后, 此时两个绿灯都是熄灭状态, 此时按下板子上的KEY3(转接板正中间的按键) PL开始对DDR进行读写操作,之后LED1灯被点亮(程序里定义的DONE 灯,代表读写操作完成),LED2(ERROR)错误提示灯保持熄灭状态, 证明我们的DDR读写实验成功,从DDR读出的数据和写入的数据是一致的。

五、PS部分工程创建

之前我们PL端的程序已经可以对DDR正常读写了,为什么还需要增加PS部分的代码呢, 这里我说明下PS部分是可以访问访问PL写入DDR地址的数据,本实验通过PS读取DDR数据来验证PL的写入是否成功,同时也为以后我们PS和PL的交互做铺垫。

1)File→Export→Export hardware…,在弹出的对话框中勾选“include bitstream”,点击“OK”确认,如下图所示。

此图片的alt属性为空;文件名为image-20.png
此图片的alt属性为空;文件名为image-24.png

2)File→Lauch SDK,在弹出的对话框中,保存默认,点击“OK”,如下图所示。

此图片的alt属性为空;文件名为image-21.png

系统将自动打开SDK开发环境

3)再创建一个APP 工程,负责PS读取DDR 部分的数据用以验证。

a. 创建一个新的空工程,可以取名叫PL_DDR_TEST

此图片的alt属性为空;文件名为image-18.png

b.右键工程的SRC目录,然后新建一个SOURCE FILE

此图片的alt属性为空;文件名为image-28.png

c.取名 main.c

此图片的alt属性为空;文件名为image-29.png

六、 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 自带的)

此图片的alt属性为空;文件名为image-40-1024x482.png

2) 打开Run as -> Run Configurations

此图片的alt属性为空;文件名为image-36.png

如果是第一次打开此页面,之前没有debug过,则双击system debugger选项

此图片的alt属性为空;文件名为image-37.png

3 ) 在右侧 的窗口勾选 Reset entire system ,以及Program FPGA, 这样每次debug 的时候都会预先加载并配置FPGA。

此图片的alt属性为空;文件名为image-38-1024x720.png

4 ) 之后系统就会自动运行了(fpga也会被自动加载) 。 如果修改程序后 再次要debug,就可以直接点绿色箭头,或者选 Run As –>Launch on Hardware (system debugger即可)

此图片的alt属性为空;文件名为image-39-1024x655.png

5) 程序工作后,会通过串口输出 AXI4 PL DDR TEST ! 以及 Please input A to start 的内容, 此时按下键盘上的A,并按下Send, 向系统发送开始命令

此图片的alt属性为空;文件名为image-41.png

之后,系统通过串口将从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修改即可,大家自行研究。

以下是本文的完整工程,仅供参考:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注