在Nexys4 DDR上实现的DDR2读写例程

· 本文使用Vivado 2015.4在Nexys4 DDR(以下简称N4DDR)开发板上实现DDR的读写。
· FPGA如果需要对DDR进行读写,则需要一个DDR的控制器。根据官方的文档(UG586,下载链接在文末),DDR控制器的时序主要有三:
(1)首先是控制信号,如下图:
在Nexys4 DDR上实现的DDR2读写例程_第1张图片
· 从上图可以看出,只有当app_rdy信号有效时,程序所发出的读写命令才会被控制器接收。这点必须注意。
(2)然后是写操作时序,如下图:
在Nexys4 DDR上实现的DDR2读写例程_第2张图片
· 由图可知,在向DDR写数据时,需要提供写命令app_cmd、地址app_addr、数据app_wdf_data等信号,且写入的数据最多可以比app_cmd提前一个时钟周期有效,最迟可以比app_cmd晚两个时钟周期有效。
【特别注意】在写数据的时候必须检测app_rdy和app_wdf_rdy信号是否同时有效,否则写入命令无法成功写入到DDR控制器的命令FIFO中,从而导致写操作失败。
(3)最后是读操作时序,如下图所示:
在Nexys4 DDR上实现的DDR2读写例程_第3张图片
· 读操作的时序比较简单,只需要注意app_rdy是否有效即可,其余不再赘述。


· Xilinx在Vivado中提供的Memory Interface Generator的IP核就是我们需要的DDR控制器,如下图所示。
在Nexys4 DDR上实现的DDR2读写例程_第4张图片
· 这里我们可以直接双击上面的MIG的IP核,开始例化我们所需的DDR控制器。(此时Win7以后的Windows版本(不含Win7)打开此IP核会报错,解决方法见http://blog.csdn.net/qq_20091945/article/details/53862467)
· 打开后是如下图所示的界面,点Next。
在Nexys4 DDR上实现的DDR2读写例程_第5张图片
· 给模块起个名字,根据实际情况选择控制器数量(这里笔者选择1),继续Next,如下图所示。
在Nexys4 DDR上实现的DDR2读写例程_第6张图片
· 在开发板芯片型号所对应的方框前打勾,如下图所示。
在Nexys4 DDR上实现的DDR2读写例程_第7张图片
· 根据开发板上的DDR芯片选择DDR的种类,如N4DDR的开发板上的DDR芯片是DDR2的,因此如下图选择。
在Nexys4 DDR上实现的DDR2读写例程_第8张图片
· 然后在Clock Period中输入合适的时钟周期长度(N4DDR的官方文档建议DDR的时钟为325MHz,故此处填3077ps);
· 接着在Memory Part中选择开发板上的DDR芯片的具体型号(N4DDR官方文档上说明为MT47H64M16HR-25E);
· 然后输入Data Width,此处以16为例。如下图所示。
在Nexys4 DDR上实现的DDR2读写例程_第9张图片
· 选择Input Clock Period,这里填开发板的系统时钟(N4DDR为100MHz)。根据应用需要选择地址映射方式(这里保持默认的Bank-Row-Column)。
在Nexys4 DDR上实现的DDR2读写例程_第10张图片
· 然后,这里的System ClockReference Clock建议选择No Buffer,System Reset Polarity则根据应用需要灵活选择(这里设置为低电平有效),如下图所示。
在Nexys4 DDR上实现的DDR2读写例程_第11张图片
· Internal Termination Impedence的选取应当参考开发板的官方文档说明,这里选50欧姆即可,继续Next。
在Nexys4 DDR上实现的DDR2读写例程_第12张图片
· 选择Fixed Pin Out
在Nexys4 DDR上实现的DDR2读写例程_第13张图片
· 接下来是DDR芯片的引脚分配。官网应该能找到,这里直接给出。文末会给出与此对应的引脚约束文件(n4ddr_ddr2_io_assign.ucf)。
在Nexys4 DDR上实现的DDR2读写例程_第14张图片
在Nexys4 DDR上实现的DDR2读写例程_第15张图片
在Nexys4 DDR上实现的DDR2读写例程_第16张图片
· 耐心填完之后点击Validate按钮,没有错误的话会弹出一个对话框提示“Current Pinout is valid.”
· 然后的3个信号建议选择No connect,后面由我们自己根据需要连接到板上的相应引脚。
在Nexys4 DDR上实现的DDR2读写例程_第17张图片
· 后面一直Next下去,点Accept,然后就可以点击Generate了。后面会再弹出一个对话框,直接点默认选中的按钮即可。


· 好了,下面是笔者自己编写的测试DDR2读写的程序。文末将提供对应工程的下载链接。

//*****************************************************************************
// Author			: Z.M.J. @ CSE, SEU
// Application		: MIG v2.4
// Filename			: example_top.v
// Date Created		: Fri Dec 30 2016
//
// Device			: 7 Series (Nexys 4 DDR)
// Design Name		: DDR2 SDRAM
// Purpose			: A demo of DDR2's read and write
// Reference		: ug586_7Series_MIS_v2.4.pdf
//*****************************************************************************

`timescale 1ps/1ps

module example_top (
	// system signals
	input					sys_rst,
	input					sys_clk_i,
	// application signals
	input  [15:0]			switch_i,
	output [15:0]			led,
	output [7:0]			an,
	output [7:0]			select_seg,
	// DDR2 chip signals
	inout [15:0]			ddr2_dq,
	inout [1:0]				ddr2_dqs_n,
	inout [1:0]				ddr2_dqs_p,
	output [12:0]			ddr2_addr,
	output [2:0]			ddr2_ba,
	output					ddr2_ras_n,
	output					ddr2_cas_n,
	output					ddr2_we_n,
	output [0:0]			ddr2_ck_p,
	output [0:0]			ddr2_ck_n,
	output [0:0]			ddr2_cke,
	output [0:0]			ddr2_cs_n,
	output [1:0]			ddr2_dm,
	output [0:0]			ddr2_odt
);
	
	parameter DQ_WIDTH			= 16;
	parameter ECC_TEST			= "OFF";
	parameter ADDR_WIDTH		= 27;
	parameter nCK_PER_CLK		= 4;
	
	localparam DATA_WIDTH		= 16;
	localparam PAYLOAD_WIDTH	= (ECC_TEST == "OFF") ? DATA_WIDTH : DQ_WIDTH;
	localparam APP_DATA_WIDTH	= 2 * nCK_PER_CLK * PAYLOAD_WIDTH;
	localparam APP_MASK_WIDTH	= APP_DATA_WIDTH / 8;
	
	// Wire declarations
	reg app_en, app_wdf_wren, app_wdf_end;
	reg [2:0] app_cmd;
	reg [ADDR_WIDTH-1:0] app_addr;
	reg [APP_DATA_WIDTH-1:0] app_wdf_data;
	wire [APP_DATA_WIDTH-1:0] app_rd_data;
	wire [APP_MASK_WIDTH-1:0] app_wdf_mask;
	wire app_rdy, app_rd_data_end, app_rd_data_valid, app_wdf_rdy;
	
	
	
	//***************************************************************************
	wire [7:0] an;
	wire [7:0] select_seg;
	reg [31:0] digit_data;
	always@ (posedge sys_clk_i) begin
		if (switch_i[3])
			digit_data <= app_addr;
		else case (switch_i[1:0])
			2'b00 : digit_data <= read_data[31:0];
			2'b01 : digit_data <= read_data[63:32];
			2'b10 : digit_data <= read_data[95:64];
			2'b11 : digit_data <= read_data[127:96];
		endcase
	end
	
	digit U2(
		.wb_clk_i(sys_clk_i),
		.wb_rst_i(~sys_rst),
		.wb_dat_i(digit_data),
		.an(an),
		.select_seg(select_seg)
	);
	
	reg [1:0] read_valid = 2'b0;
	reg [127:0] read_data = 128'h0;
	always@ (posedge app_rd_data_valid) begin
		read_data = app_rd_data;
		read_valid[0] = (app_rd_data == data0);
		read_valid[1] = (app_rd_data == data1);
	end
	
	assign led[15] = app_en;
	assign led[14] = init_calib_complete;
	assign led[13] = app_rdy;
	assign led[12] = app_wdf_rdy;
	assign led[4] = sys_rst ? read_valid[1] : 1'b0;
	assign led[3] = sys_rst ? read_valid[0] : 1'b0;
	assign led[2] = stop_w[1];
	assign led[1] = stop_w[0];
	assign led[0] = app_cmd[0];
	
	reg [15:0] counter = 16'h0;
	parameter cnt_init = 16'h1;	// minimum: 1
	reg [26:0] addr0 = 27'h000_0008;
	reg [26:0] addr1 = 27'h003_0100;
	reg [127:0] data0 = 128'h1111_2222_3333_4444_5555_6666_7777_8888;
	reg [127:0] data1 = 128'h9999_0000_aaaa_bbbb_cccc_dddd_eeee_ffff;
	reg [1:0] stop_w = 2'b00;
	always@ (posedge sys_clk_i or negedge sys_rst) begin
		if (sys_rst == 1'b0) begin
			counter = 12'b0;
			stop_w = 2'b0;
			app_en = 1'b0;
			app_addr = 27'h0;
			app_cmd = 3'b1;
			app_wdf_data = 128'h0;
			app_wdf_end = 1'b0;
			app_wdf_wren = 1'b0;
		end else begin
			if (counter == cnt_init && ~stop_w[0])
				if (app_rdy & app_wdf_rdy) begin
					app_wdf_data = data0;
					app_addr = addr0;
					app_cmd = 3'b0;
					app_wdf_wren = 1'b1;
					app_wdf_end = 1'b1;
					app_en = 1'b1;
				end else		// Hold specific signals until app_wdf_rdy is asserted.
					counter = counter - 16'h1;
			else if (counter == cnt_init + 1 && ~stop_w[0])
				if (app_rdy & app_wdf_rdy) begin
					app_wdf_end = 1'b0;
					app_wdf_wren = 1'b0;
					app_en = 1'b0;
					app_cmd = 3'b1;
					stop_w[0] = 1'b1;
				end else		// Hold specific signals until app_wdf_rdy is asserted.
					counter = counter - 16'h1;
			else if (counter == cnt_init + 8 && ~stop_w[1])
				if (app_rdy & app_wdf_rdy) begin
					app_wdf_data = data1;
					app_addr = addr1;
					app_cmd = 3'b0;
					app_wdf_wren = 1'b1;
					app_wdf_end = 1'b1;
					app_en = 1'b1;
				end else		// Hold specific signals until app_wdf_rdy is asserted.
					counter = counter - 16'h1;
			else if (counter == cnt_init + 9 && ~stop_w[1])
				if (app_rdy & app_wdf_rdy) begin
					app_wdf_end = 1'b0;
					app_wdf_wren = 1'b0;
					app_en = 1'b0;
					app_cmd = 3'b1;
					stop_w[1] = 1'b1;
				end else		// Hold specific signals until app_wdf_rdy is asserted.
					counter = counter - 16'h1;
			else if (counter == cnt_init + 88) begin
				app_addr = switch_i[2] ? addr1 : addr0;
				app_en = 1'b1;
				if (~app_rdy) counter = counter - 16'h1;
			end else if (counter == cnt_init + 89)
				app_en = 1'b0;
			
			counter = counter + 16'h1;
		end
	end
	
	// Start of User Design top instance
	//***************************************************************************
	// The User design is instantiated below. The memory interface ports are
	// connected to the top-level and the application interface ports are
	// connected to the traffic generator module. This provides a reference
	// for connecting the memory controller to system.
	//***************************************************************************
	my_ddr u_my_ddr (
		// Memory interface ports
		.ddr2_cs_n					(ddr2_cs_n),
		.ddr2_addr					(ddr2_addr),
		.ddr2_ba					(ddr2_ba),
		.ddr2_we_n					(ddr2_we_n),
		.ddr2_ras_n					(ddr2_ras_n),
		.ddr2_cas_n					(ddr2_cas_n),
		.ddr2_ck_n					(ddr2_ck_n),
		.ddr2_ck_p					(ddr2_ck_p),
		.ddr2_cke					(ddr2_cke),
		.ddr2_dq					(ddr2_dq),
		.ddr2_dqs_n					(ddr2_dqs_n),
		.ddr2_dqs_p					(ddr2_dqs_p),
		.ddr2_dm					(ddr2_dm),
		.ddr2_odt					(ddr2_odt),
		// Application interface ports
		.app_addr					(app_addr),
		.app_cmd					(app_cmd),
		.app_en						(app_en),
		.app_wdf_rdy				(app_wdf_rdy),
		.app_wdf_data				(app_wdf_data),
		.app_wdf_end				(app_wdf_end),
		.app_wdf_wren				(app_wdf_wren),
		.app_rd_data				(app_rd_data),
		.app_rd_data_end			(app_rd_data_end),
		.app_rd_data_valid			(app_rd_data_valid),
		.app_rdy					(app_rdy),
		.app_sr_req					(1'b0),
		.app_ref_req				(1'b0),
		.app_zq_req					(1'b0),
		.app_wdf_mask				(16'h0000),
		.init_calib_complete		(init_calib_complete),
		// System Clock Ports
		.sys_clk_i					(sys_clk_i),
		// Reference Clock Ports
		.clk_ref_i					(sys_clk_i),
		.sys_rst					(sys_rst)
	);
	
endmodule

· 保存后直接生成比特流就可以下板验证了。
· 在摸索过程中笔者发现,写入了数据之后最快要到发出写命令的第8个系统时钟才能读出所写入的数据,且读操作必须在写操作后经过8的整数倍个时钟后进行。有时将比特流下载到N4DDR上面之后读写的数据有误,但是重启开发板再重新下载即可解决问题,知道个中缘由的朋友欢迎在评论中告知笔者,笔者在此先行谢过。
· 需要说明的是,此处突发长度(BL)为8,因此app_addr必须是8对齐的地址。同时,由于前面选择的Data Width为16,因此每次读写数据的长度为8*16bit==128bit。


· 笔者水平有限,文中难免存在纰漏,望读者在评论中慷慨指出。笔者在此先行谢过。


· 参考文档:
Nexys4-DDR_rm.pdf
ug586_7Series_MIS_v2.4.pdf

· 模板工程下载链接:
http://download.csdn.net/detail/qq_20091945/9728980

· 测试代码下载链接:
http://download.csdn.net/detail/qq_20091945/9725407
【注意】由于工程大小超出上限,此处仅提供工程的源码文件和比特流文件。

· DDR2 IP核引脚约束文件下载链接:
n4ddr_ddr2_io_assign.ucf

你可能感兴趣的:(嵌入式,Vivado,DDR,Xilinx)