zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

快速上手Xilinx DDR3 IP核(3)----把MIG IP核封装成一个FIFO(上)(Native接口)

2023-04-18 16:41:58 时间

写在前面

        本文将把Xilinx的MIG IP核DDR3的Native接口进行二次封装,将其封装成一个类似FIFO的接口,使其应用起来更加方便简单。

        DDR3系列文章:

                快速上手Xilinx DDR3 IP核----汇总篇(直达链接)


1、框架

        这个类FIFO模块主要由以下几个部分组成:

mig_ctrl:顶层模块,使用该模块通过控制MIG IP核间接实现对DDR3芯片的突发写、突发读。分为用户接口与DDR3控制接口,用于只需要控制用户接口即可实现对DDR3芯片的突发写、突发读。用户接口的应用类似FIFO接口,用户只需要提供写使能信号与写入的数据即可实现突发写操作(实际上还需要提供其他信息:如突发长度、起止地址等信息);用户只需要提供读使能信号即可实现突发读操作(实际上还需要提供其他信息:如突发长度、起止地址等信息)。

------fifo_ctrl:分别例化写FIFO与读FIFO,用来缓存用户要写入DDR3的数据与从DDR3中读取的数据,实现DDR3端与用户端的跨时钟域处理。同时生成MIG读写的地址、读写请求信号。每当写FIFO中的数据量超过设置阈值时,即生成ddr3写请求信号,同时更新写入地址,使ddr3_wr生成控制MIG IP的时序来将数据写入DDR3;每当读FIFO中的数据量不足设置阈值时,即生成ddr3读请求信号,同时更新读取地址,使ddr3_rd生成控制MIG IP的时序来从DDR3中读取数据;

-------------写FIFO:写位宽16bit,写端口与用户端相连,写入数据来自用户端;读位宽128bit(DDR3固定突发长度8,位宽16bit),读端口与ddr3_wr模块相连,从FIFO读取数据写入DDR3。

-------------读FIFO:写位宽128bit(DDR3固定突发长度8,位宽16bit),写端口与ddr3_rd模块相连,写入数据来自DDR3;读位宽16bit,读端口与用户端相连,用户从FIFO读取数据即为从DDR3中的缓存数据。

------ddr3_wr:突发写入模块,驱动MIG IP将数据以突发写的方式写入DDR3芯片。

------ddr3_rd:突发读取模块,驱动MIG IP以突发读的方式将数据从DDR3芯片中读出。

MIG IP:Xilinx提供的DDR3 P核,将DDR3读写的复杂时序封装成非常简单的接口供用户使用。

        该模块的使用流程:

写端口:

  1. 提供写使能信号、写入数据就可实现DDR3的突发写操作,类似FIFO。之所以说类FIFO,是因为还需要提供其他必要信息:突发写的突发长度、DDR3写入的起始地址与结束地址;
  2. 用户写入的数据先是被缓存到写FIFO,当写FIFO中的数据量满足设定的阈值(该阈值默认为突发长度)时,生成突发写请求信号,与DDR3突发写的首地址(每一次新的突发写请求都会生成新的首地址(累加));
  3. ddr3_wr模块根据突发写信息驱动MIG IP核对DDR3芯片操作,实现一次突发写操作;
  4. 此时写FIFO中的数据被读出,写入到DDR3中,FIFO中的数据数量减少,等待用户继续写入数据直到再次超过设定阈值,从而开始新的一次突发写操作。

读端口:

  1. 提供读使能信号就可实现DDR3的突发读操作,类似FIFO。之所以说类FIFO,是因为还需要提供其他必要信息:突发读的突发长度、DDR3读取的起始地址与结束地址;
  2. 每当读FIFO中的数据量不足设定的阈值(该阈值默认为突发长度)时,生成突发读请求信号,与DDR3突发读的首地址(每一次新的突发读请求都会生成新的首地址(累加));
  3. ddr3_rd模块根据突发读信息驱动MIG IP核对DDR3芯片操作,实现一次突发读操作;
  4. 此时DDR3中的数据被读出,写入到读FIFO中,FIFO中的数据数量增加,等待用户将数据读出,直到数据量不足设定阈值,从而开始新的一次突发读操作。

2、ddr3_wr突发写模块

2.1、端口

        MIG IP核提供了Native接口给用户使用,ddr3_wr模块实际上就是对MIG的Native接口的突发写驱动模块,该模块的输入、输出端口如下:

        端口主要分为2个部分:

                用户端:输入突发写的相关信号;MIG端:自带的Native接口。

        用户写操作的顺序如下(待MIG初始化完成后):

  • 拉高wr_burst_start一个时钟,表示要对DDR3进行一次突发写操作,同时输入突发长度与突发的首地址,拉高wr_burst_busy表示此时正在进行突发写操作
  • 根据MIG Native接口的时序,生成突发写响应信号,该响应信号被连接到上层的写FIFO模块的读端口,这样就把用户写入缓存在写FIFO的数据读出,从而写入DDR3芯片
  • 待写入数据个数满足突发长度后,拉高wr_burst_done信号一个周期,表示一次突发写操作完成

2.2、Verilog代码

        完整的Verilog代码如下(代码不复杂,只要实现native接口的时序就可以了,其他请参考注释):

//**************************************************************************
// *** 名称 : ddr3_rw
// *** 作者 : 孤独的单刀
// *** 博客 : https://blog.csdn.net/wuzhikaidetb
// *** 日期 : 2021.12
// *** 描述 : 对DDR3进行循环读写
//**************************************************************************
module ddr3_wr #
( 
	parameter	integer					DATA_WIDTH	= 128	,		//数据位宽,突发长度为8,16bit,共128bit
	parameter	integer					ADDR_WIDTH	= 28			//根据MIG例化而来
)(  
//时钟与复位-------------------------------------------------       	
    input                   			ui_clk				,		//用户时钟
    input                   			ui_clk_sync_rst		,		//复位,高有效
//用户端信号------------------------------------------------- 	
	input								wr_burst_start		,		//一次突发写开始							
	input	[ADDR_WIDTH - 1:0]			wr_burst_len		,		//突发写入的长度							
	input	[ADDR_WIDTH - 1:0]			wr_burst_addr		,		//突发写入的首地址							
	input	[DATA_WIDTH - 1:0]			wr_burst_data		,		//需要突发写入的数据。来源写FIFO							
	output								wr_burst_ack		,		//突发写响应,高电平表示正在进行突发写操作							
	output	reg							wr_burst_done		,		//一次突发写完成
	output	reg							wr_burst_busy		,		//突发写忙状态,高电平有效	
//MIG端控制信号----------------------------------------------	
    output	reg                			app_en				,		//MIG IP发送命令使能	
    input                   			app_rdy				,		//MIG IP命令接收准备好标致
    output		[2:0]     				app_cmd				,		//MIG IP操作命令,读或者写
    output	reg	[ADDR_WIDTH - 1:0]		app_addr			,		//MIG IP操作地址	
    input                   			app_wdf_rdy			,		//MIG IP数据接收准备好
    output	                			app_wdf_wren		,		//MIG IP写数据使能
	output								app_wdf_end			,		//MIG IP突发写当前时钟最后一个数据
 	output		[(DATA_WIDTH/8) - 1:0]	app_wdf_mask		,		//MIG IP数据掩码
    output		[DATA_WIDTH - 1:0]		app_wdf_data				//MIG IP写数据
);

//parameter define
localparam								WRITE = 3'b000;				//MIG写命令
//reg define
reg				[ADDR_WIDTH - 1:0]		wr_burst_cnt		;		//写入数据个数计数器计数
reg				[ADDR_WIDTH - 1:0]		wr_burst_len_r		;		//锁存突发长度
reg				[ADDR_WIDTH - 1:0]		wr_burst_addr_r		;		//锁存突发地址
reg										wr_burst_start_r	;		//突发开始打一拍,用于锁存突发地址、突发长度   
//wire define					
wire									wr_burst_last		;		//拉高表示写入最后一个数据

 //*********************************************************************************************
//**                    main code
//**********************************************************************************************

//不使用掩码
assign app_wdf_mask = 0	;
//固定为写状态 
assign app_cmd = WRITE;				              
//写指令,命令接收和数据接收都准备好,此时拉高写使能
assign app_wdf_wren = wr_burst_ack;
//从用户端输入的数据直接赋值给MIG IP核
assign app_wdf_data = wr_burst_data; 
//由于DDR3芯片时钟和用户时钟的分频选择4:1,突发长度为8,故两个信号相同
assign app_wdf_end = app_wdf_wren; 
//处于写使能且是最后一个数据
assign wr_burst_last = app_wdf_wren && (wr_burst_cnt == wr_burst_len_r - 1) ;
//写响应信号,用于从前级获取数据
assign wr_burst_ack = app_en && app_wdf_rdy && app_rdy;

//wr_burst_start打一拍,锁存突发地址、突发长度   
always @(posedge ui_clk) begin
	if(ui_clk_sync_rst)
		wr_burst_start_r <= 0;
	else 
		wr_burst_start_r <= wr_burst_start;		
end

//锁存突发长度
always @(posedge ui_clk) begin
    if(ui_clk_sync_rst)
        wr_burst_len_r <= 1'b0;
	else if(wr_burst_start)
		wr_burst_len_r <= wr_burst_len;
end

//锁存突发地址
always @(posedge ui_clk) begin
    if(ui_clk_sync_rst)
        wr_burst_addr_r <= 1'b0;
	else if(wr_burst_start)
		wr_burst_addr_r <= wr_burst_addr;
end

//app_en控制:突发开始时一直拉高,直到突发结束   
always @(posedge ui_clk) begin
	if(ui_clk_sync_rst)
		app_en <= 0;
	else if(wr_burst_start_r)
		app_en <= 1;
	else if(wr_burst_last)
		app_en <= 0;
	else 
		app_en <= app_en;		
end

//突发写忙状态,拉高表示其处于突发写状态
always @(posedge ui_clk) begin
    if(ui_clk_sync_rst)
        wr_burst_busy <= 0;
	else if(wr_burst_start)
		wr_burst_busy <= 1;				//进入写忙状态
	else if(wr_burst_done)
		wr_burst_busy <= 0;				//突发写完成,拉低写忙状态
	else 
		wr_burst_busy <= wr_burst_busy;		
end

//写入地址
always @(posedge ui_clk) begin
    if(ui_clk_sync_rst)
        app_addr <= 0;
	else if(wr_burst_start_r)					
		app_addr <= wr_burst_addr_r;	//将突发写的初始地址赋值给MIG
	else if(app_wdf_wren)				//
		app_addr <= app_addr + 8;		//
	else 
		app_addr <= app_addr;
end

//突发写完成信号
always @(posedge ui_clk) begin
    if(ui_clk_sync_rst)
        wr_burst_done <= 0;
	else if(wr_burst_last)
		wr_burst_done <= 1;				
	else 
		wr_burst_done <= 0;		
end

//写入数据个数(突发长度计数器)
always @(posedge ui_clk) begin
    if(ui_clk_sync_rst)
        wr_burst_cnt <= 0;
	else if(app_wdf_wren)begin
		if(wr_burst_cnt == wr_burst_len_r - 1)		
			wr_burst_cnt <= 0;					//进入写忙状态
		else
			wr_burst_cnt <= wr_burst_cnt + 1;
	end
	else 
		wr_burst_cnt <= wr_burst_cnt;
end

endmodule

3、ddr3_rd突发读模块

3.1、端口

        MIG IP核提供了Native接口给用户使用,ddr3_rd模块实际上就是对MIG的Native接口的突发读驱动模块,该模块的输入、输出端口如下:

        端口主要分为2个部分:

                用户端:输入突发读的相关信号;MIG端:自带的Native接口。

        用户读操作的顺序如下(待MIG初始化完成后):

  • 拉高rd_burst_start一个时钟,表示要对DDR3进行一次突发读操作,同时输入突发长度与突发的首地址,拉高rd_burst_busy表示此时正在进行突发读操作
  • 根据MIG Native接口的时序,生成突发读响应信号,该响应信号被连接到下层的读FIFO模块的写端口,这样就把MIG 从DDR3芯片读出的数据写入读FIFO
  • 待读出的数据个数满足突发长度后,拉高rd_burst_done信号一个周期,表示一次突发读操作完成

3.2、Verilog代码

        完整的Verilog代码如下(代码不复杂,只要实现native接口的时序就可以了,其他请参考注释):

//**************************************************************************
// *** 名称 : ddr3_rd
// *** 作者 : 孤独的单刀
// *** 博客 : https://blog.csdn.net/wuzhikaidetb
// *** 日期 : 2021.12
// *** 描述 : 对DDR3进行循环读写
//**************************************************************************
module ddr3_rd #
( 
	parameter	integer					DATA_WIDTH	= 128	,		//数据位宽,突发长度为8,16bit,共128bit
	parameter	integer					ADDR_WIDTH	= 28			//根据MIG例化而来
)	
(   
//时钟与复位-------------------------------------------------       	
    input                   			ui_clk				,		//用户时钟
    input                   			ui_clk_sync_rst		,		//复位,高有效	
//用户端信号------------------------------------------------- 	
	input								rd_burst_start		,		//一次突发读开始								
	input	[ADDR_WIDTH - 1:0]			rd_burst_len		,		//突发读取的长度								
	input	[ADDR_WIDTH - 1:0]			rd_burst_addr		,		//突发读取的首地址								
	output	[DATA_WIDTH - 1:0]			rd_burst_data		,		//突发读取的数据。存入读FIFO			
	output								rd_burst_ack		,		//突发读响应,高电平表示正在进行突发读操作			
	output	reg							rd_burst_done		,   	//一次突发读完成
	output	reg							rd_burst_busy		,		//突发读忙状态,高电平有效	
//MIG端控制信号----------------------------------------------	
    output	reg                			app_en				,		//MIG发送命令使能	
    input                   			app_rdy				,		//MIG命令接收准备好标致
    output		[2:0]     				app_cmd				,		//MIG操作命令,读或者写
    output	reg	[ADDR_WIDTH - 1:0]		app_addr			,		//MIG读取DDR3地址							
	input	    [DATA_WIDTH - 1:0]   	app_rd_data         ,		//MIG读出的数据
	input								app_rd_data_end     ,		//MIG读出的最后一个数据
	input								app_rd_data_valid   		//MIG读出的数据有效
);

//parameter define
localparam								READ = 3'b001;				//MIG读命令
//reg define
reg				[ADDR_WIDTH - 1:0]		rd_addr_cnt			;		//读取地址个数计数
reg				[ADDR_WIDTH - 1:0]		rd_data_cnt			;		//读取数据个数计数
reg				[ADDR_WIDTH - 1:0]		rd_burst_len_r		;		//锁存突发长度
reg				[ADDR_WIDTH - 1:0]		rd_burst_addr_r		;		//锁存突发地址
reg										rd_burst_start_r	;		//突发开始打一拍,用于锁存突发地址、突发长度   
//wire define					
wire									rd_addr_last		;		//拉高
wire									rd_data_last		;		//拉高读出最后一个数据

//*********************************************************************************************
//**                    main code
//**********************************************************************************************

//固定为读状态 
assign app_cmd = READ;
//将从MIG中读出的数据直接赋值给上级模块
assign rd_burst_data = app_rd_data;				              
//读出的最后一个地址
assign rd_addr_last = app_en && app_rdy && (rd_addr_cnt == rd_burst_len_r - 1) ;
//读出的最后一个数据
assign rd_data_last = app_rd_data_valid && (rd_data_cnt == rd_burst_len_r - 1) ;
//读响应信号,用于将MIG中的数据输出给上级模块(读出)
assign rd_burst_ack = app_rd_data_valid;

//rd_burst_start打一拍,锁存突发地址、突发长度   
always @(posedge ui_clk) begin
	if(ui_clk_sync_rst)
		rd_burst_start_r <= 0;
	else 
		rd_burst_start_r <= rd_burst_start;		
end

//锁存突发长度
always @(posedge ui_clk) begin
    if(ui_clk_sync_rst)
        rd_burst_len_r <= 1'b0;
	else if(rd_burst_start)
		rd_burst_len_r <= rd_burst_len;
end

//锁存突发地址
always @(posedge ui_clk) begin
    if(ui_clk_sync_rst)
        rd_burst_addr_r <= 1'b0;
	else if(rd_burst_start)
		rd_burst_addr_r <= rd_burst_addr;
end

//app_en控制:突发开始时一直拉高,直到突发结束   
always @(posedge ui_clk) begin
	if(ui_clk_sync_rst)
		app_en <= 0;
	else if(rd_burst_start_r)
		app_en <= 1;
	else if(rd_addr_last)
		app_en <= 0;
	else 
		app_en <= app_en;		
end

//突发读忙状态,拉高表示其处于突发读状态
always @(posedge ui_clk) begin
    if(ui_clk_sync_rst)
        rd_burst_busy <= 0;
	else if(rd_burst_start)
		rd_burst_busy <= 1;				//进入写忙状态
	else if(rd_burst_done)
		rd_burst_busy <= 0;				//突发传输完成,拉低写忙状态
	else 
		rd_burst_busy <= rd_burst_busy;		
end

//读取地址
always @(posedge ui_clk) begin
    if(ui_clk_sync_rst)
        app_addr <= 0;
	else if(rd_burst_start_r)					
		app_addr <= rd_burst_addr_r;		//将突发写的初始地址赋值给MIG
	else if(app_en && app_rdy)	
		app_addr <= app_addr + 8;		//
	else 
		app_addr <= app_addr;
end

//突发读完成信号
always @(posedge ui_clk) begin
    if(ui_clk_sync_rst)
        rd_burst_done <= 0;
	else if(rd_data_last)
		rd_burst_done <= 1;				
	else 
		rd_burst_done <= 0;		
end
//读出地址个数计数(读取突发长度计数器)
always @(posedge ui_clk) begin
    if(ui_clk_sync_rst)
        rd_addr_cnt <= 0;
	else if(rd_addr_last)
		rd_addr_cnt <= 0;
	else if(app_en && app_rdy)begin
		rd_addr_cnt <= rd_addr_cnt + 1;
	end
	else 
		rd_addr_cnt <= rd_addr_cnt;
end

//读出数据个数计数
always @(posedge ui_clk) begin
    if(ui_clk_sync_rst)
        rd_data_cnt <= 0;
	else if(rd_data_last)
		rd_data_cnt <= 0;
	else if(app_rd_data_valid)begin
		rd_data_cnt <= rd_data_cnt + 1;
	end
	else 
		rd_data_cnt <= rd_data_cnt;
end

endmodule

4、Testbench与仿真结果

4.1、Testbench

        Testbench我们把突发写模块与突发读模块一起例化来测试。预期实现功能:1、从地址0开始,突发写入512个数据,写入的数据从0累加到511;2、从地址0开始突发读取512个数据。根据读出的数据与写入数据是否一致来判断突发写、读模块是否设计成功。

        需要生成MIG IP核与DDR3的仿真模型,具体方法不讲了,可以参考:

        快速上手Xilinx DDR3 IP核(1)----MIG IP核的介绍及配置(Native接口)

        快速上手Xilinx DDR3 IP核(2)----MIG IP核的官方例程与读写测试模块(Native接口)

        完整的testbench如下:

//**************************************************************************
// *** 名称 : tb_ddr3_wr_rd
// *** 作者 : 孤独的单刀
// *** 博客 : https://blog.csdn.net/wuzhikaidetb
// *** 日期 : 2021.12
// *** 描述 : 对DDR3写模块、读模块进行测试
//**************************************************************************
`timescale 1ns/100ps

module	tb_ddr3_wr_rd();
//============================< 信号定义 >======================================                      
//parameter define-----------------------------------
parameter	integer			DATA_WIDTH	   = 128	;	    //数据位宽,突发长度为8,16bit,共128bit
parameter	integer			ADDR_WIDTH     = 28		;	    //根据MIG例化而来										   
parameter	integer			TEST_BURST_LEN = 512	;	    //测试单次突发长度
//时钟和复位 ------------------------------------
reg							sys_clk				;			//系统时钟
reg							sys_rst_n			;			//系统复位	
wire                  		ui_clk				;			//用户时钟			
wire                  		locked				;			//锁相环频率稳定标志
wire                  		clk_ref_i			;			//DDR3参考时钟
wire                  		sys_clk_i			;			//MIG IP核输入时钟
wire                  		clk_200				;			//200M时钟
wire                  		ui_clk_sync_rst		;     		//用户复位信号
wire                  		init_calib_complete	;			//校准完成信号		
//DDR3相关 --------------------------------------								
wire [15:0]     			ddr3_dq				;			//DDR3 数据
wire [1:0]      			ddr3_dqs_n			;			//DDR3 dqs负
wire [1:0]      			ddr3_dqs_p			;			//DDR3 dqs正       
wire [13:0]     			ddr3_addr			;			//DDR3 地址
wire [2:0]      			ddr3_ba				;			//DDR3 banck 选择
wire            			ddr3_ras_n			;			//DDR3 行选择
wire            			ddr3_cas_n			;			//DDR3 列选择
wire            			ddr3_we_n			;			//DDR3 读写选择
wire            			ddr3_reset_n		;			//DDR3 复位
wire [0:0]      			ddr3_ck_p			;			//DDR3 时钟正
wire [0:0]      			ddr3_ck_n			;			//DDR3 时钟负
wire [0:0]      			ddr3_cke			;			//DDR3 时钟使能
wire [0:0]      			ddr3_cs_n			;			//DDR3 片选
wire [1:0]      			ddr3_dm				;			//DDR3_dm
wire [0:0]					ddr3_odt			;			//DDR3_odt
//用户端信号 ----------------------------------- 								
wire						rd_burst_ack		;			//突发读响应					
wire						rd_burst_done		;			//突发读完成
wire						rd_burst_busy		;			//突发忙状态
wire	[DATA_WIDTH - 1:0]	rd_burst_data		;			//突发读出的数据
wire						wr_burst_ack		;			//突发写响应						
wire						wr_burst_done		;			//突发写完成
wire						wr_burst_busy		;			//突发写忙状态
reg							rd_burst_start		;			//突发读开始					
reg		[ADDR_WIDTH - 1:0]	rd_burst_len		;			//突发长度				
reg		[ADDR_WIDTH - 1:0]	rd_burst_addr		;	        //突发读首地址	
reg							wr_burst_start		;			//突发写开始				
reg		[ADDR_WIDTH - 1:0]	wr_burst_len		;			//突发写长度				
reg		[ADDR_WIDTH - 1:0]	wr_burst_addr		;			//突发首地址				
reg		[DATA_WIDTH - 1:0]	wr_burst_data		;			//突发数据
//MIG端信号--------------------------------------
wire 	[ADDR_WIDTH - 1:0]	app_addr			;			//DDR3 地址
wire 	[ADDR_WIDTH - 1:0]	app_addr_wr			;			//DDR3 地址
wire 	[ADDR_WIDTH - 1:0]	app_addr_rd			;			//DDR3 地址
wire 	[2:0]            	app_cmd				;			//用户读写命令
wire 	[2:0]            	app_cmd_wr			;			//用户读写命令
wire 	[2:0]            	app_cmd_rd			;			//用户读写命令
wire                  		app_en				;			//MIG IP核使能
wire                  		app_en_wr			;			//MIG IP核使能
wire                  		app_en_rd			;			//MIG IP核使能
wire                  		app_rdy				;			//MIG IP核空闲
wire 	[DATA_WIDTH - 1:0]	app_wdf_data		;			//用户写数据 
wire                  		app_wdf_end			;			//突发写当前时钟最后一个数据 
wire 	[15:0]           	app_wdf_mask		;			//写数据屏蔽
wire                  		app_wdf_rdy			;			//写空闲
wire                  		app_wdf_wren		;			//DDR3 写使能 
wire	[DATA_WIDTH - 1:0]	app_rd_data         ;			//MIG读出的数据
wire						app_rd_data_end     ;       	//MIG读出的最后一个数据               	                          
wire						app_rd_data_valid   ;			//MIG读出的数据有效
                	
integer		i;
//MIG 时钟赋值,200M----------------------------- 	
assign		clk_ref_i = clk_200	;							//DDR3参考时钟,200M
assign		sys_clk_i = clk_200	;							//MIG IP核输入时钟,200M
//分时复用写、读模块
assign		app_addr =	(rd_burst_busy) ?   app_addr_rd : app_addr_wr;
assign		app_cmd	 =	(rd_burst_busy) ?   app_cmd_rd : app_cmd_wr;
assign		app_en	 =	(rd_burst_busy) ?   app_en_rd : app_en_wr;	
//============================< 测试条件设置 >===============================
//设置初始测试条件--------------------------------------------------------
initial begin
	sys_clk = 0				;								//初始时钟为0
	sys_rst_n <= 0			;								//初始复位
	rd_burst_start <= 0		;			
	rd_burst_len <= 0		;		
	rd_burst_addr <= 0		;		
	wr_burst_start <= 0		;			
	wr_burst_len <= 0		;		
	wr_burst_addr <= 0		;		
	wr_burst_data <= 0		;			
	#50														//50个时钟周期后
	sys_rst_n <= 1'b1		;								//拉高复位,系统进入工作状态
//写数据---------------------------------------------------------------	
	@(posedge init_calib_complete)							//MIG初始化完成
		wr_burst_start <= 1'b1;								//开始一次突发写
		wr_burst_len <= TEST_BURST_LEN;						//突发长度512
		wr_burst_addr <= 0;									//突发起始地址0
	@(posedge ui_clk)
		wr_burst_start <= 1'b0;	
	for(i = 0; i<= TEST_BURST_LEN - 1; i = i+1)begin
		@(posedge ui_clk)
			if(wr_burst_ack)
				wr_burst_data <= wr_burst_data + 1;			//突发写入的数据从0开始累加1
			else 
				i = i - 1;			
	end
	
//读数据---------------------------------------------------------------		
	@(posedge wr_burst_done)                                //突发写完成
		rd_burst_start <= 1'b1;                             //开始一次突发读
		rd_burst_len <= TEST_BURST_LEN;                     //突发长度512
		rd_burst_addr <= 0;									//突发起始地址0
	@(posedge ui_clk)
		rd_burst_start <= 1'b0;	
end	

//设置时钟----------------------------------------------------------------
always #10 sys_clk = ~sys_clk;								//系统时钟周期20ns

//============================< 例化突发读模块 >=============================================== 
 ddr3_rd #(
	.DATA_WIDTH                     (DATA_WIDTH 		),
	.ADDR_WIDTH                     (ADDR_WIDTH 		)
 )
u_ddr3_rd(
    .ui_clk               			(ui_clk				),                
    .ui_clk_sync_rst      			(ui_clk_sync_rst	),       
	//MIG端信号	
    .app_rdy              			(app_rdy			),
    .app_addr             			(app_addr_rd		),
    .app_en               			(app_en_rd			),
    .app_cmd              			(app_cmd_rd			),	
	.app_rd_data    				(app_rd_data		),
	.app_rd_data_end                (app_rd_data_end	),
	.app_rd_data_valid              (app_rd_data_valid	),	
	//用户端信号														 
	.rd_burst_start					(rd_burst_start		),
	.rd_burst_len		            (rd_burst_len		),
	.rd_burst_addr		            (rd_burst_addr		),
	.rd_burst_data		            (rd_burst_data		),
	.rd_burst_ack		            (rd_burst_ack		),
	.rd_burst_done		            (rd_burst_done		),
	.rd_burst_busy		            (rd_burst_busy		)		
);
//============================< 例化突发写模块 >=============================================== 
ddr3_wr #(
	.DATA_WIDTH                     (DATA_WIDTH 		),
	.ADDR_WIDTH                     (ADDR_WIDTH 		)
)
u_ddr3_wr(
    .ui_clk               			(ui_clk				),                
    .ui_clk_sync_rst      			(ui_clk_sync_rst	),       
	//MIG端信号------------------------------------------	
    .app_rdy              			(app_rdy			),
    .app_addr             			(app_addr_wr		),
    .app_en               			(app_en_wr			),
    .app_cmd              			(app_cmd_wr			),	
    .app_wdf_wren         			(app_wdf_wren		),
    .app_wdf_end          			(app_wdf_end		),
    .app_wdf_data         			(app_wdf_data		),
    .app_wdf_rdy          			(app_wdf_rdy		), 
 	.app_wdf_mask					(app_wdf_mask		),	
	//用户端信号-----------------------------------------														 
	.wr_burst_start					(wr_burst_start		),
	.wr_burst_len		            (wr_burst_len		),
	.wr_burst_addr		            (wr_burst_addr		),
	.wr_burst_data		            (wr_burst_data		),
	.wr_burst_ack		            (wr_burst_ack		),
	.wr_burst_done		            (wr_burst_done		),
	.wr_burst_busy		            (wr_burst_busy		)
);
//============================< 例化MIG IP核模块 >=============================================== 
mig_7series_0 u_mig_7series_0 (
	//DDR3接口-------------------------------------------
    .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_reset_n                   (ddr3_reset_n		),
    .ddr3_we_n                      (ddr3_we_n			),   
    .ddr3_dq                        (ddr3_dq			),     
    .ddr3_dqs_n                     (ddr3_dqs_n			),  
    .ddr3_dqs_p                     (ddr3_dqs_p			),                                                    
	.ddr3_cs_n                      (ddr3_cs_n			),   
    .ddr3_dm                        (ddr3_dm			),     
    .ddr3_odt                       (ddr3_odt			),
	//用户接口-------------------------------------------    
    .app_addr                       (app_addr			),    
    .app_cmd                        (app_cmd			),     
    .app_en                         (app_en				),
    .app_rdy                        (app_rdy			), 	
    .app_wdf_mask                   (app_wdf_mask		),                                                    
    .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_sr_req                     (1'b0				), 
    .app_ref_req                    (1'b0				), 
    .app_zq_req                     (1'b0				), 
    .app_sr_active                  (					),
    .app_ref_ack                    (					),  
    .app_zq_ack                     (					),
	//全局信号-------------------------------------------	
    .ui_clk                         (ui_clk				),       
    .ui_clk_sync_rst                (ui_clk_sync_rst	), 
    .init_calib_complete            (init_calib_complete), 														      
    .sys_clk_i                      (sys_clk_i			),
    .clk_ref_i                      (clk_ref_i			),
    .sys_rst                        (sys_rst_n			)     
);
//============================< 例化PLL模块 >=============================================== 
clk_wiz_0 u_clk_wiz_0
   (
    .clk_out1						(clk_200			),     	// output clk_out1
    .reset							(1'b0				),		// input resetn
    .locked							(locked				),		// output locked
    .clk_in1						(sys_clk			)		// input clk_in1
); 
//============================< 例化DDR3模型 >===============================================    
ddr3_model	ddr3_model_inst
(	
	.rst_n   						(sys_rst_n			),	
	.ck      						(ddr3_ck_p			),	
	.ck_n    						(ddr3_ck_n			),	
	.cke     						(ddr3_cke			),	
	.cs_n    						(ddr3_cs_n			),	
	.ras_n   						(ddr3_ras_n			),	
	.cas_n   						(ddr3_cas_n			),	
	.we_n    						(ddr3_we_n			),	
	.dm_tdqs 						(ddr3_dm			),	
	.ba      						(ddr3_ba			),	
	.addr    						(ddr3_addr			),	
	.dq      						(ddr3_dq			),	
	.dqs     						(ddr3_dqs_p			),	
	.dqs_n   						(ddr3_dqs_n			),	
	.tdqs_n  						(					),			//NULL
	.odt     						(ddr3_odt			)	
);

endmodule

4.2、仿真结果

        接下来我们分别观看各个子模块的仿真结果来验证设计功能。

4.2.1、MIG IP模块

        在本次设计中,MIG IP模块是最直接对DDR3芯片操作的模块,我们先来观察MIG IP的仿真结果如何:

        从上图中可以看到:在MIG初始化完成后,开始了一轮突发写数据的过程,然后突发写停止,开始一轮突发读数据的过程。虽然数据的具体大小从上图无法看到,但至少整个流程是符合我们的预先设计的。再分别截取局部图。

        上图中:MIG初始化完成后开始突发写数据,写入的地址从0开始,往后每写入一个数据,地址累加8----DDR3固定突发长度为8,写入的数据从0开始累加1.目前是符合设计要求的。

        上图中:是突发写的结束阶段。写入的最后一个地址是4088,写入的最后一个数据是511,说明一共写入了512个数据(0-511)。最后阶段已经开始进行了突发读操作。目前也是符合设计要求的。

        看完了突发写操作,再看看突发读操作相关截图。

        上图中:是突发读的开始阶段。MIG从地址0开始进行读取操作,其后读取的地址累加8。从发出第一条读指令到第一个读取的数据(伴随读取有效信号)出现需要几个周期的时间,可以看到读出的数据也是从0开始累加1,与写入的数据一致。证明我们的读写操作无误。

        上图中:是突发读的结束阶段。最后一个读取的地址是4088,读出的最后一个数据是511,同样与写入的数据一致。

        从这里可以看到整个仿真是符合我们预期设计的。但是我们还需要看一下设计的突发写模块ddr3_wr以及突发读模块ddr3_rd的仿真波形。

4.2.2、突发写模块ddr3_wr

        老规矩,先看下整体过程,了解下大概:

        上图中:拉高了突发写开始信号,标志开始一轮突发写操作(突发长度512、突发首地址0),然后突发写忙信号被拉高,表示此时正在进行突发写操作(可用于后续写、读仲裁的判断),接着不停地写入数据,待写入最后一个数据后,拉高突发写完成标志,表示一次突发写操作完成,同时拉低突发写忙信号,表示此时没有进行突发写操作。

         上图中:突发写操作的首地址为0,其后地址累加8,写入的数据从0开始累加1。

        上图中:突发写操作的地址累加8,累加1直到511。完成最后一个突发写操作后(写入数据511),拉高突发写完成标志,表示一次突发写操作完成,同时拉低突发写忙信号,表示此时没有进行突发写操作。

4.2.3、突发读模块ddr3_rd

        老规矩,先看下整体过程,了解下大概:

        上图中:拉高了突发读开始信号,标志开始一轮突发读操作(突发长度512、突发首地址0),然后突发读忙信号被拉高,表示此时正在进行突发读操作(可用于后续写、读仲裁的判断),一段时间后不停地读出数据,待读出最后一个数据后,拉高突发读完成标志,表示一次突发读操作完成,同时拉低突发读忙信号,表示此时没有进行突发读操作。 

        上图中:从地址0开始读取数据,其后地址累加8,发出读指令后需要一定的时间才能读到对应地址的数据。读出的数据与写入的数据一致。

         上图中:对最后一个地址进行读取后,一段时间读到最后一个数据511。然后拉高突发读完成标志,表示一次突发读操作完成,同时拉低突发读忙信号,表示此时没有进行突发读操作。 

5、其他

  • 我们下一篇再来讲讲FIFO控制模块,以及将整个类FIFO模块仿真和下板验证。
  • 创作不易,如果本文对您有帮助,还请多多点赞、评论和收藏。您的支持是我持续更新的最大动力!
  • 关于本文,您有什么想法均可在评论区留言交流。如果需要整个工程,请在评论留下邮箱或者私信我邮箱(注意保护隐私)。
  • 自身能力不足,如有错误还请多多指出!