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

写在前面

        接上一篇文章(配置MIG IP过程):

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

        DDR3系列文章:

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


1、官方例程(example design)

        在我心中,Xilinx是一家完美的公司(自动忽略vivado编译太慢),技术生态支持实在是做的太好了。Xilinx也知道我们不会用DDR3,所以提供了一个example design给你学习,怎么样?惊不惊喜?意不意外?(实际上,绝大多数的IP Xilinx都提供了example design~~)

        那么接下来就一起学习下官方提供的参考设计呗。

1.1、示例生成步骤

        右击生成的IP核(默认你已经生成了MIG IP核),选择open IP example design,选择好路径后就会生成一个新的工程mig_7series_0_ex。

        打开工程mig_7series_0_ex,看下整个工程的结构----2个主要部分:1、MIG IP核;2、读写测试的数据生成模块

        读写测试模块我们不展开讲(太多了,一时半会讲不完),我们直接通过波形来学习。

        点击RUN SIMULATION,直接执行仿真(这里可能等几分钟(吃电脑配置)----强烈建议使用Modelsim仿真,速度快的不是一星半点) 

1.2、仿真结果

        仿真结果出来后,只保留一些比较有用的信号,并进行分组,如下:

         

找init_calib_complete拉高后(DDR3初始化完成)的一些局部时序图,如下图:

        蓝线、和黄线处,app_cmd为0,app_en、app_rdy均为高,代表在执行写命令操作;同时app_wdf_data同地址一致,app_wdf_en、app_wdf_rdy均为高,代表在进行写数据操作

如下图:

        很多个周期的app_rdy拉低,代表MIG没有准备好进行写数据命令,但此时app_wdf_data同地址一致,app_wdf_en、app_wdf_rdy均为高,代表在进行写数据操作。这正是前面章节提到了写数据提前与写命令,因为MIG种有一个FIFO,将要写入的数据存入FIFO,等到写命令有效时,再对FIFO进行读取。这样做的好处是可以很大程度上做到写命令和写数据分离。

如下图:

         蓝线处,app_cmd为1,app_en、app_rdy均为高,地址app_addr为0x0000200,代表在执行读命令操作。经过若干个周期后,黄线处读取数据有效标志信号app_rd_data_valid拉高,代表成功读取到了一个数据,数据为0x0000200000020000002000000200,与前面写入的一致

2、读写测试模块

        通过上节对官方例程的学习,已经初步了解了DDR3的接口时序,接下来就自己编写一个简单的读写测试模块来对DDR3操作一番。(不当云玩家)

2.1、Verilog代码

        读写测试模块预期要实现的功能:

  • 写入一定量的数据(可设置,默认512个)到DDR3,写入地址从0开始
  • 从地址0开始读取之前写入DDR3的数据,同时判断读、写数据是否一致
  • 循环上两个步骤,即写、读、写、读····

         不妨先来回顾下DDR3提供的Native接口的时序。

        DDR3 的读或者写都包含写命令操作,其中写操作命令(app_cmd)的值等于 0,读操作 app_cmd 的值 等于 1。首先来看写命令时序,如下图所示。首先检查 app_rdy,为高则表明此时 IP 核命令接收处于准备好 状态,可以接收用户命令,在当前时钟拉高 app_en,同时发送命令(app_cmd)和地址(app_addr),此时命令和地址被写入。

        写数据的时序,如下图所示: 

        写数据的情况有3种:

  1. 写命令与写数据发生在同一时钟周期
  2. 写数据先于写命令发生(不一定是图上的一个时钟周期,因为数据是先写到了FIFO)发生在同一时钟周期
  3. 写数据落后于写命令发生,但不能超过两个时钟周期

        结合上图,写时序总结如下:首先需要检查 app_wdf_rdy,该信号为高表明此时 IP 核数据接收处于准备完成状态,可以接收用户发过来的数据,在当前时钟拉高写使能(app_wdf_wren),给出写数据 (app_wdf_data)。这样加上发起的写命令操作就可以成功向 IP 核写数据。

        接着来看读数据,如下图所示:

        读时序比较简单,发出读命令后,用户只需等待数据有效信号(app_rd_data_valid)拉高,为高表明此 时数据总线上的数据是有效的返回数据。需要注意的是,在发出读命令后,有效读数据要晚若干周期才出现在数据总线上 (延迟的时间不定)。

2.1.1、PLL模块

        本来仿真是不需要PLL模块的(直接写时钟激励就行),但是考虑到等下还需要上板测试,而我手头的开发板主频50M,MIG的工作时钟却是200M,所以需要PLL模块生成工作时钟。具体配置略。

2.1.2、DDR3仿真模型

        在生成的example design中提供了DDR3的仿真模型,共两个文件:

1、DDR3仿真模型的头文件

2、DDR3仿真模型

找到这两个文件,把它们添加进我们自己的测试工程里。

2.1.3、读写测试模块

        读写测试模块生成对MIG IP核的控制时序,并使用一个状态机来实现循环写、读的过程。状态机如下:

  • IDLE:初始状态,等MIG IP核初始化完成后跳转到写数据状态WRITE
  • WRITE:写数据状态,在这个状态向MIG IP核写入一定量的数据(测试为512个)。当写入最后一个数据时,同步跳转到等待状态WAIT
  • WAIT:过渡状态,仅维持一个周期
  • READ:读数据状态,在这个状态从MIG IP核读取一定量的数据(测试为512个)。当读取最后一个数据时,同步跳转到初始状态IDLE。开始新一轮的写、读过程

        前面说到,DDR3写数据的时候有3种模式,我们在测试的时候,固定使用“写命令与写数据发生在同一时钟周期这一模式。这样一来,代码编写会简单很多,但相应的会牺牲一点点效率(不会造成多大的影响)。

        完整的读写模块测试代码如下:

//**************************************************************************
// *** 名称 : ddr3_rw
// *** 作者 : 孤独的单刀
// *** 博客 : https://blog.csdn.net/wuzhikaidetb
// *** 日期 : 2021.12
// *** 描述 : 对DDR3进行循环读写
//**************************************************************************

//============================< 端口 >======================================
module ddr3_rw #
( 
	parameter	integer					WR_LEN		= 1024		,	//读、写长度
	parameter	integer					DATA_WIDTH	= 128		,	//数据位宽,突发长度为8,16bit,共128bit
	parameter	integer					ADDR_WIDTH	= 28			//根据MIG例化而来
)(   
//DDR3相关 ------------------------------------------------------      
    input                   			ui_clk					,	//用户时钟
    input                   			ui_clk_sync_rst			,	//复位,高有效
    input                   			init_calib_complete		,	//DDR3初始化完成
//DDR3相关 ------------------------------------------------------	
    input                   			app_rdy					,	//MIG 命令接收准备好标致
    input                   			app_wdf_rdy				,	//MIG数据接收准备好
    input                   			app_rd_data_valid		,	//读数据有效
    input		[DATA_WIDTH - 1:0]   	app_rd_data				,	//用户读数据
    output	reg	[ADDR_WIDTH - 1:0]		app_addr				,	//DDR3地址                      
    output	                			app_en					,	//MIG IP发送命令使能
    output	                			app_wdf_wren			,	//用户写数据使能
    output	                			app_wdf_end				,	//突发写当前时钟最后一个数据 
    output		[2:0]     				app_cmd					,	//MIG IP核操作命令,读或者写
    output	reg	[DATA_WIDTH - 1:0]		app_wdf_data			,	//用户写数据
//指示 ----------------------------------------------------------		
    output	reg             			error_flag     	    	   	//读写错误标志
    );
	
//============================< 信号定义 >======================================
//测试状态机-----------------------------------------				
localparam					IDLE	= 4'b0001		;            	//空闲状态
localparam					WRITE 	= 4'b0010		;            	//写状态
localparam					WAIT  	= 4'b0100		;            	//读到写过度等待
localparam					READ  	= 4'b1000		;            	//读状态
//reg define ----------------------------------------
reg	[3:0]					cur_state				;				//三段式状态机现态
reg	[3:0]					next_state				;				//三段式状态机次态
reg	[ADDR_WIDTH - 1:0]		rd_addr_cnt				;				//用户读地址计数
reg	[ADDR_WIDTH - 1:0]		wr_addr_cnt				;				//用户写地址计数
reg	[ADDR_WIDTH - 1:0]		rd_cnt					;				//实际读地址标记
//wire define ---------------------------------------										
wire						error					;     			//读写错误标记
wire						rst_n					;     			//复位,低有效
wire						wr_proc					;				//拉高表示写过程进行
wire						wr_last					;				//拉高表示写入最后一个数据
wire						rd_addr_last			;				//拉高表示是最后一个读地址

 //*********************************************************************************************
//**                    main code
//**********************************************************************************************
//==========================================================================
//==    信号赋值
//==========================================================================  
assign rst_n = ~ui_clk_sync_rst;
//当MIG准备好后,用户同步准备好
assign app_en = app_rdy && ((cur_state == WRITE && app_wdf_rdy) || cur_state == READ);              
//写指令,命令接收和数据接收都准备好,此时拉高写使能
assign app_wdf_wren = (cur_state == WRITE) && wr_proc;
//由于DDR3芯片时钟和用户时钟的分频选择4:1,突发长度为8,故两个信号相同
assign app_wdf_end = app_wdf_wren; 
assign app_cmd = (cur_state == READ) ? 3'd1 :3'd0;					//处于读的时候命令值为1,其他时候命令值为0	
assign wr_proc = ~app_cmd && app_rdy && app_wdf_rdy;				//拉高表示写过程进行
//处于写使能且是最后一个数据
assign wr_last = app_wdf_wren && (wr_addr_cnt == WR_LEN - 1) ;
//处于读指令、读有效且是最后一个数据
assign rd_addr_last = (rd_addr_cnt == WR_LEN - 1) && app_rdy && app_cmd;
  
//==========================================================================
//==    状态机
//==========================================================================    

always @(posedge ui_clk or negedge rst_n) begin
	if(~rst_n)
		cur_state <= IDLE;
	else
		cur_state <= next_state;
end

always @(*) begin
	if(~rst_n)
		next_state = IDLE;
	else
		case(cur_state)
			IDLE:
				if(init_calib_complete)					//MIG IP核初始化完成 
					next_state = WRITE;				
				else				
					next_state = IDLE;				
			WRITE:				
				if(wr_last) 							//写入最后一个数据
					next_state = WAIT;				
				else				
					next_state = WRITE;							
			WAIT:				
					next_state = READ;				
			READ:				
				if(rd_addr_last) 						//写入最后一个读地址,数据读出需要时间
					next_state = IDLE;
				else
					next_state = READ;					
			default:;
		endcase
end

always @(posedge ui_clk or negedge rst_n) begin
    if(~rst_n) begin				 
        app_wdf_data <= 0;     
        wr_addr_cnt  <= 0;      
        rd_addr_cnt  <= 0;       
        app_addr     <= 0;          
    end
	else
        case(cur_state)
            IDLE:begin
                app_wdf_data <= 0;   
                wr_addr_cnt  <= 0;     
                rd_addr_cnt  <= 0;       
                app_addr     <= 0;
             end
            WRITE:begin
                if(wr_proc)begin   						//写条件满足
                    app_wdf_data <= app_wdf_data + 1;  	//写数据自加
                    wr_addr_cnt  <= wr_addr_cnt + 1;   	//写地址自加
                    app_addr     <= app_addr + 8;      	//DDR3 地址加8
                end
                else begin                             	//写条件不满足,保持当前值
                    app_wdf_data <= app_wdf_data;      
                    wr_addr_cnt  <= wr_addr_cnt;
                    app_addr     <= app_addr; 
                end
              end
            WAIT:begin                                                  
                rd_addr_cnt <= 0;                		//读地址复位
                app_addr    <= 0;                		//DDR3读从地址0开始
              end
            READ:begin                               	//读到设定的地址长度     
                if(app_rdy)begin                  		//若MIG已经准备好,则开始读
                    rd_addr_cnt <= rd_addr_cnt + 1'd1; 	//用户地址每次加一
                    app_addr    <= app_addr + 8;       	//DDR3地址加8
                end
                else begin                             	//若MIG没准备好,则保持原值
                    rd_addr_cnt <= rd_addr_cnt;
                    app_addr    <= app_addr; 
                end
              end
            default:begin
                app_wdf_data <= 0;
                wr_addr_cnt  <= 0;
                rd_addr_cnt  <= 0;
                app_addr     <= 0;
            end
        endcase
end   
//==========================================================================
//==    其他
//==========================================================================

//读信号有效,且读出的数不是写入的数时,将错误标志位拉高
assign error = (app_rd_data_valid && (rd_cnt!=app_rd_data));  
                      
//寄存状态标志位
always @(posedge ui_clk or negedge rst_n) begin
    if(~rst_n) 
        error_flag <= 0;
    else if(error)
        error_flag <= 1;
end
 
//对DDR3实际读数据个数编号计数
always @(posedge ui_clk or negedge rst_n) begin
    if(~rst_n) 
        rd_cnt <= 0;
	//若计数到读写长度,且读有效,地址计数器则置0   		
    else if(app_rd_data_valid && rd_cnt == WR_LEN - 1)
         rd_cnt <= 0;              
    else if (app_rd_data_valid )	//读有效情况下每个时钟+1
        rd_cnt <= rd_cnt + 1;
end
 
endmodule

        注释还是比较详细的,就不讲解了,只说几个需要注意的点。

  • DDR3的突发长度固定为8,选择的DDR3芯片为16bit,所以数据位宽 = 8 * 16bit = 128bit
  • DDR3的突发长度固定为8,等于每次操作实际上是对8个地址操作,所以每次写入数据的地址需要累加8

2.1.4、顶层模块

        顶层模块的设计十分简单:只要例化上述3个模块即可。如下:

//**********************************************************************************
// *** 名称 : ddr3_rw_top
// *** 作者 : 孤独的单刀
// *** 博客 : https://blog.csdn.net/wuzhikaidetb
// *** 日期 : 2021.12
// *** 描述 : 顶层模块----例化DDR3仿真模型与DDR3读写测试模块,对DDR3进行循环读写测试
//**********************************************************************************

//============================< 端口 >======================================
module ddr3_rw_top(
//时钟和复位 ---------------------------
   input              	sys_clk			,         	//系统时钟
   input              	sys_rst_n		,       	//复位,低有效
//DDR3相关 -----------------------------
   inout	[15:0]		ddr3_dq			,         	//DDR3 数据
   inout	[1:0]		ddr3_dqs_n		,      		//DDR3 dqs负
   inout	[1:0]		ddr3_dqs_p		,      		//DDR3 dqs正       
   output	[13:0]      ddr3_addr		,       	//DDR3 地址
   output	[2:0]       ddr3_ba			,         	//DDR3 banck 选择
   output	            ddr3_ras_n		,      		//DDR3 行选择
   output	            ddr3_cas_n		,      		//DDR3 列选择
   output	            ddr3_we_n		,       	//DDR3 读写选择
   output	            ddr3_reset_n	,    		//DDR3 复位
   output	[0:0]       ddr3_ck_p		,       	//DDR3 时钟正
   output	[0:0]       ddr3_ck_n		,       	//DDR3 时钟负
   output	[0:0]       ddr3_cke		,        	//DDR3 时钟使能
   output	[0:0]       ddr3_cs_n		,       	//DDR3 片选
   output	[1:0]       ddr3_dm			,         	//DDR3_dm
   output	[0:0]       ddr3_odt		,        	//DDR3_odt
//标志相关------------------------------
   output             	error_flag					//错误指示信号
);
                
//============================< 信号定义 >======================================                      
//parameter define
parameter	integer		WR_LEN		= 512	;		//读、写长度	
parameter	integer		DATA_WIDTH	= 128	;	    //数据位宽,突发长度为8,16bit,共128bit
parameter	integer		ADDR_WIDTH	= 28	;	    //根据MIG例化而来
//wire define  
wire                  	ui_clk				;		//用户时钟
wire [ADDR_WIDTH - 1:0]	app_addr			;		//DDR3 地址
wire [2:0]            	app_cmd				;		//用户读写命令
wire                  	app_en				;		//MIG IP核使能
wire                  	app_rdy				;		//MIG IP核空闲
wire [DATA_WIDTH - 1:0]	app_rd_data			;		//用户读数据
wire                  	app_rd_data_end		;     	//突发读当前时钟最后一个数据 
wire                  	app_rd_data_valid	;   	//读数据有效
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                  	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	;		//校准完成信号

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

//============================< 例化DDR3读写测试模块 >======================================
 ddr3_rw #(
	.WR_LEN							(WR_LEN					),
	.DATA_WIDTH                     (DATA_WIDTH 			),
	.ADDR_WIDTH                     (ADDR_WIDTH 			)
 )
 u_ddr3_rw(
    .ui_clk               			(ui_clk					),                
    .ui_clk_sync_rst      			(ui_clk_sync_rst		),       
    .init_calib_complete  			(init_calib_complete	),
	
    .app_rdy              			(app_rdy				),
    .app_wdf_rdy          			(app_wdf_rdy			),
    .app_rd_data_valid    			(app_rd_data_valid		),
    .app_rd_data          			(app_rd_data			),   
    .app_addr             			(app_addr				),
    .app_en               			(app_en					),
    .app_wdf_wren         			(app_wdf_wren			),
    .app_wdf_end          			(app_wdf_end			),
    .app_cmd              			(app_cmd				),
    .app_wdf_data         			(app_wdf_data			),
   
    .error_flag           			(error_flag				)
    );	
//============================< 例化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_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_wdf_mask                   (31'b0				),                                                    
    .app_rdy                        (app_rdy			), 
    .app_wdf_rdy                    (app_wdf_rdy		), 
    .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                      (clk_200			),
    .clk_ref_i                      (clk_200			),
    .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
);                     

endmodule

2.2、Testbench及仿真结果

        好的,Verilog代码写完了,接下来就跑跑仿真验证以下。

2.2.1、Testbench

        Testbench也很简单,只需要提供时钟、复位激励;例化读写测试模块及DDR3仿真模型就好了,如下:

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

module	tb_ddr3_rw_top();
//============================< 信号 >======================================
//时钟和复位 --------------------
reg				sys_clk			;				//系统时钟
reg				sys_rst_n		;				//系统复位
//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			error_flag		;				//读、写错误标志

//============================< 测试条件设置 >===============================
//设置初始测试条件--------------------------------------------------------
initial begin
	sys_clk = 1'b0			;					//初始时钟为0
	sys_rst_n <= 1'b0		;					//初始复位
	#50											//5个时钟周期后
	sys_rst_n <= 1'b1		;					//拉高复位,系统进入工作状态
end
//设置时钟----------------------------------------------------------------
always #10 sys_clk = ~sys_clk;					//系统时钟周期20ns


//============================< 被测试模块例化 >===============================
//例化DDR3读写测试-------------------
ddr3_rw_top	ddr3_rw_top_inst(
   .sys_clk			(sys_clk		),			//系统时钟
   .sys_rst_n		(sys_rst_n		),			//复位,低有效
   .ddr3_dq			(ddr3_dq		),			//DDR3 数据
   .ddr3_dqs_n		(ddr3_dqs_n		),      	//DDR3 dqs负
   .ddr3_dqs_p		(ddr3_dqs_p		),      	//DDR3 dqs正       
   .ddr3_addr		(ddr3_addr		),       	//DDR3 地址
   .ddr3_ba			(ddr3_ba		),         	//DDR3 banck 选择
   .ddr3_ras_n		(ddr3_ras_n		),      	//DDR3 行选择
   .ddr3_cas_n		(ddr3_cas_n		),      	//DDR3 列选择
   .ddr3_we_n		(ddr3_we_n		),       	//DDR3 读写选择
   .ddr3_reset_n	(ddr3_reset_n	),    		//DDR3 复位
   .ddr3_ck_p		(ddr3_ck_p		),       	//DDR3 时钟正
   .ddr3_ck_n		(ddr3_ck_n		),       	//DDR3 时钟负
   .ddr3_cke		(ddr3_cke		),        	//DDR3 时钟使能
   .ddr3_cs_n		(ddr3_cs_n		),       	//DDR3 片选
   .ddr3_dm			(ddr3_dm		),         	//DDR3_dm
   .ddr3_odt		(ddr3_odt		),       	//DDR3_odt
   .error_flag		(error_flag		)			//错误标志
);   

//例化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

2.2.2、仿真结果

        仿真我们不用Vivado跑了(不是说慢,是真慢),改用Modelsim跑。Modelsim跑仿真速度快,来来回回的修改调试也方便。

        废话少说,仿真结果如下:

        上图中:MIG初始化完成信号init_calib_complete拉高,表示MIG控制器初始化完成。然后,进入开始写数据,再读数据。如此不停重复。  

        上图是:写数据过程的开头部分:

                写入地址从0开始累加,每次累加8,因为突发长度为8;

                写入数据从0开始累加,每次累加1;

                当命令有效、写命令有效,且写指令为低(代表写)时,数据被写入。

        上图是:写数据过程的结束部分:

                写入地址从0开始累加,每次累加8,因为突发长度为8,到4088截止;

                写入数据从0开始累加,每次累加1,到511截止,共写入512个数据;

                当命令有效、写命令有效,且写指令为低(代表写)时,数据被写入。

        上图是:读数据过程的开始部分:

                读取地址从0开始累加,每次累加8,因为突发长度为8;

                从发出读取指令到真正读到数据需要一定的时间;

                当命令有效,且指令为高(代表读)时,数据被读出;

                读出的数据与写入的数据一致(0-1-2-······)。

        上图是:读数据过程的结束部分:

                读取地址从0开始累加,每次累加8,因为突发长度为8,到4088;

                从发出读取指令到真正读到数据需要一定的时间;

                当命令有效,且指令为高(代表读)时,数据被读出;

                读出的数据与写入的数据一致(0-1-2-······511)。

3、上板验证

3.1、验证环境

        FPGA:XC7A35T FGG484-2

        DDR3:NT5CB128M16CP-DI

        Vivado:Vivado 2019.2

        Modelsim:Modelsim SE-64 2020.4

        文件:V1.0

        编号:71

3.2、验证结果

        结果基本与仿真结果一致,写、读验证无误,读取数据一致。截几张图贴出来

        初始化完成后进行的写操作:

        读操作:

        至此,读写模块验证成功。 

3、其他

3.1、可能遇到的问题:

3.1.1、Vivado如何联合modelsim仿真?

        使用modelsim仿真xilinx的IP核,主要需要解决仿真库的问题。不得不说modelsim是真的好用。

        供参考:vivado与modelsim的联合仿真

3.1.2、使用modelsim仿真DDR3报错Module ‘SIP_PHY_CONTROL‘ is not defined

        没找到什么好办法,重装了新版本的modelsim(2020.4版本)。重装以后问题解决。

3.1.3、VIVADO调用 ModelSim一直无响应

        基本上是语法错误,请仔细检查。

        供参考:Xilinx VIVADO 仿真时无法调用 ModelSim 失败的解决办法

3.1.4、app_rdy信号被拉高一段时间后,然后一直为低的问题

        下面三个保留的信号全接到0后解决(Xilinx都在手册告诉你这么做了,你又不听,哎······)

3.2、后记

  • 可以看到,Native接口其实相对来讲已经很好用了。但是有没有办法用起来更方便一点?当然有咯!FIFO用起来方不方便,一个使能信号就能写数据了。我们接下来就把MIG给封装成一个FIFO来用,大大简化工作量。
  • 创作不易,如果本文对您有帮助,还请多多点赞、评论和收藏。您的支持是我持续更新的最大动力!
  • 关于本文,您有什么想法均可在评论区留言交流。如果需要整个工程,请在评论留下邮箱或者私信我邮箱(注意保护隐私)。
  • 自身能力不足,如有错误还请多多指出!

猜你喜欢

转载自blog.csdn.net/wuzhikaidetb/article/details/121646652
今日推荐