DDR3基本的读写测试,适用于verilog语言学习

        近期学习使用Verilog编写DDR3接口读写测试,在编写过程中遇到许多问题,最终功夫不负有心人,实现了DDR3数据写入和数据读取功能。同时在问题排查过程中,也学习到了很多新的东西。

        现将我实现DDR3读写测试功能过程与大家一起分享,在此感谢我的朋友们对我的指点和帮助。在本例中只讲解如何添加IP核,实现DDR3数据的读写功能。因作者水平有限,文档中难免会有疏漏之处,欢迎读者批评指正。

目录

1:使用器件

2:开发环境

3:IP核添加

 4:实例工程生成

 5:实例工程讲解

6:参考代码

7:仿真测试


1:使用器件

        a):FPGA器件型号:xc7vx485tffg1761-2;

        b):DDR3器件型号:MT41K256M16XX-125;

2:开发环境

        a):开发环境:Vivado 2017.2

        b):开发语言:Verilog

3:IP核添加

        在新建工程中添加IP核,添加步骤如下所示:

        a):点击project manager 菜单下IP Catalog选项,打开IP界面;

DDR3基本的读写测试,适用于verilog语言学习_第1张图片

b):在IP核界面搜索栏输入mig ,会自动出现Memory Interface Generator选项,双击该选项进入到DDR配置界面;

DDR3基本的读写测试,适用于verilog语言学习_第2张图片

 c):等待Vivado运行完成,会出现Memory Interface Generator配置界面,检查器件型号等信息无误后,点击next,(左下角选项 User Guide可以点击下载官方资料文件);

DDR3基本的读写测试,适用于verilog语言学习_第3张图片

d):选择Create Design,Component Name选项可以修改名称,Number of Controllers选项可以选择DDR的片数,本例中只选择一片,配置完成后点击next;

DDR3基本的读写测试,适用于verilog语言学习_第4张图片

 e):此处不选择其他型号,点击next;

DDR3基本的读写测试,适用于verilog语言学习_第5张图片

 f): 本例器件类型为DDR3,选择DDR3 SDRAM,点击next;

DDR3基本的读写测试,适用于verilog语言学习_第6张图片

 g): clock period选项选择1250ps,800MHz时钟,Memory Type选项中选择Components(贴片式组件),Memory Part选择:MT41K256M16XX-125,点击Create Custom Part,在出现的对话框Enter New Memory Part Name选项中输入MT41K512M16XX-125,Row Address下拉选项中选择16位宽(这里设置按照我的硬件资源环境设置的,可以根据个人硬件环境进行调整,在后面代码中修改位宽即可),设置完成后点击next;

DDR3基本的读写测试,适用于verilog语言学习_第7张图片

DDR3基本的读写测试,适用于verilog语言学习_第8张图片

 h):在Input Clock Period选项中选择5000ps,200MHz,完成后点击next;

DDR3基本的读写测试,适用于verilog语言学习_第9张图片

  i):System Clock选项选择No Buffer,Reference Clock选择Use System Clock,设置完成后点击next;

DDR3基本的读写测试,适用于verilog语言学习_第10张图片

 j):默认设置,点击next;

DDR3基本的读写测试,适用于verilog语言学习_第11张图片

  k):选择New Deign,本例不提供引脚配置文件,点击next;

DDR3基本的读写测试,适用于verilog语言学习_第12张图片

  l):采用默认分配,点击next;

DDR3基本的读写测试,适用于verilog语言学习_第13张图片

  m):采用默认分配,点击next;

DDR3基本的读写测试,适用于verilog语言学习_第14张图片

  n):在界面中检查配置选项是否存在错误,确认无误后点击next;

DDR3基本的读写测试,适用于verilog语言学习_第15张图片

 o):点击选择Accept,点击next,接下来几个页面都可以直接点击next;

DDR3基本的读写测试,适用于verilog语言学习_第16张图片

 p):等待vivado运行完成,出现生成界面,点击Generate;

DDR3基本的读写测试,适用于verilog语言学习_第17张图片

q):等待vivado完成IP核生成;

DDR3基本的读写测试,适用于verilog语言学习_第18张图片

 r):继续等待vivado运行完成,运行完成后IP核就添加完成;

DDR3基本的读写测试,适用于verilog语言学习_第19张图片

 4:实例工程生成

a):添加IP核完成后,在sources管理界面下方选择IP sources界面,选中IP核。

DDR3基本的读写测试,适用于verilog语言学习_第20张图片

 b):点击右键,选择Open IP Example Design打开实例工程;

DDR3基本的读写测试,适用于verilog语言学习_第21张图片

 c):选择实例工程保存位置,点击ok后vivado会自动运行生成一个实例工程文件;

DDR3基本的读写测试,适用于verilog语言学习_第22张图片

 d):运行完成后打开工程文件,生成的实例工程文件是可以直接进行仿真的,点击project manager 管理栏中的run simulation即可进行仿真。我们的重点不是查看官方的仿真实例,我们要自己编写一个读写实例,现在可以关掉IP核的工程文件,重点放在实例工程上;

DDR3基本的读写测试,适用于verilog语言学习_第23张图片

 5:实例工程讲解

a):双击打开example_top文件,里面有很多描述和参数定义,输入输出端口定义,这些都不用修改,了解一下就好。往下翻到第412行Application interface ports,重点关注第413行到423行,这些端口都是数据的读写需要用到的端口 。可以在文件中查看端口的定义,有助于后面写代码端口的定义。第450行以后的代码都可以删掉,不用生成的实例文件,注意不要把endmodule删除了;

DDR3基本的读写测试,适用于verilog语言学习_第24张图片

 b):在工程中添加一个新的文件,用于编写我们自己的DDR3读写模块;

DDR3基本的读写测试,适用于verilog语言学习_第25张图片

 c):在编写代码前我们还是要分析一下DDR3的读写时序要求,下图为写数据时的时序要求,内容来源于前面Use rGuide下载问文档里面。

DDR3基本的读写测试,适用于verilog语言学习_第26张图片

 d):从上面写数据时序图可以看出,写数据需要10个输入输出信号来实现。我们来一一讲解信号的作用:

        clk:系统时钟;

        app_cmd:读写操作命令,3'b000为写操作,3'b001为读操作;

        app_addr:地址线,写数据地址和读数据地址都走这条线;

        app_en:地址线有效使能信号,高有效;

        app_rdy:DDR3给你的信号,为高时表明UI已准备好接受命令;

        app_wdf_mask:app_wdf_data 的掩码;

        app_wdf_rdy:DDR3给你的信号,为高时表明写入数据FIFO已准备好接收数据;

        app_wdf_data:写入的数据;

        app_wdf_wren:数据有效使能信号,高有效;

        app_wdf_end:高电平有效输入,表明当前时钟周期是app_wdf_data 上输入数据的最后一个周期,一般情况下可让app_wdf_end == app_wdf_wren;

e):对于这10个信号又可分为2个系统,地址系统和数据系统,clk为系统时钟。

        地址系统:app_cmd,app_addr,app_en,app_rdy;

        数据系统:app_wdf_mask,app_wdf_rdy,app_wdf_data,app_wdf_wren,app_wdf_end;  

        地址系统只关联地址有关的信号,数据系统就关联数据有关的信号,两者之间不应纠缠在一起,不然容易造成一些bug出现。从写数据时序图还可以看出,地址系统与数据系统之间的时序关系有三种:

        第一种:地址与数据严格对其;

        第二种:数据比地址先给一个周期;

        第三种:数据比地址最多延迟2个周期;

        地址系统有关信号严格按照时序要求对齐,数据系统有关信号严格按照时序要求对齐,地址系统与数据系统之间选择哪种关系,根据个人需求进行选择。

f):地址系统信号之间的关系:

        当DDR3发出app_rdy信号时表明已经准备好接收命令,可以发送app_cmd命令和app_addr地址,同时标志地址有效app_en信号也要同时发出,只有app_en为高时写入的地址才是有效的。

       数据系统信号之间关系:

        当DDR3发出app_wdf_rdy信号时,表明写入数据FIFO准备好接收数据,这时就要将要写入的数据发送给app_wdf_data,写入多少个地址就只能写入多少个数据,多给的数据会丢失。在写入app_wdf_data同时拉高app_wdf_wren信号,只有app_wdf_wren为高时写入的数据才是有效的。前面文章中讲过app_wdf_end == app_wdf_wren,所以可以将app_wdf_end进行app_wdf_wren同样的处理。app_wdf_mask信号为app_wdf_data数据的掩码,app_wdf_mask为1的位对应的数据将被屏蔽掉,本例中不使用掩码,可以赋值为全0(要注意app_wdf_mask信号的位宽);

 g):读数据时序要求,下图为读数据时序图。

DDR3基本的读写测试,适用于verilog语言学习_第27张图片

        通过上图可以看出,读数据也需要用到地址系统,但是读数据只需要你给出地址和读出的数据个数就可以,数据个数可以理解为给了多少个地址,这个可以自由决定。发送地址后不是马上读出数据,从上图也可以看出发送完addr0之后时钟信号用双波浪符号表示中间省略多少个周期,毕竟DDR3也需要反应时间。DDR3准备好数据后,就会通过app_rd_data端口发出数据,同时给出app_rd_data_valid信号,只有 app_rd_data_valid为高时读出的数据才是有效数据;

h):读写时序要求就分析这么多,分析的比较简单,重在理解。需要详细分析,请在CSND里面搜索其他作者写的时序分析文章,这里就不在过多赘述。到此IP核、DDR3实例工程、时序分析都已完成,剩下的就是编写代码实现功能了,这个就交给天赋异禀的读者了,后面我会提供我的实例代码,供读者参考。

6:参考代码

        以下代码可实现DDR3读写数据功能。

`timescale 1ns / 1ps
module ddr_app_module (

    input                     										i_sys_clk,										//时钟
    input                     										i_sys_rst,										//复位		
    input                     										init_calib_complete,							//DDR3初始完成					
    input                     										app_rdy,										//app_rdy		
    output  	reg   		[ 2 : 0 ]       						app_cmd,										//读写命令		
    output  	reg               									app_en,											//地址en	
    output  	reg   		[ 29 : 0 ]      						app_addr,										//操作地址		
    input                     										app_wdf_rdy,									//app_wdf_rdy			
    output  	reg               									app_wdf_end,									//app_wdf_end			
    output  	reg               									app_wdf_wren,									//写数据有效en			
    output  	reg      	[ 127 : 0 ]     						app_wdf_data,									//写数据			
    input         			[ 127 : 0 ]     						app_rd_data,									//读数据			
	input                     										app_rd_data_valid                               //读数据有效
    );
				localparam											TEST_LEN					= 8'd99;
				reg			[ 29 : 0 ]								write_addr;
				reg			[ 29 : 0 ]								read_addr;
				reg													app_write_req;
				reg													app_write_req_r0;
				reg													app_write_req_r1;
				reg													write_data_en;
				reg													app_read_req;
				reg													app_read_req_r0;
				reg													app_read_req_r1;
				reg													read_data_en;
				reg			[ 7 : 0 ]								write_addr_cnt;
				reg			[ 7 : 0 ]								write_data_cnt;
				reg			[ 7 : 0 ]								read_addr_cnt;
				reg			[ 7 : 0 ]								read_data_cnt;
always @ ( posedge i_sys_clk ) begin																				//写数据请求
	if ( init_calib_complete == 1'b0 ) begin
		app_write_req	 <= 1'b0;
		app_write_req_r0 <= 1'b0;
		app_write_req_r1 <= 1'b0;
	end else begin
		app_write_req_r0 <= init_calib_complete;
		app_write_req_r1 <= app_write_req_r0;
		app_write_req 	 <= app_write_req_r0 & ( ~app_write_req_r1 );
	end
end
always @ ( posedge i_sys_clk ) begin																				//写数据en
	if ( init_calib_complete == 1'b0 ) begin
		write_data_en <= 1'b0;
	end else begin 
		if ( app_write_req == 1'b1 ) begin
			write_data_en <= 1'b1;
		end else begin
			if ( write_addr_cnt == TEST_LEN && app_rdy == 1'b1 ) begin
				write_data_en <= 1'b0;
			end else begin
				write_data_en <= write_data_en;
			end
		end
	end
end
always @ ( posedge i_sys_clk ) begin																				//读数据请求
	if ( init_calib_complete == 1'b0 ) begin
		app_read_req	<= 1'b0;
		app_read_req_r0 <= 1'b0;
		app_read_req_r1 <= 1'b0;
	end else begin
		app_read_req_r0 <= write_data_en;
		app_read_req_r1 <= app_read_req_r0;
		app_read_req 	<= app_read_req_r1 & ( ~app_read_req_r0 );
	end
end
always @ ( posedge i_sys_clk ) begin																				//读数据en
	if ( init_calib_complete == 1'b0 ) begin
		read_data_en <= 1'b0;
	end else begin 
		if ( app_read_req == 1'b1 ) begin
			read_data_en <= 1'b1;
		end else begin
			if ( read_addr_cnt == TEST_LEN && app_rdy == 1'b1 ) begin
				read_data_en <= 1'b0;
			end else begin
				read_data_en <= read_data_en;
			end
		end
	end
end
always @ ( posedge i_sys_clk ) begin																				//app_en信号产生
	if ( init_calib_complete == 1'b0 ) begin
		app_en <= 1'b0;
	end else begin 
		if ( app_write_req == 1'b1 || app_read_req == 1'b1 ) begin
			app_en <= 1'b1;
		end else begin
			if ( ( write_addr_cnt == TEST_LEN ||  read_addr_cnt == TEST_LEN ) && app_rdy == 1'b1  ) begin
				app_en <= 1'b0;
			end else begin
				app_en <= app_en;
			end
		end
	end
end
always @ ( posedge i_sys_clk ) begin																				//app_amd命令产生
	if ( init_calib_complete == 1'b0 ) begin
		app_cmd <= 3'b000;
	end else begin 
		if ( read_addr_cnt == TEST_LEN && app_rdy == 1'b1 ) begin
			app_cmd <= 3'b000;
		end else begin
			if ( app_read_req == 1'b1 ) begin
				app_cmd <= 3'b001;
			end else begin
				app_cmd <= app_cmd;
			end
		end
	end
end
always @ ( posedge i_sys_clk ) begin																				//写数据地址
	if ( init_calib_complete == 1'b0 ) begin
		write_addr <= 30'h0;
	end else begin 
		if ( write_data_en == 1'b1 && app_rdy == 1'b1 ) begin
			write_addr <= write_addr + 4'h8;
		end else begin
			if ( write_addr_cnt == TEST_LEN && app_rdy == 1'b1 ) begin
				write_addr <= write_addr + 4'h8;
			end else begin
				write_addr <= write_addr;
			end
		end
	end
end
always @ ( posedge i_sys_clk ) begin																				//读数据地址
	if ( init_calib_complete == 1'b0 ) begin
		read_addr <= 30'h0;
	end else begin 
		if ( read_data_en == 1'b1 && app_rdy == 1'b1 ) begin
			read_addr <= read_addr + 4'h8;
		end else begin
			if ( read_addr_cnt == TEST_LEN && app_rdy == 1'b1 ) begin
				read_addr <= read_addr + 4'h8;
			end else begin
				read_addr <= read_addr;
			end
		end
	end
end
always @ ( posedge i_sys_clk ) begin																				//app_addr数据地址
	if ( init_calib_complete == 1'b0 ) begin
		app_addr <= 30'h0;
	end else begin 
		if ( app_write_req == 1'b1 ) begin
			app_addr <= write_addr;
		end else begin
			if ( app_read_req == 1'b1 ) begin
				app_addr <= read_addr;
			end else begin
				if ( write_data_en == 1'b1 && app_rdy == 1'b1 ) begin
					app_addr <= write_addr + 4'h8;
				end else begin
					if ( read_data_en == 1'b1 && app_rdy == 1'b1 ) begin
						app_addr <= read_addr + 4'h8;
					end else begin
						app_addr <= app_addr;
					end
				end
			end
		end
	end
end
always @ ( posedge i_sys_clk ) begin																				//写地址计数器
	if ( init_calib_complete == 1'b0 ) begin
		write_addr_cnt <= 8'd0;
	end else begin 
		if( write_addr_cnt == TEST_LEN && app_rdy == 1'b1 ) begin
			write_addr_cnt <= 8'd0;
		end else begin
			if ( write_data_en == 1'b1 && app_rdy == 1'b1 ) begin
				write_addr_cnt <= write_addr_cnt + 1'b1;
			end else begin
				write_addr_cnt <= write_addr_cnt;
			end
		end
	end
end
always @ ( posedge i_sys_clk ) begin																				//读地址计数器
	if ( init_calib_complete == 1'b0 ) begin
		read_addr_cnt <= 8'd0;
	end else begin 
		if( read_addr_cnt == TEST_LEN && app_rdy == 1'b1 ) begin
			read_addr_cnt <= 8'd0;
		end else begin
			if ( read_data_en == 1'b1 && app_rdy == 1'b1 ) begin
				read_addr_cnt <= read_addr_cnt + 1'b1;
			end else begin
				read_addr_cnt <= read_addr_cnt;
			end
		end
	end
end
always @ ( posedge i_sys_clk ) begin																				//写数据有效
	if ( init_calib_complete == 1'b0 ) begin
		app_wdf_wren <= 1'b0;
	end else begin 
		if ( write_addr_cnt == TEST_LEN && app_wdf_rdy == 1'b1 ) begin
			app_wdf_wren <= 1'b0;
		end else begin
			if ( app_write_req == 1'b1 ) begin
				app_wdf_wren <= 1'b1;
			end else begin
				app_wdf_wren <= app_wdf_wren;
			end
		end
	end
end
always @ ( posedge i_sys_clk ) begin																				//app_wdf_end == app_wdf_wren
	if ( init_calib_complete == 1'b0 ) begin
		app_wdf_end <= 1'b0;
	end else begin 
		if ( write_addr_cnt == TEST_LEN && app_wdf_rdy == 1'b1 ) begin
			app_wdf_end <= 1'b0;
		end else begin
			if ( app_write_req == 1'b1 ) begin
				app_wdf_end <= 1'b1;
			end else begin
				app_wdf_end <= app_wdf_end;
			end
		end
	end
end
always @ ( posedge i_sys_clk ) begin																				//产生写入的数据
	if ( i_sys_rst == 1'b0 ) begin
		app_wdf_data <= 128'h0;
	end else begin
		if ( app_wdf_rdy == 1'b1 && app_wdf_wren == 1'b1 ) begin
			app_wdf_data <= app_wdf_data + 1'b1;
		end else begin
			app_wdf_data <= app_wdf_data;
		end
	end
end
always @ ( posedge i_sys_clk ) begin																				//写数据计数器
	if ( i_sys_rst == 1'b0 ) begin
		write_data_cnt <= 8'd0;
	end else begin
		if ( write_data_cnt == TEST_LEN && app_wdf_rdy == 1'b1 ) begin
			write_data_cnt <= 8'd0;
		end else begin
			if ( app_wdf_wren == 1'b1 && app_wdf_rdy == 1'b1 ) begin
				write_data_cnt <= write_data_cnt + 1'b1;
			end else begin
				write_data_cnt <= write_data_cnt;
			end
		end
	end
end
always @ ( posedge i_sys_clk ) begin																				//读数据计数器
	if ( i_sys_rst == 1'b0 ) begin
		read_data_cnt <= 8'd0;
	end else begin
		if ( read_data_cnt == TEST_LEN && app_rdy == 1'b1 ) begin
			read_data_cnt <= 8'd0;
		end else begin
			if ( app_rd_data_valid == 1'b1 ) begin
				read_data_cnt <= read_data_cnt + 1'b1;
			end else begin
				read_data_cnt <= read_data_cnt;
			end
		end
	end
end
endmodule

7:仿真测试

        本例中测试数据长度为100,写入0~99,读出0~99即为读写数据正确,仿真结果如下,供参考。DDR3数据读写测试讲解到此结束,感谢各位读者阅读,希望对你有所帮助。后附工程文件下载链接,欢迎下载测试。

DDR3基本的读写测试,适用于verilog语言学习_第28张图片

DDR3基本的读写测试,适用于verilog语言学习_第29张图片

DDR3基本的读写测试,适用于verilog语言学习_第30张图片

工程文件下载链接: 

https://download.csdn.net/download/ForeveryMissYou/21358108

 

你可能感兴趣的:(FPGA,Verilog,fpga开发,ddr)