PL读写DDR3 实现PS和PL间的数据交互

本文构建一个AXI4-Lite Master IP来实现PL读写DDR3。最后用sdk 程序验证pl 读写过程中写入的数据。

本文参考 https://www.eefocus.com/antaur/blog/17-08/423773_0818c.html 学习而来,但我添加了数据验证部分。

我的另篇博文是:zynq 7000 自定义IP 实验https://blog.csdn.net/leon_zeng0/article/details/78674832 

对于理解本文也有很多帮助。那篇是 构建一个AXI4-Lite Slave IP,本文是AXI4-Lite Master IP。

对于代码的解说,请见:PL读写DDR3 实现PS和PL间的数据交互 代码分析

1:新建一个试验工程

这里只是简单介绍一下,详细的设置,可以查看我的上文:自定义IP 实验。

打开Vivado, 点击新建工程,工程取名 RWddr。

一路Next , 在硬件设置界面设置好你所用的硬件。

点击Create Block Design,取名system。

在原理图里右键添加ip , 添加ZYNQ Processing System. 添加完成后点击 Run Block Automation。

然后 点击图标, recustom ip, 设置处理器。首先设置好DDR,设置信息监控端口, UART1,

时钟设置比较重要,选择Clock Configuration,点开 IO Peripheral Clocks, PL Fabric Clocks, 检查FCLK_CLK0 是否已勾上,并且频率设置为100 MHZ。

PS-PL Configuration 这里是本文与上文自定义IP 实验的差异。上面红箭是上文中选择了的,使用 AXI4-Lite Slave IP 就要勾上一个。

下面的红箭则是因为我们要用到AXI4-Lite Master IP

PL读写DDR3 实现PS和PL间的数据交互_第1张图片

2:建立自定义IP 

菜单选择 Tools -> Create and Package IP... 。这里前面操作与上文基本一致。

这里说一下不同点:就是在下面这个界面里,Interface Mode不是选择 Slave, 而是Master

PL读写DDR3 实现PS和PL间的数据交互_第2张图片

在上文中,我介绍的是直接Edit IP, 这里介绍另个方法, 先 Add IP, 然后再编辑ip,在Add IP 后,选择左边Flow Navigator的  IP Catalog, 在右边原理图这边, IP Catalog , 选择User Repository, 可以看到我们加的 IP, 图中名字为 AzIP_AXI_Master-v1.0,在这个ip 上右键,选择Edit in Packager

PL读写DDR3 实现PS和PL间的数据交互_第3张图片

 3: 自定义ip 的设计

我们先来看看主界面。在 Design Source 下面有2个.v 文件。上面的AzIP_AXI_Master_v1_0.v 是顶层文件, top level,但他只是实现接口界面,并调用实例函数。所以真正实现是在 下面的文件, AzIP_AXI_Master_v1_0_M00_AXI.v, 当然这个文件主要是实现AXI接口。重要的逻辑实现需要我们另外添加自己的函数。但在这个例子,我们把这部分最简单化,只是看看他的代码,主要是学习他的读写流程。
PL读写DDR3 实现PS和PL间的数据交互_第4张图片

 我们再查看这个ip 的逻辑连线。这个操作是左边FLOW->RTL ANALYSIS->Schematic 得到。看这个图可以帮助我们了解输入输出的信息。总共有157 个io

PL读写DDR3 实现PS和PL间的数据交互_第5张图片

如果要看代码的中文讲解,可以查看 https://www.eefocus.com/antaur/blog/17-08/423751_6cc0d.html

这里不做过多介绍了。

除了读写的基本逻辑线外,程序里多了INIT_AXI_TXT, TXT_DONE, ERROR, 这3根应该算测试线吧。

本以为只有读写逻辑模块的,可能master 的特别性, 包含了一个测试例子。INIT_AXI_TXT发起,TXT_DONE结束, ERROR作为错误指示。

例子里的逻辑是写入DDR, 读取 DDR, 比较2个数据是否一致,不一致就ERROR指示,这样循环4个地址。

基地址由参数 parameter  C_M_TARGET_SLAVE_BASE_ADDR    = 32'h40000000 这个确定。偏置地址是0,4,8,12

如果要做出自己的ip ,估计要整理这个例子。也不是那么容易,这里就不动了。

4:添加ip,完成工程

ip add 选择添加刚才的工程, 添加完后点击 自动连线,然后 在上面3个端口 Make External, 用Validate Design(F6)检查原理图错误, 用原理图上方图标Regenerate Layout整理图形,最后的原理图如下:

PL读写DDR3 实现PS和PL间的数据交互_第6张图片

Generate Output Products, Create HDL  Wrapper, 然后添加约束文件,内容如下:

这个文件的端口号要与你硬件对应, m00_axi_error_0,m00_axi_txn_done_0 是输出,比如led, m00_axi_init_axi_txn_0 则是输入,比如按钮。

set_property IOSTANDARD LVCMOS33 [get_ports {m00_axi_error_0}]
set_property IOSTANDARD LVCMOS33 [get_ports {m00_axi_txn_done_0}]

set_property PACKAGE_PIN G15 [get_ports {m00_axi_error_0}]
set_property PACKAGE_PIN K16 [get_ports {m00_axi_txn_done_0}]

set_property IOSTANDARD LVCMOS33 [get_ports m00_axi_init_axi_txn_0]
set_property PACKAGE_PIN T19 [get_ports m00_axi_init_axi_txn_0]

产生 比特流, Export Hardware, launch SDK,

5: SDK 工程

建立一个 hello world 工程,Program fpga, debug 或 Run as  

可以看到 helloworld ,但这不是重要的。按按钮,激活axi_init_axi_txn, 然后观察报错,和完成指示灯。

我在读写完后done 亮, 错误不亮。这是最后结果的情况,开始没怎么注意。

在这里我们要注意一个对应关系,操作的ddr 地址是 C_M_TARGET_SLAVE_BASE_ADDR    = 32'h40000000开始的4个4字节。这里对吗?

如果写的范围不是有效的ddr 范围,估计会报错吧, 还有要避免写代码区和栈区。

6:查看数据

在上面这个简单helloworld 的程序,感觉没什么内容,我把以前的ddr 读写代码放这里,再来看看ddr 的ps 读写过程。

#include 
#include "platform.h"
#include "xil_printf.h"

#define MAXLENTH	512000000
unsigned char lasercmd[MAXLENTH];
//int ip_data;

void test_ram(void)
{
	int i,j;
	int* ip=(int*)lasercmd;
	printf("ip=%x\n",(int)ip);
	for(i=0;i<512000000/4;i++)
	{
		ip[i]=i*2;
	}
	for(i=0;i<51;i++)
	{
		printf("%d=%d\n",i,ip[i*1000000]);
	}
}

int main()
{
    init_platform();

    print("Hello World\n\r");
    test_ram();

    cleanup_platform();
    return 0;
}

程序运行的结果是:

Hello World
ip=114320
0=0
1=2000000
2=4000000

这个程序与我们实验有什么相关呢?ip=114320 (hex)

我们开始设置的地址根本就不在ddr 区,回到vivado 再重新设置

C_M_TARGET_SLAVE_BASE_ADDR    = 32'h00400000

还有起始数据 CM00 AXI START DATA VALUE = 0xA1002200

设置的界面如下,具体数据,你也可以不一样,但要保证读取范围在DDR里:.

PL读写DDR3 实现PS和PL间的数据交互_第7张图片

现在把测试代码修改一下,读取pl-ip 读写的区间,我在这里设置了断点的。显示完pl 读写区间,赋初值,断点,按一下按钮,显示pl 读写区间。

void test_ram(void)
{
	int i=0;
	int* ip=(int*)lasercmd;
	int* obj=(int*)0x400000;
	printf("ip=%x,obj=%x\n",(int)ip,(int)obj);
	printf("pl ddr=%x, %x %x %x\n",obj[0],obj[1],obj[2],obj[3]);

	for(i=0;i<512000000/4;i++)
	{
		ip[i]=i*2;
	}
	printf("2pl ddr=%x, %x %x %x\n",obj[0],obj[1],obj[2],obj[3]);
	for(i=0;i<512000000/4;i++)
	{
		if(ip[i]!=i*2)printf("i=%x, ip=%x\n",i,ip[i]);
	}
	for(i=0;i<51;i++)
	{
		printf("%d=%x\n",i,ip[i*1000000]);
	}
}

程序运行的显示结果部分如下:

ip=114320,obj=400000
pl ddr=175e70, 175e72 175e74 175e76
2pl ddr=a1002200, a1002201 a1002202 a1002203
i=baf38, ip=a1002200
i=baf39, ip=a1002201
i=baf3a, ip=a1002202
i=baf3b, ip=a1002203
0=0
1=1e8480

主程序里,对这个程序循环了几次,方便测试。可以看到  

2pl ddr=a1002200, a1002201 a1002202 a1002203

这个是pl 写入的数据的四个数据。

开始好像读取不到,所以我有段查找的代码,看这段空间里哪个数据与ps 写入的不一致,这里找到4个数据。

i=baf38, ip=a1002200
i=baf39, ip=a1002201
i=baf3a, ip=a1002202
i=baf3b, ip=a1002203

我们进行一下计算 (hex 运算):baf38*4=2ebce0

2ebce0+114320=400000

我觉得看灯,好像不能说明问题,既然是读写,至少看看写入的数据是不是预期的。

 

 

你可能感兴趣的:(fpga,zynq,zynq,fpga,ddr,axi,master)