ZYNQ-使用自定义AXI总线IP核进行DDR读写测试

本文详细介绍了如何使用Vivado设计自定义IP进行DDR的读写操作,通过AXI总线接口实现数据传输,并在SDK中编写C代码进行读写测试。开发环境为Vivado18.3和PYNQ-Z2开发板。在硬件平台上,设计了按键消抖模块以消除按键抖动对读写操作的影响。在SDK中,通过主函数读取DDR中的数据并显示,实现了数据的读取验证。此外,还展示了如何进行硬件调试,包括ILA调试和波形分析,以辅助问题排查。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习内容

本文首先进行自定义IP的AXI总线IP的设计,然后在SDK下编写代码进行DDR的读写数据的测试。

开发环境

vivado 18.3&SDK
PYNQ-Z2开发板

系统框图

首先对本次工程进行简要说明:本次工程使用AXI-Full接口的IP进行DDR的读写测试。在我们的DDR读写IP中,我们把读写完成和读写错误信号关联到PL端的LED上,用于指示DDR读写IP的读写运行状态。然后使用PL部分消抖处理后的按键进行启动AXI总线工作,控制数据写入。通过AXI互联模块连接到AXI_HP0端口,由PS端口进行数据的读取操作,并通过串口进行读写数据的监控。
在这里插入图片描述

自定义IP设计

首先打开Vivado软件,在Tasks这里选择New IP lacation
在这里插入图片描述
点击next,对IP的信息进行设置,这里我们使用默认配置即可。设置好我们IP要保存的位置。
在这里插入图片描述
点击Tools中的创建和封装新的IP选项,在这里插入图片描述
点击NEXT ,选择我们的封装类型。因为这里我们是直接进行打开IP设计的界面,前两个选项是可以在我们的vivado当前工程下面进行封装设计,这里我们只进行了IP设计没有建立工程,所以前两个选项是无法选中的。我们也可以通过工程界面,进入点击Tools中的创建和封装新的IP选项。
在这里插入图片描述
这里是用DDR读写IP来做主机,控制数据写入,PS作为从机进行读取IP中写入的数据。
在这里插入图片描述
可以直接选中进行编辑IP,用户可以根据自己的设计进行修改编辑IP的功能,这里没有对IP进行修改处理,所以可以直接保存选择第一个添加到IP库中即可。
在这里插入图片描述
若修改相应的逻辑功能打开IP,在对应位置编辑添加代码即可。
在这里插入图片描述
添加完成综合后对IP进行重新打包。DDR读写IP设计完成,创建的 IP 核将通过 AXI4 Master 端口向 Slave 端指定的 4K 存储空间中连续写入 1024 个数据, 写入的数值从 1 累加到 1024, 每个数据占 32bit。然后进行硬件平台的构建。

硬件平台构建

首先,添加ZYNQ7 IP核,以及添加已经完成设计的ddr读写IP核。

添加用户自定义IP

用户自定义的IP可通过以下步骤完成添加。点击Settings,
在这里插入图片描述
在project settings选择IP,依次点击,在IP库那里点击加号,把对应的IP目录文件夹添加后,点击OK或者Apply即可完成添加,在IP库中就可以找到用户设计的IP。
在这里插入图片描述
完成IP和ZYNQ7 IP的导入后,如下图:
在这里插入图片描述
双击打开zynq删除多余的接口,这里只需要保留uart,并打开Slave HP0端口、时钟、复位端口。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
配置完,选择自动连接接口,完成部分连接设计。
在这里插入图片描述
整体设计图如下,
在这里插入图片描述

添加按键消抖IP

在这里插入图片描述
由于ddr读写IP的axi_init_axi_txn接入的是按键,这里按键按下会产生抖动,axi_init_axi_txn与好多读写信号关联,如果不添加消抖IP,在按键按下的时,产生的毛刺会进行影响后续的操作,从而导致读写操作的错误,也就是读写操作的指示灯会亮起。
在这里插入图片描述
系统复位后, 状态机处于初始状态,在该状态下等待外部输入的启动传输脉冲 init_txn_pulse。一旦检测到 init_txn_pulse 为高电平,状态机跳转到 INIT_WRITE 状态。
在 INIT_WRITE 状态下, 状态机拉高 start_single_burst_write 信号, 来不断地启动 AXI4 Master 接口对Slave 端大小为 4KB 的存储空间进行突发写操作。写操作完成后, write_done 信号会拉高,状态机进入INIT_READ 状态。
在 INIT_READ 状态下, 状态机拉高 start_single_burst_read 信号, 不断地启动 AXI4 Master 接口对 Slave端同一存储空间进行突发读操作, 同时将读出的数据与写入的数据进行对比。读操作完成后, read_done 信号拉高,状态机进入 INIT_COMPARE 状态。
在 INIT_COMPARE 状态下, 判断 AXI4 接口在读写过程中的是否发生错误, 并将错误状态赋值给ERROR 信号, 然后将 compare_done 信号拉高,表示一次读写测试完成。最后跳转到 IDLE 状态,等待下一次读写操作的启动信号。
这里的消抖模块直接添加之前写过的按键消抖模块即可,这里给出我的设计:

module key_filter(
			Clk,      //50M时钟输入
			Rst_n,    //模块复位
			key_in,   //按键输入
			key_flag, //按键标志信号
			key_state //按键状态信号
		);

	input Clk;
	input Rst_n;
	input key_in;
	
	output reg key_flag;
	output reg key_state;
	
	localparam
		IDEL		= 4'b0001,
		FILTER0		= 4'b0010,
		DOWN		= 4'b0100,
		FILTER1 	= 4'b1000;
		
	reg [3:0]state;
	reg [19:0]cnt;
	reg en_cnt;	//使能计数寄存器
	
//对外部输入的异步信号进行同步处理
	reg key_in_sa,key_in_sb;
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		key_in_sa <= 1'b0;
		key_in_sb <= 1'b0;
	end
	else begin
		key_in_sa <= key_in;
		key_in_sb <= key_in_sa;	
	end
	
	reg key_tmpa,key_tmpb;
	wire pedge,nedge;
	reg cnt_full;//计数满标志信号
	
//使用D触发器存储两个相邻时钟上升沿时外部输入信号(已经同步到系统时钟域中)的电平状态
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		key_tmpa <= 1'b0;
		key_tmpb <= 1'b0;
	end
	else begin
		key_tmpa <= key_in_sb;
		key_tmpb <= key_tmpa;	
	end

//产生跳变沿信号	
	assign nedge = !key_tmpa & key_tmpb;
	assign pedge = key_tmpa & (!key_tmpb);
	
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)begin
		en_cnt <= 1'b0;
		state <= IDEL;
		key_flag <= 1'b0;
		key_state <= 1'b1;
	end
	else begin
		case(state)
			IDEL :
				begin
					key_flag <= 1'b0;
					if(nedge)begin
						state <= FILTER0;
						en_cnt <= 1'b1;
					end
					else
						state <= IDEL;
				end
					
			FILTER0:
				if(cnt_full)begin
					key_flag <= 1'b1;
					key_state <= 1'b0;
					en_cnt <= 1'b0;
					state <= DOWN;
				end
				else if(pedge)begin
					state <= IDEL;
					en_cnt <= 1'b0;
				end
				else
					state <= FILTER0;
					
			DOWN:
				begin
					key_flag <= 1'b0;
					if(pedge)begin
						state <= FILTER1;
						en_cnt <= 1'b1;
					end
					else
						state <= DOWN;
				end
			
			FILTER1:
				if(cnt_full)begin
					key_flag <= 1'b1;
					key_state <= 1'b1;
					state <= IDEL;
					en_cnt <= 1'b0;
				end
				else if(nedge)begin
					en_cnt <= 1'b0;
					state <= DOWN;
				end
				else
					state <= FILTER1;
			
			default:
				begin 
					state <= IDEL; 
					en_cnt <= 1'b0;		
					key_flag <= 1'b0;
					key_state <= 1'b1;
				end
				
		endcase	
	end
	

	
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		cnt <= 20'd0;
	else if(en_cnt)
		cnt <= cnt + 1'b1;
	else
		cnt <= 20'd0;
	
	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		cnt_full <= 1'b0;
	else if(cnt == 20'd999_999)
		cnt_full <= 1'b1;
	else
		cnt_full <= 1'b0;	

endmodule

添加完模块后系统设计如下:
注:axi_init_axi_txn是上升沿有效,这里为了保证系统上电后是初始默认随机状态,要确保按键未按下给启动脉冲时,是低电平。因为PYNQZ2开发板按键默认电位是低,按下为高,这里不用进行处理,若按键按下后为低,默认拉高,这里可以对按键进行添加非逻辑的IP进行取反。(使用 utility vector logic IP完成配置)

在这里插入图片描述
双击DDR读写的IP核进行配置,这里没有用到user的接口所以都设置为0,如图:
在这里插入图片描述
对于Base address,我们可以查看下接口的地址范围,避免数据操作超过可操作范围,这里我们就取中间值0X10000000。
在这里插入图片描述
然后我们进行generate output product 然后生成HDL封装。接着就对应引脚进行引脚约束即可(PYNQ的粉色开发板可以直接引用这个约束):

##LEDs
set_property -dict { PACKAGE_PIN R14   IOSTANDARD LVCMOS33 } [get_ports { m0_axi_error_0 }]; #IO_L6N_T0_VREF_34 Sch=led[0]
set_property -dict { PACKAGE_PIN P14   IOSTANDARD LVCMOS33 } [get_ports { m0_axi_txn_done_0}]; #IO_L6P_T0_34 Sch=led[1]

##Buttons
set_property -dict { PACKAGE_PIN D19   IOSTANDARD LVCMOS33 } [get_ports { key }]; #IO_L4P_T0_35 Sch=btn[0]

完成约束后进行综合布局布线,等待生成bit流文件。
在这里插入图片描述
bit文件生成后在FILE处,点击导出硬件资源(包含bit流文件),接着launch SDK。

SDK软件部分

打开SDK后,新建application project。在main.c中输入以下代码:

#include "stdio.h"
#include "xil_cache.h"
#include "xil_printf.h"
#include "xil_io.h"
int main(){
	int i;
	char chardata;
	Xil_DCacheDisable();
	printf("AXI4-FULL RW TEST~\n\r");
	while(1){
		scanf("%c",chardata);
		if(chardata="y"){
			printf("start\r\n");
			for(i=0;i<4096;i=i+4){
				printf("%d is %d\n",i,(int)(Xil_In32(0x10000000+i)));
			}
		}
	}
	return 0;
}

代码简要说明

这里使用的IP我们设定成不需要进行缓存的,所以在main函数中调用Xil_DCacheDisable();。使用Xil_In32(),对DDR对应位置的数据进行读取。参数只需要传递所要读取的地址即可。因为一次写入的数据是32位的,每个地址的数据位宽是8位,所以在for循环中使用了i=i+4

在串口中使用printf("%d is %d\n",i,(int)(Xil_In32(0x10000000+i)));对相应地址的数据进行读取显示。

运行效果

当按键未按下时,也就是未进行写入操作直接读取数据,DDR中的数据是默认的随机状态,如下所示:
在这里插入图片描述

当按键按下后,IP完成数据写入操作,数据是从1-1024自增的
在这里插入图片描述

如何波形进行debug?

这里我们选中要进行DEBUG的数据信号右击选中debug
在这里插入图片描述
然后点击自动连接,完成debug功能搭建
在这里插入图片描述
综合后会多出ILA的IP进行波形分析帮助DEBUG。

在这里插入图片描述
然后打开硬件设备,如下图在ila界面即可看到我们debug的波形数据了。
在这里插入图片描述
添加触发条件,标号2是单次触发,标号1是一直运行debug抓取波形。
在这里插入图片描述

### Zynq 平台 DDR3 内存测试的方法和工具 #### 测试方法概述 对于Zynq平台上DDR3内存的测试,通常会采用多种方式来验证其稳定性和性能。一种常见的方式是在开发板上运行特定的应用程序来进行功能性测试。这些应用程序可以包括但不限于`helloworld`应用文件、`lwip Echo server`以及专门针对DRAM的功能测试程序[^1]。 #### 使用专用测试软件包 为了更全面地评估DDR3 SDRAM的工作状态,还可以编写或使用现有的测试套件如`Memory Tests`和`zynq DRAM tests`等。这类测试往往涉及向DDR3存储器写入数据模式再读取回来比较差异,以此检测是否存在错误位或其他异常情况。此外,也可以创建一个名为`wr_rd_test.c`的新源文件用于实现自定义读写操作逻辑,并将其集成到现有项目中以便于调试与分析[^2]。 #### 中断机制辅助测试 当涉及到更加复杂的交互场景时,比如实时处理外部事件的同时访问DDR3资源,则可能需要用到ZYNQ内部的通用中断控制器(Generic Interrupt Controller, GIC)[^3]。通过合理配置GIC及其关联组件(例如定时器),可以在不影响正常业务流程的前提下周期性触发对DDR3模块的状态查询或是执行预设指令序列从而达到监控目的。 ```c // 示例代码片段:简单展示如何初始化并使能GIC以支持后续基于中断的操作 void init_gic(void){ XScuGic_Config *Config; int Status; Config = XScuGic_LookupConfig(XPAR_SCUGIC_SINGLE_DEVICE_ID); if (NULL == Config) { return XST_FAILURE; } Status = XScuGic_CfgInitialize(&gic_instance, Config, Config->CpuBaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; } // 进一步设置具体外设产生的IRQ号对应的Handler函数... } ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FPGA and ICer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值