FPGA_MIG驱动DDR3

FPGA_MIG驱动DDR3
说明:
FPGA: zynq(7z100)。
DDR3:MT41K256M16TW-107:内存大小为512MB,数据接口为16bit。。
环境:Vivado2018.2。
IP核:Memory Interface Generator(MIG 7 Series)。
参考手册:ug586(7 Series Devices Memory Interface Solutions v4.1)。
源码:源码下载


1.DDR型号解析

MT41K256M16TW-107:
内存大小为512MB,数据接口为16bit。
为什么是512MB:256M空间,数据线为16bit,1Byte=8bit,256M×16bit=512M×8bit=512MB。
下图为型号参数的具体解析:

1.1DDR3地址

  DDR3的内部是一个存储阵列,将数据“填”进去,你可以它想象成一张表格。和表格的检索原理一样,先指定一个行(Row),再指定一个列(Column),我们就可以准确地找到所需要的单元格,这就是内存芯片寻址的基本原理。对于内存,这个单元格可称为存储单元,那么这个表格(存储阵列)就是逻辑 Bank(Logical Bank,下面简称Bank)。 DDR3内部Bank示意图,这是一个NXN的阵列,B代表Bank地址编号,C代表列地址编号,R代表行地址编号。在这里插入图片描述
以下列举了不同容量的DDR3地址:
在这里插入图片描述
在这里插入图片描述
注意以上单位为GBit。比如MT41K256M16TW-107容量为512MB=512×8Mbit=4096Mbit=4Gbit,就要看表2.11.4 4Gb中的256M×16。
在这里插入图片描述

2.FPGA(MIG)与DDR3连接示意图.

如下图所示:FPGA用户逻辑↔MIG↔DDR3的端口连接示意图:
在这里插入图片描述

3.DDR端口介绍

符号 类型 功能
CK、CK# 输入 时钟:CK和CK#是差分时钟输入。所有地址和控制输入信号均为在CK的上升沿和CK#的下降沿的交点上采样。
CKE, (CKE0),(CKE1) 输入 时钟使能。使能(高)和禁止(低)内部电路和DRAM上的时钟。由DDR3SDRAM配置和操作模式决定特定电路被使能和禁止。CKE为低,提供PRECHARGE POWER-DOWN和SELF REFRESH操作(所有Bank都处于空闲),或者有效掉电(在任何bank里的行有效)。CKE与掉电状态的进入退出以及自刷新的进入同步。CKE与自刷新的退出异步,输入Buffer (除了CK、CK#、RESE T#和ODT)在POWER-DOWN期间被禁止。输入Buffer (除了CKE和RESET#)在SEL _F REFRESH期间被禁止。CKE的参考是VREFCA 。
CS#, (CS0#), (CS1#),(CS2#), (CS3#) 输入 片选:当CS#为高时,所有命令均被屏蔽。CS#提供外部 具有多个等级的系统上的等级选择。CS#被视为命令代码的一部分。
ODT, (ODT0),(ODT1) 输入 片上终端使能: ODT 使能(高)和禁止(低)片内终端电阻。
RAS#. CAS#. WE# 输入 命令输入:RAS#,CAS#和WE#(以及CS#)定义了要输入的命令。
DM, (DMU), (DML) 输入 数据输入屏蔽:DM是用于写入数据的输入掩码信号。 在写访问期间采样到的高电平,输入数据被屏蔽
BA0 - BA2 输入 BANK址输入:BA0-BA2定义ACTIVATE、读取、写入或PRECHARGE是对哪个BANK操作的。
A0 - A15 输入 地址输入:为ACTIVATE命令提供行地址,为读取/提供列地址 编写命令以从相应存储体的存储器阵列中选择一个位置。。地址输入还提供操作码 在模式寄存器设置命令期间。
A10 / AP 输入 自动预充位 :A10在PRECHARGE命令期间被采样,已确定PRECHARGE是否应用于某个BANK;A10为低,这个BANK由BA[2:0]来选择,A10为高,对所有BANK。
A12 / BC# 输入 burst chop:在读取和写入命令期间对A12 / BC#进行采样,以确定是否存在burst chop将被执行。(高,无burst chop;低:burst chop)。见命令表以了解详细信息。
RESET# 输入 低电平有效异步复位:当RESET#为低电平时,复位有效。在正常操作期间,RESET#必须为高。
DQ 输入输出 数据输入/输出:双向数据总线。
DQU, DQL, DQS,DQS#, DQSU,DQSU#, DQSL,DQSL# 输入输出 数据选通:读时输出,边缘与读出的数据对齐,写时输入,中心与写数据对齐 。

4.MIG_UI端口介绍

以下为FPGA的MIG IP核用户界面接口。

app_addr[ADD_WIDTH-1:0] 输入 此输入指示当前提交给UI的请求的地址。 UI聚合外部SDRAM的所有地址字段,并提供平面地址空间(Rank,bank,row,colum)。
app_cmd[2:0] 输入 此输入指定当前提交给UI的请求的命令。在这里插入图片描述
app_en 输入 此输入在请求中变化。 您必须将所需的值应用于app_addr []和app_cmd [2:0],然后断言app_en以将请求提交给UI。 这会通过断言app_rdy来启动UI确认的握手。
app_wdf_data[APP_DATA_WIDTH – 1:0] 输入 该总线提供当前正在写入外部存储器的数据。
app_wdf_end 输入 此输入指示当前周期中app_wdf_data []总线上的数据是当前请求的最后一个数据。
app_wdf_wren 输入 此输入表示app_wdf_data []总线上的数据有效。
app_wdf_rdy 输出 此输出表示写数据FIFO已准备好接收数据。 当app_wdf_rdy和app_wdf_wren都被声明时,接受写入数据。
app_wdf_mask[APP_MASK_WIDTH – 1:0] 输入 该总线指示app_wdf_data []的哪些字节写入外部存储器以及哪些字节保持其当前状态。 通过将值1设置为app_wdf_mask中的相应位来屏蔽字节。 例如,如果应用程序数据宽度为256,则掩码宽度取值为32. app_wdf_data的最低有效字节[7:0]使用app_wdf_mask的Bit [0]屏蔽,app_wdf_data的最高有效字节[255:248]使用app_wdf_mask的Bit [31]屏蔽。 因此,如果必须屏蔽最后一个DWORD,即app_wdf_data的字节0,1,2和3,则app_wdf_mask应设置为32’h0000_000F。
app_rdy 输出 此输出指示您是否接受当前提交给UI的请求。 如果在确认app_en后UI未声明此信号,则必须重试当前请求。 如果出现以下情况,则不会声明app_rdy输出:1.PHY /内存初始化尚未完成; 2.所有bank都被占用(可以看作命令缓冲区已满);3.请求读取并且读取缓冲区已满;4.正在插入定期读取。
app_rd_data[APP_DATA_WIDTH – 1:0] 输出 此输出包含从外部存储器读取的数据。
app_rd_data_end 输出 此输出表示当前周期中app_rd_data []总线上的数据是当前请求的最后一个数据。
app_rd_data_valid 输出 此输出表明app_rd_data []总线上的数据有效。
ui_clk_sync_rst 输出 reset信号来自于UI,与ui_clk同步。
ui_clk 输出 这是UI的输出时钟。 它必须是输出到外部SDRAM的时钟频率的一半或四分之一,这取决于在GUI中选择的2:1或4:1模式。
init_calib_complete 输出 校准完成后,PHY将init_calib_complete置‘1’。 在将命令发送到内存控制器之前,应用程序无需等待init_calib_complete。
app_ref_req 输入 置位时,此高电平有效输入请求内存控制器向DRAM发送刷新命令。 它必须在一个周期内进行脉冲以发出请求,直到app_ref_ack信号被置位以确认请求并指示它已被发送,然后置为无效。
app_ref_ack 输出 置位时,此高电平有效输入确认刷新请求,并指示该命令已从存储器控制器发送到PHY。
app_zq_req 输入 置位时,此高电平有效输入请求存储器控制器向DRAM发送ZQ校准命令。 它必须在一个周期内进行脉冲以发出请求,直到app_zq_ack信号被置位以确认请求并指示它已被发送,然后取消置位。
app_zq_ack 输出 置位时,此高电平有效输入确认ZQ校准请求,并指示该命令已从存储器控制器发送到PHY。

5.MIG 控制器概述及读写时序介绍

在这里插入图片描述
如上图所示,FPGA用户逻辑代码通过MIG控制器IP连接到DDR3的物理芯片。MIG提供的UI接口大大简化了DDR在FPGA上的应用。

5.1.MIG内存控制器用户逻辑时序

指令通道: 在这里插入图片描述
当用户逻辑app_en信号置位并且app_rdy置位命令被写入FIFO。app_rdy被取消置位时,UI会忽略该命令。用户逻辑需要将app_en保持为高电平以及有效的命令和地址值,直到app_rdy被置位。

写时序:
在这里插入图片描述
写入数据的三种场景(如上图的1、2、3):
1:写入命令的同时写入数据
2:写入数据在相应的写入命令之前
3:写入数据在相应的写命令之后,不应超过两个时钟周期的限制。
在这里插入图片描述
当app_wdf_wren被置位时且app_wdf_rdy为高时,数据写入FIFO,如果app_wdf_rdy取消置位,则用户逻辑需要保留app_wdf_wren和app_wdf_end以及有效的app_wdf_data值,直到app_wdf_rdy被置位。

读时序:
在这里插入图片描述
在app_rd_data_valid被置位时,数据由UI请求的顺序返回(即读信号与appen置位期间请求的地址顺序)。app_rd_data_end信号表示每个读命令脉冲串的结束。

扫描二维码关注公众号,回复: 12972411 查看本文章

5.2.BL8 突发长度

理解一下概念:
突发传输: 在通信领域中一般指在短时间内进行相对高带宽的数据传输。
Burst(突发): 是指在同一行中相邻的存储单元连续进行数据传输的方式。
BL突发长度: 连续传输的周期数就是突发长度。
在突发传输的模式下,多个数据单元(相当于一个数据块)来传送,从而提高传输效率。
BL8: 突发长度为8,app_addr必须是8对齐的地址,比如数据宽度Data Width为32,每次读写数据的长度为8*32=256bit。

5.3.MIG时钟和DDR3时钟

例如IP核 中配置如下:
1.
在这里插入图片描述
即MIG对DDR接口的速率为800M*2=1600M(双沿)。
MIG输出到app接口上的时钟ui_clk为800M/4=200M。
UI时钟频率ui_clk为DDR时钟频率的1/4,也就是一个用户时钟周期(两个边沿)对应8个DDR时钟边沿。burst length可以理解为MIG连续操作DDR地址的个数,故在4:1时钟比例下,一个用户时钟周期正好对8个地址进行了读/写操作,256bit数据分8次(32bit)写入DDR中,即逻辑用户以时钟ui_clk写256bit数据至MIG,MIG控制器将以时钟ddr_clk向DDR写8次(32bit(数据位宽配置为32bit))数据。由此分析,在写数据时让app_wdf_end = app_wdf_wren即可,并且读/写操作时地址递增步长为8。
在这里插入图片描述
2.
在这里插入图片描述
输入给MIG的时钟为200M。

5.4.掩码_Mask

  掩码是一串二进制代码对目标字段进行位与运算,屏蔽当前的输入位。
  在MIG的端口中有app_wdf_mask[APP_MASK_WIDTH – 1:0]信号充当掩码,该总线指示app_wdf_data []的哪些字节写入外部存储器以及哪些字节保持其当前状态。 通过将值1设置为app_wdf_mask中的相应位来屏蔽字节。 例如,如果应用程序数据宽度为256,则掩码宽度取值为32. app_wdf_data的最低有效字节[7:0]使用app_wdf_mask的Bit [0]屏蔽,app_wdf_data的最高有效字节[255:248]使用app_wdf_mask的Bit [31]屏蔽。 因此,如果必须屏蔽最后一个DWORD,即app_wdf_data的字节0,1,2和3,则app_wdf_mask应设置为32’h0000_000F。

6. MIG IP核配置

Step1:IP Catalog中搜索MIG,双击。
在这里插入图片描述
Step2:Name 看自己。
在这里插入图片描述
Step3:型号选择。
在这里插入图片描述
Step4:DDR3。
在这里插入图片描述
Step5:时钟、型号、位宽。
在这里插入图片描述
Step6:输入给MIG的时钟。
在这里插入图片描述
Step7:系统和参考时钟选择no buffer;MIG低电平复位。
在这里插入图片描述
Step8:终端阻抗50hms。
在这里插入图片描述
Step9:不New,Fixed Pin Out。
在这里插入图片描述
Step10:约束,可以根据原理图自己填也可以导入现有的约束。
在这里插入图片描述
Step11:Next、Next、Next、Next、Generate。
在这里插入图片描述
Step12:右击Open IP_Example Design…,并设置好路径,创建Example工程。
在这里插入图片描述

7.实战-DDR3读写

7.1原理图

  通过两片512MB的MT41K256M16TW-107扩展为1GB的DDR3。两片DDR3的地址线互联连接到FPGA,数据线分别连接至FPGA扩展为高16位和低16位,扩展后的DDR3数据位宽为32bit。
以下为扩展示意图:
在这里插入图片描述
以下为电路连接原理图:
在这里插入图片描述

7.2IP核配置以及Example代码修改及分析

IP核配置如上所示。配置完IP核后生成Example工程,修改以下代码:
(1)、MIG输入时钟为200M,所以系统时钟通过PLL产生200M时钟。
(2)、修改MIG IP CORE

  	   .ddr3_addr                      (ddr3_addr),
       .ddr3_ba                        (ddr3_ba),
       .ddr3_cas_n                     (ddr3_cas_n),
       .ddr3_ck_n                      (ddr3_ck_n),
       .ddr3_ck_p                      (ddr3_ck_p),
       .ddr3_cke                       (ddr3_cke),
       .ddr3_ras_n                     (ddr3_ras_n),
       .ddr3_we_n                      (ddr3_we_n),
       .ddr3_dq                        (ddr3_dq),
       .ddr3_dqs_n                     (ddr3_dqs_n),
       .ddr3_dqs_p                     (ddr3_dqs_p),
       .ddr3_reset_n                   (ddr3_reset_n),
       .init_calib_complete            (init_calib_complete),
      
       .ddr3_cs_n                      (ddr3_cs_n),
       .ddr3_dm                        (ddr3_dm),
       .ddr3_odt                       (ddr3_odt),

           
// Application interface ports
      .app_addr                       (app_addr),			//输入,当前请求的地址
      .app_cmd                        (app_cmd),			//输入,当前请求的命令,001读	000写
      .app_en                         (app_en),				//输入,输出表明UI已准备好接受命令
      .app_wdf_data                   (app_wdf_data),		//输入,为写命令提供了数据
      .app_wdf_end                    (app_wdf_end),		//输入,高电平指示app_wdf_data上输入数据的最后一个周期
      .app_wdf_wren                   (app_wdf_wren),		//输入,高电平指示app_wdf_data总线上的数据有效
      .app_rd_data                    (app_rd_data),		//输出,读取命令的输出数据
      .app_rd_data_end                (app_rd_data_end),	//输出,高电平指示app_rd_data上输出数据的最后一个周期
      .app_rd_data_valid              (app_rd_data_valid),	//输出,高电平指示app_rd_data有效
      .app_rdy                        (app_rdy),			//输出,此输出表明UI已准备好接收命令
      .app_wdf_rdy                    (app_wdf_rdy),		//输出,此输出表明写数据FIFO已准备好接收数据。
      .app_sr_req                     (1'b0),
      .app_ref_req                    (1'b0),
      .app_zq_req                     (1'b0),
      .app_sr_active                  (app_sr_active),
      .app_ref_ack                    (app_ref_ack),
      .app_zq_ack                     (app_zq_ack),
      .ui_clk                         (clk),				//输出,必须是DDR时钟频率的一半或四分之一,取决于GUI中选择的2:1或4:1模式(UI配置的为4:1),800M/4=200M
      .ui_clk_sync_rst                (rst),
      .app_wdf_mask                   (32'd0),				//该总线指示app_wdf_data[]的哪些字节被写入外部存储器,并且哪些字节保持其当前状态,通过设置1来屏蔽。
															//如果app data宽度为256,则app_wdf_mask为32bit,例:D0屏蔽app_wdf_data[7:0],D31屏蔽app_wdf_data[255:248]
																	
// System Clock Ports
      .sys_clk_i                      (sys_clk_i),			//PLL产生的200M时钟		
// Reference Clock Ports
      .clk_ref_i                      (clk_ref_i),			//PLL产生的200M时钟
      .device_temp                    (device_temp),
      .sys_rst                        (locked)

(3)、添加测试代码
代码说明:3次写入256bit数据(由计数产生)至DDR3,然后3次读出写入DDR3的数据。

/*--读写测试--*/
/*	UI输入:						UI输出
*	-->	app_addr					app_rd_data			-->
*	-->	app_wdf_data				app_rd_data_end		-->
*	-->	app_en						app_rd_data_valid	-->
*	-->	app_cmd						app_rdy				-->
*	-->	app_wdf_end					app_wdf_rdy			-->
*	-->	app_wdf_wren	
*/
//写入3*256bit数据,需写入3次。

//状态机
reg[1:0]	MIG_State=0;
parameter	[1:0]IDLE		=	2'd0;
parameter	[1:0]WRITE		=	2'd1;
parameter	[1:0]WAIT		=	2'd2;
parameter	[1:0]READ		=	2'd3;

//读写状态
parameter	[2:0]CMD_WRITE	=	3'b000;
parameter	[2:0]CMD_READ	=	3'b001;

//测试大小
parameter	TEST_RANGE 		=	10'd3;

//计数值写入DDR
reg 		[9:0]cnt=0;	

//app_addr	输入UI的地址 = RANK_WIDTH + BANK_WIDTH  + ROW_WIDTH + COL_WIDTH;
reg    [ADDR_WIDTH-1:0]app_addr_r=0;

//控制信号产生
//app_cmd:输入,当前请求的命令,001读	000写
assign	app_cmd			=	(MIG_State==WRITE)?	CMD_WRITE:CMD_READ;

//app_en:输入,输出表明UI已准备好接受命令
//写状态时app_rdy、app_wdf_rdy均置位时表明UI已经准备好接收,读状态时app_rdy置位表明UI已准备就绪。
assign	app_en			=	(MIG_State==WRITE)?	(app_rdy&&app_wdf_rdy):((MIG_State==READ)&&app_rdy);

//app_wdf_wren:输入至UI。app_wdf_wren高电平指示app_wdf_data总线上的数据有效
//在写状态时,app_rdy、app_wdf_rdy两个信号均为就绪状态时为1。
assign	app_wdf_wren	=	(MIG_State==WRITE)?(app_rdy&&app_wdf_rdy):1'b0;

//app_wdf_end:输入,高电平指示app_wdf_data上输入数据的最后一个周期。
assign	app_wdf_end		=	app_wdf_wren;

//地址
assign	app_addr		=	app_addr_r;

//写入数据[255:0],通过8次32bit写入DDR
assign    app_wdf_data	={
    
    
							cnt[7:0],cnt[7:0],cnt[7:0],cnt[7:0],	//1	32bit
							cnt[7:0],cnt[7:0],cnt[7:0],cnt[7:0],	//2	32bit
							cnt[7:0],cnt[7:0],cnt[7:0],cnt[7:0],	//3	32bit
							cnt[7:0],cnt[7:0],cnt[7:0],cnt[7:0],	//4	32bit
							cnt[7:0],cnt[7:0],cnt[7:0],cnt[7:0],	//5	32bit
							cnt[7:0],cnt[7:0],cnt[7:0],cnt[7:0],	//6	32bit
							cnt[7:0],cnt[7:0],cnt[7:0],cnt[7:0],	//7	32bit
							cnt[7:0],cnt[7:0],cnt[7:0],cnt[7:0]		//8	32bit
						};

//状态机,clk为ui_clk,200MHz
always@(posedge	clk)
begin	
	if(rst&!init_calib_complete)
	begin
		MIG_State <= IDLE;
		cnt	<= 10'd0;
    end
	else
	begin
		case(MIG_State)
			IDLE:
			begin
				MIG_State	<= WRITE;
				cnt			<= 10'd0;	//复位计数(用来写)
				app_addr_r	<= 29'd0;	//复位地址(用来写)
			end
			WRITE:
			begin
				if(cnt==TEST_RANGE)
				begin
					MIG_State	<= 	WAIT;
				end
				else
				begin
					MIG_State	<= 	MIG_State;
					cnt			<= (app_rdy&&app_wdf_rdy)?	(cnt+10'd1):cnt;
					app_addr_r	<= (app_rdy&&app_wdf_rdy)?	(app_addr_r+29'd8):app_addr_r;//跳到下一个(8*32=256)bit数据地址,数据app_wdf_data位宽为256bit。	
				end	
			end
			WAIT:
			begin
				MIG_State	<=	READ;	
				cnt			<=	10'd0;	//复位计数(用来读)
				app_addr_r	<= 	29'd0;	//复位地址(用来读)
			end
			READ:
			begin
				if(cnt==TEST_RANGE)
				begin
					MIG_State	<= 	IDLE;
				end
				else
				begin
					MIG_State	<= 	MIG_State;
					cnt			<=	app_rdy?	(cnt+10'd1):cnt;
					app_addr_r	<= 	app_rdy?	(app_addr_r+29'd8):app_addr_r;			//跳到下一个(8*32=256)bit数据地址,数据app_wdf_data位宽为256bit。
				end
			end
			default:
			begin
				MIG_State	<= IDLE;
				cnt			<= 10'd0;
				app_addr_r	<= 29'd0;
			end
		endcase
	end
end

ila_0 degbug_0 (
	.clk(clk), 					// input wire clk
	.probe0(app_cmd), 			// input wire [2:0] 	 probe0  
	.probe1(app_en), 			// input wire [0:0] 	 probe1 
	.probe2(app_wdf_wren), 		// input wire [0:0]  	 probe2 
	.probe3(app_wdf_end), 		// input wire [0:0] 	 probe3 
	.probe4(app_addr),			// input wire [28:0] 	 probe4 
	.probe5(app_rd_data),		// input wire [255:0]	 probe5
	.probe6(MIG_State), 		// input wire [2:0]  	 probe6
	.probe7(app_wdf_data), 		// input wire [255:0]    probe7
	.probe8(app_rdy),			// input wire [0:0]  	 probe8 
	.probe9(app_wdf_rdy), 		// input wire [0:0]  	 probe9
	.probe10(app_rd_data_end),  // input wire [0:0]		 probe10 
	.probe11(app_rd_data_valid) // input wire [0:0] 	 probe11
);

(4)、代码对应时序分析
写数据:
在这里插入图片描述
写入数据的三种场景:
1:写入命令的同时写入数据
2:写入数据在相应的写入命令之前
3:写入数据在相应的写命令之后,不应超过两个时钟周期的限制。
我们采用1.写入命令的同时写入数据。
当app_wdf_wren被置位时且app_wdf_rdy为高时,数据写入FIFO,如果app_wdf_rdy取消置位,则用户逻辑需要保留app_wdf_wren和app_wdf_end以及有效的app_wdf_data值,直到app_wdf_rdy被置位。
在这里插入图片描述
在MIG_UI中app_cmd为001时为读状态、为000时为写状态。
认读理解上面两行红字,看下图,看标注的即可:
在这里插入图片描述上图中,当app_wdf_wren被置位时且app_wdf_rdy为高时,数据线的3个256bit数据被写入DDR3。
此时上图的三个app_addr为(上图看不清楚地址看下图):
在这里插入图片描述
读数据:
在这里插入图片描述
在app_rd_data_valid被置位时,数据由UI请求的顺序返回(即读信号与app_en置位期间请求的地址顺序)。观察上图app_rd_data_valid不是立即置位的。
注意:认真读上面那段红色的话,app_cmd为001时为读,观察app_cmd为001,同时app_en拉高时的地址app_addr(与写入时的地址是一样):
在这里插入图片描述
★★★此刻app_en拉高时的地址MIG控制器会记住的,数据会在app_rd_data_valid置位时按这个地址顺序返回数据。 如下图所示,标号①处为app_en拉高处,即MIG获取读取地址的时候,标号②处为app_rd_data_valid置位处标明表明此刻返回了有效数数据。观察和写入的数据一致。

在这里插入图片描述

8.附带源码

源码下载

★★★如有错误欢迎指导!!!

猜你喜欢

转载自blog.csdn.net/qq_40147893/article/details/109746721