基于Vivado MIG IP核的DDR3读写实验(top_rom_ddr/ddr_top)

一、前言

二、实验内容

本次实验的内容主要是通过MIG IP核向DDR3读写数据,DDR3的接口时序由ddr_top模块提供:

  • ddr_top模块的数据来源是wr_fifo,wr_fifo的数据实际来自top_sd_photo模块(本实验仿真时在tb文件中手动提供数据)
  • ddr_top模块的数据输出到rd_fifo,rd_fifo的数据被timing_gen模块读出(本实验只输出观察)。

top_sd_phototiming_gen模块在本专栏中前面的文章中都介绍过,这里就不说了,重点只是如何提供MIG IP核用户端的接口时序,从而对DDR3完成读写操作,也即ddr_top模块的设计。

1、顶层模块:
在这里插入图片描述
2、ddr_top模块:
在这里插入图片描述

  • mem_test模块:发送读写请求
  • mem_burst模块:提供MIG IP核用户端的接口时序
  • DDR3_CONTROL:MIG IP核,用来将用户端的读写时序转换成直接提供给DDR3的读写时序

三、程序设计

这里我们只讲解ddr_top中的三个子模块。

1、mem_test 模块:就是利用状态机生成读写请求、读写数据长度、读写起始地址的信号,并发送到mem_burst模块。

module mem_test
#(
	parameter MEM_DATA_BITS = 256, //8突发,8*32=256
	parameter ADDR_BITS = 29
)
(
    input rd_fifo_full,							  	  //来自rd_fifo的满信号
	input rst,                                        //复位
	input mem_clk,                                    //来自mig IP核的用户时钟

	//mem_burst连接信号
	input wr_burst_data_req,                          //ddr写数据信号
	input rd_burst_finish,                            //ddr读完成
	input wr_burst_finish,                            //ddr写完成
	output reg rd_burst_req,                          //ddr读请求
	output reg wr_burst_req,                          //ddr写请求
	output reg[15:0] rd_burst_len,                    //读ddr数据长度
	output reg[15:0] wr_burst_len,                    //写ddr数据长度
	output reg[ADDR_BITS - 1:0] rd_burst_addr,        //读ddr首地址
	output reg[ADDR_BITS - 1:0] wr_burst_addr,        //写ddr首地址
	
	//output[MEM_DATA_BITS - 1:0] wr_burst_data,        //输出给ddr的测试数据 正常应该是wr_fifo给数据
    output reg read_ddr	//输出给外部,用来标志rd_fifo可以读取的信号
);
localparam IDLE = 3'd0;
localparam MEM_WRITE  = 3'd1;
localparam MEM_READ = 3'd2;


reg[2:0] state;
always@(posedge mem_clk or posedge rst)
begin
	if(rst)
	begin
		state <= IDLE;
		wr_burst_req <= 1'b0;
		rd_burst_req <= 1'b0;
		rd_burst_len <= 16'd0;          	
		wr_burst_len <= 16'd0;			//512*512/8 = 32768个数一次写入
		rd_burst_addr <= 0;                 //读写起始地址均为0
		wr_burst_addr <= 0;
		read_ddr <= 0;
	end
	else
	begin
		case(state)
			IDLE:
			begin
				state <= MEM_WRITE;         //复位后进入写操作,将写请求拉高,发送到mem_burst模块
				
				wr_burst_req <= 1'b1;
				wr_burst_len <= 16'd30;
				wr_burst_addr <= 29'h0;
			end
			MEM_WRITE:
			begin
				if(wr_burst_finish)//写突发结束进入读突发阶段
				begin
					state <= MEM_READ;     //检测到wr_burst_finish后,将状态切换至read状态
					wr_burst_req <= 1'b0;  //写请求拉低,读请求拉高,给出读数据个数,并给出读的起始地址,发送到mem_burst模块
					rd_burst_len <= 16'd30;
					rd_burst_addr <= 29'h0;
				end
			end
			MEM_READ:
			begin
				if(rd_burst_finish)//读突发结束拉高read_ddr,告诉外部可以读取rd_fifo了
								   //因为从ddr读出的数据会先缓存到rd_fifo中
				begin
					read_ddr <= 1;
					rd_burst_req <= 1'b0;				
				end
				else if(!rd_fifo_full)
					rd_burst_req <= 1'b1;    
				else 
					rd_burst_req <= 0;
			end
			default:
				state <= IDLE;
		endcase
	end
end

endmodule

2、mem_burst模块:也是利用状态机以及其他逻辑生成MIG IP核用户端所需的各种信号。

module mem_burst
#(
	parameter MEM_DATA_BITS = 256,//8突发,8*32=256
	parameter ADDR_BITS = 29
)
(
	input rst,                                   
	input mem_clk,                               /*来自mig IP核的用户时钟*/

	//mem_test模块的连接信号
	input rd_burst_req,                          /*读ddr请求*/
	input wr_burst_req,                          /*写ddr请求*/ 
	input[15:0] rd_burst_len,                    /*读数据长度*/
	input[15:0] wr_burst_len,                    /*写数据长度*/
	input[ADDR_BITS - 1:0] rd_burst_addr,        /*读首地址*/
	input[ADDR_BITS - 1:0] wr_burst_addr,        /*写首地址*/
	output rd_burst_finish,                      /*读完成*/
	output wr_burst_finish,                      /*写完成*/


	//wr_fifo的连接信号
	input wr_fifo_empty,						 /*来自wr_fifo的fifo空信号,因为要向该fifo读数据,所以关注空信号*/
	input[MEM_DATA_BITS - 1:0] wr_burst_data,    /*来自wr_fifo的要写入ddr的数据*/
	output  wr_burst_data_req,                	 /*输出给wr_fifo的数据请求信号,作为其rd_en信号*/

	//rd_fifo的连接信号
	input rd_fifo_full,						 	 /*来自rd_fifo的满信号,因为要往该fifo写数据,所以关注满信号*/
	output rd_burst_data_valid,                  /*数据有效信号,作为rd_fifo的wr_en信号*/
	output[MEM_DATA_BITS - 1:0] rd_burst_data,   /*ddr读出的要写入rd_fifo的数据*/
	
	
	
	//下面的信号输出给mig IP核
	output[ADDR_BITS-1:0]                       app_addr,
	output[2:0]                                 app_cmd,
	output                                      app_en,
	output [MEM_DATA_BITS-1:0]                  app_wdf_data,
	output                                      app_wdf_end,
	output [MEM_DATA_BITS/8-1:0]                app_wdf_mask,
	output                                      app_wdf_wren,
	
	//下面的信号来自mig IP核
	input [MEM_DATA_BITS-1:0]                   app_rd_data,
	input                                       app_rd_data_end,
	input                                       app_rd_data_valid,
	input                                       app_rdy,
	input                                       app_wdf_rdy,
	input                                       init_calib_complete,
   
   	output reg write_done
);

assign app_wdf_mask = {MEM_DATA_BITS/8{1'b0}};


localparam IDLE = 3'd0;
localparam MEM_READ = 3'd1;
localparam MEM_READ_WAIT = 3'd2;
localparam MEM_WRITE  = 3'd3;
localparam MEM_WRITE_WAIT = 3'd4;
localparam READ_END = 3'd5;
localparam WRITE_END = 3'd6;


reg[2:0]  state;	
reg[15:0] rd_addr_cnt;
reg[15:0] rd_data_cnt;
reg[15:0] wr_addr_cnt;
reg[15:0] wr_data_cnt;

reg[2:0] app_cmd_r;
reg[ADDR_BITS-1:0] app_addr_r;
reg app_en_r;
reg app_wdf_wren_r;


/**********************************************************************************/
//读写突发结束标志
assign rd_burst_finish = (state == READ_END);
assign wr_burst_finish = (state == WRITE_END);
/**********************************************************************************/
//读取的ddr的数据要输出给rd_fifo,信号来自mig IP核的数据输出端口
assign rd_burst_data = app_rd_data;
assign rd_burst_data_valid = app_rd_data_valid;
/**********************************************************************************/

always @(posedge mem_clk or posedge rst)
begin
	if (rst)
		write_done <= 0;
	else if(wr_burst_finish)
		write_done <=1;
	else 
		write_done <= write_done;
end

重点是以下信号的生成:

1、app_en信号的生成逻辑:

  • 写数据时,当app_rdy和wr_burst_data_req有效(包含app_wdf_rdy有效)时拉高app_en信号,这是因为app_rdy和app_en同时有效时命令和地址可以被写入,所以干脆让app_en跟随app_rdy。
  • 但又加了一个wr_burst_data_req信号,这是因为由于数据来自fifo,fifo存在空满现象,也即有时读数据这个动作并不是连续的。既然此时不读新数据,那么app_en也拉低以避免命令和地址的写入。
assign app_en = app_en_r;

always @(*) begin
	if (state == MEM_WRITE) begin
		app_en_r <= app_rdy && wr_burst_data_req;
	end
	else if (state == MEM_WRITE_WAIT) begin
		app_en_r <= app_rdy;
	end
	else if (state == MEM_READ) begin
		app_en_r <= app_rdy && !rd_fifo_full;
	end
	else
		app_en_r <= 0;
end

2、写数据时的app_wdf_wren信号的生成逻辑:

  • 由于读数据请求信号wr_burst_data_req有效时,读出的数据会延迟一拍,所以刚好也让app_wdf_wren_r比wr_burst_data_req延迟一个周期,这样读出的数据wr_burst_data和app_wdf_wren_r同步。
  • 由于wr_burst_data_req有效包含app_wdf_rdy有效,也符合app_wdf_rdy和app_wdf_wren都有效时,数据wr_burst_data才能被写入的逻辑。
//当app_wdf_rdy和app_wdf_wren_r都有效时,IP核才会接收到用户端发送的app_wdf_data
assign app_wdf_wren = wr_burst_finish? 0 : app_wdf_wren_r;
assign app_wdf_end = app_wdf_wren;

always@(posedge mem_clk)
begin
	if (wr_data_cnt < wr_burst_len) begin
		//app_wdf_wren_r比wr_burst_data_req延迟一个周期
		//wr_burst_data_req连接到wr_fifo的rd_en,刚好读出的wr_burst_data和app_wdf_wren_r同步
		app_wdf_wren_r <= wr_burst_data_req;
	end
	/*
	else if (wr_data_cnt == wr_burst_len) begin
		app_wdf_wren_r <= 0;
	end*/
	else 
		app_wdf_wren_r <= 0;
end

3、wr_burst_data_req(也即app_wdf_data)的生成逻辑:处于写状态、mig IP核写空闲信号有效、wr_fifo中有数据,上述条件同时满足时就拉高wr_burst_data_req,开始请求读取wr_fifo中的数据wr_burst_data(app_wdf_data)

assign app_wdf_data = wr_burst_data;

//也可以再加一个条件:app_rdy拉高,但是由于该信号会忽高忽低,所以读出的数据也不连续,根据实际情况处理。
assign wr_burst_data_req = (state == MEM_WRITE) & app_wdf_rdy & !wr_fifo_empty;

4、状态机控制 app_cmd、app_addr的生成逻辑:这两个就一直有效即可,在一次写入或者读取完成之后,变化到下一个地址或者命令即可。

  • 读操作有效:app_rdy有效、app_en有效、并且rd_fifo不满即可。
  • 写操作有效:app_rdy有效、app_en有效、app_wdf_rdy有效、wr_fifo不空;最后两个信号有效其实也就是wr_burst_data_req有效,而wr_burst_data_req有效时,wr_burst_data和app_wdf_wren刚好也有效;这样刚好在命令和地址有效写入时,数据也有效可以被写入。
assign app_cmd = app_cmd_r;
assign app_addr = app_addr_r;

always@(posedge mem_clk or posedge rst)
begin
	if(rst)
	begin
		state <= IDLE;
		app_cmd_r <= 3'b000;
		app_addr_r <= 0;
		app_en_r <= 1'b0;
		rd_addr_cnt <= 0;
		rd_data_cnt <= 0;
		wr_addr_cnt <= 0;
		wr_data_cnt <= 0;
		app_wdf_wren_r <= 1'b0;
	end
	else if(init_calib_complete == 1'b1)//DDR3复位完成
	begin
		case(state)
			IDLE:  //复位状态、一次写完成、一次读完成均会回到IDLE状态
			begin
				if(rd_burst_req)//检测到rd_burst_req 信号后,进入读状态,
								//发送读命令、读起始地址、使能信号app_en 到 DDR_CONTROL
				begin
					state <= MEM_READ;   //进入读状态
					
					//下面三个信号一般都是一块给出
					app_cmd_r <= 3'b001; //发送读命令
					app_addr_r <= rd_burst_addr;//发送读初始地址
					app_en_r <= 1'b1;//发送命令使能

					rd_addr_cnt <= 0;
					rd_data_cnt <= 0;
				end	
				else if(wr_burst_req)    //检测到wr_burst_req 信号后,进入写状态,发送 写命令、写起始地址、 使能信号app_en 到DDR_CONTROL
				begin
					state <= MEM_WRITE;  //进入写状态

					//下面三个信号一般都是一块给出
					app_cmd_r <= 3'b000; //发送写命令
					app_addr_r <= wr_burst_addr;//发送写初始地址
					app_en_r <= 1'b1;//发送命令使能

					wr_addr_cnt <= 0;
					wr_data_cnt <= 0;
				end
			end
			MEM_READ:
			begin
				if(app_rdy && !rd_fifo_full)//等待DDR_CONTROL的app_ready 信号拉高
				begin
					app_addr_r <= app_addr_r + 8; //地址每次加8
					
					if(rd_addr_cnt == rd_burst_len - 1)  //检测读地址是否将要写完成
					begin
						//因为写地址完成肯定在数据读取完成之前,所以计数时前者先结束
						//但此时不能直接进入end状态,所以就定义了一个MEM_READ_WAIT状态
						state <= MEM_READ_WAIT;
						rd_addr_cnt <= 0;	         
						//app_en_r <= 1'b0; //地址写完成后拉低使能信号app_en
					end
					else
						rd_addr_cnt <= rd_addr_cnt + 1; //每写一次地址,加1,用于检测写入的读地址的个数
				end
				
				if(app_rd_data_valid)//读出数据有效信号
				begin
					if(rd_data_cnt == rd_burst_len - 1)//检测读出的数据的个数
					begin
						rd_data_cnt <= 0;
						state <= READ_END;//读完成后跳转至READ_END状态
					end
					else
					begin
						rd_data_cnt <= rd_data_cnt + 1;
					end
				end
			end
			MEM_READ_WAIT:
			begin
				if(app_rd_data_valid)
				begin
					if(rd_data_cnt == rd_burst_len - 1)
					begin
						rd_data_cnt <= 0;
						state <= READ_END;
					end
					else
					begin
						rd_data_cnt <= rd_data_cnt + 1;
					end
				end
			end
			
			MEM_WRITE:
			begin
			/*
			
				正常情况下,只要app_rdy和app_wdf_rdy都有效,然后写相关信号与数据都有效就可以完成写操作了。
				但是这里是要求wr_burst_data_req有效(其中包含app_wdf_rdy有效),也即多了个wr_fifo是否为空
				的信号,这是因为写数据来自wr_fifo,当没有数据可写的时候就保持cmd、addr不变,然后将app_en
				和app_wdf_wren拉低即可,表示此时不进行写操作了,这里没有直接拉低app_wdf_wren,但由于
				app_wdf_wren = wr_burst_data_req,所以,由于此时wr_burst_data_req也为低,因此app_wdf_wren也拉低了。

				
			*/
				if(app_rdy && wr_burst_data_req)
				begin
					//app_en_r <= 1'b1;
					app_addr_r <= app_addr_r + 8;   //地址每次加8
					if(wr_addr_cnt == wr_burst_len - 1)  //检测写地址是否将要写完成
					begin 
						wr_addr_cnt <= 0; 
						//app_en_r <= 1'b0;//地址写完成后拉低使能信号app_en
					end
					else
					begin
						wr_addr_cnt <= wr_addr_cnt + 1;  //每写一次地址,加1,用于检测写入的写地址的个数
					end
				end
				else
					app_en_r <= 1'b0;
						
				if(wr_burst_data_req)
				begin
					
					if(wr_data_cnt == wr_burst_len - 1) //检测写数据是否将要完成
					begin
						wr_data_cnt <= 0; 
						state <= MEM_WRITE_WAIT;//数据写完成后,跳转至MEM_WRITE_WAIT状态,等待所有地址发送完毕
					end
					else
					begin
						wr_data_cnt <= wr_data_cnt + 1; //每写一次数据,加1,用于检测写数据的个数
					end
				end
				
			end
			READ_END:
				begin
					state <= IDLE; //返回初始状态
					app_addr_r <=0;
				end
			
			MEM_WRITE_WAIT:
			begin
				if(app_rdy)
				begin
					//app_en_r <= 1'b1;
					app_addr_r <= app_addr_r + 8; //等待所有地址发送完毕
					if(wr_addr_cnt == wr_burst_len - 1)
					begin
						wr_addr_cnt <= 0; 
						//app_en_r <= 1'b0;
						
						state <= WRITE_END;    //写数据结束,拉高wr_burst_finish,代表本次突发写数据结束,将wr_burst_finish发送到mem_test模块
					end						   //在mem_test模块中,检测到wr_burst_finish后,将状态切换至read状态
					else
					begin
						wr_addr_cnt <= wr_addr_cnt + 1;
					end
				end
				
			end
			WRITE_END:
				begin
					state <= IDLE; //返回初始状态
					app_addr_r <=0;
				end
			default:
				state <= IDLE;
		endcase
	end
end
endmodule 

3、ddr_top模块:就是将三个子模块连接起来。


//`define SKIP_CALIB
`timescale 1ps/1ps

 /*module ddr_top #
  (
    
   //***************************************************************************
   // The following parameters refer to width of various ports
   //***************************************************************************
   parameter CK_WIDTH              = 1, //ck时钟信号的宽度
                                     // # of CK/CK# outputs to memory.
   parameter nCS_PER_RANK          = 1, //每个RANK的片选信号数量
                                     // # of unique CS outputs per rank for phy
   parameter CKE_WIDTH             = 1, //ck时钟有效信号的宽度
                                     // # of CKE outputs to memory.
   parameter DM_WIDTH              = 4, //数据掩码的宽度 一位控制一个字节
                                     // # of DM (data mask)
   parameter ODT_WIDTH             = 1, //片上终端使能信号的宽度
                                     // # of ODT outputs to memory.
   parameter BANK_WIDTH            = 3, //bank地址的宽度
                                     // # of memory Bank Address bits.
   parameter COL_WIDTH             = 10,//列地址宽度
                                     // # of memory Column Address bits.
   parameter CS_WIDTH              = 1, //片选信号的宽度
                                     // # of unique CS outputs to memory.
   parameter DQ_WIDTH              = 32, //数据宽度
                                     // # of DQ (data)
   parameter DQS_WIDTH             = 4, //数据选取脉冲的宽度 一位对应一个字节
   parameter DQS_CNT_WIDTH         = 2,
                                     // = ceil(log2(DQS_WIDTH))
   parameter DRAM_WIDTH            = 8,
                                     // # of DQ per DQS
   parameter ECC                   = "OFF",
   parameter ECC_TEST              = "OFF",
   
   parameter nBANK_MACHS           = 4,//管理bank的machine的数量,一个machine管理一个DRAM bank
                                       
   parameter RANKS                 = 1,//RANK的数量,每64bit为一个RANK
                                     // # of Ranks.
   parameter ROW_WIDTH             = 15, //行地址的宽度
                                     // # of memory Row Address bits.
   parameter ADDR_WIDTH            = 29,
                                     // # = RANK_WIDTH + BANK_WIDTH 1+3+15+10
                                     //     + ROW_WIDTH + COL_WIDTH;
                                     // Chip Select is always tied to low for
                                     // single rank devices

   //***************************************************************************
   // The following parameters are mode register settings
   //***************************************************************************
   parameter BURST_MODE            = "8",//突发模式
                                     // DDR3 SDRAM:
                                     // Burst Length (Mode Register 0).
                                     // # = "8", "4", "OTF".
                                     // DDR2 SDRAM:
                                     // Burst Length (Mode Register).
                                     // # = "8", "4".

   
   //***************************************************************************
   // The following parameters are multiplier and divisor factors for PLLE2.
   // Based on the selected design frequency these parameters vary.
   //***************************************************************************
   parameter CLKIN_PERIOD          = 5000,
                                     // Input Clock Period
   parameter CLKFBOUT_MULT         = 8,
                                     // write PLL VCO multiplier
   parameter DIVCLK_DIVIDE         = 1,
                                     // write PLL VCO divisor
   parameter CLKOUT0_PHASE         = 337.5,
                                     // Phase for PLL output clock (CLKOUT0)
   parameter CLKOUT0_DIVIDE        = 2,
                                     // VCO output divisor for PLL output clock (CLKOUT0)
   parameter CLKOUT1_DIVIDE        = 2,
                                     // VCO output divisor for PLL output clock (CLKOUT1)
   parameter CLKOUT2_DIVIDE        = 32,
                                     // VCO output divisor for PLL output clock (CLKOUT2)
   parameter CLKOUT3_DIVIDE        = 8,
                                     // VCO output divisor for PLL output clock (CLKOUT3)
   parameter MMCM_VCO              = 800,
                                     // Max Freq (MHz) of MMCM VCO
   parameter MMCM_MULT_F           = 4,
                                     // write MMCM VCO multiplier
   parameter MMCM_DIVCLK_DIVIDE    = 1,
                                     // write MMCM VCO divisor

   //***************************************************************************
   // Simulation parameters
   //***************************************************************************
   parameter SIMULATION            = "FALSE",
                                     // Should be TRUE during design simulations and
                                     // FALSE during implementations

   //***************************************************************************
   // IODELAY and PHY related parameters
   //***************************************************************************
   parameter TCQ                   = 100,
   
   parameter DRAM_TYPE             = "DDR3",

   
   //***************************************************************************
   // System clock frequency parameters
   //***************************************************************************
   parameter nCK_PER_CLK           = 4,//ddr3工作频率:用户端ui_clk
                                     // # of memory CKs per fabric CLK

   

   //***************************************************************************
   // Debug parameters
   //***************************************************************************
   parameter DEBUG_PORT            = "OFF",
                                     // # = "ON" Enable debug signals/controls.
                                     //   = "OFF" Disable debug signals/controls.
      
   parameter RST_ACT_LOW           = 1
                                     // =1 for active low reset,
                                     // =0 for active high.
	
  )*/

  module ddr_top(

   /*
      wr_fifo 是写ddr3前缓存数据的fifo,也即ddr3需要写入的数据从wr_fifo中读出

      rd_fifo 是读ddr3后缓存数据的fifo,也即从ddr3读出的数据写入rd_fifo
   */
	
   input wr_fifo_empty,//wr_fifo的空信号
   input [255:0]wr_fifo_dout,//wr_fifo的读取数据
   output wire wr_fifo_read,//读wr_fifo的使能信号
   
   input rd_fifo_full,//rd_fifo的满信号
   output wire rd_burst_data_valid,//写入rd_fifo的数据的有效信号
   output wire [255:0]rd_burst_data,//写入rd_fifo的数据

   // Inouts
   inout [31:0]           ddr3_dq,//inout [31:0] 数据
   inout [3:0]            ddr3_dqs_n,//inout [3:0] 数据选取脉冲
   inout [3:0]            ddr3_dqs_p,//inout [3:0] 数据选取脉冲
   // Outputs
   output [14:0]          ddr3_addr,//output [14:0] 行列地址
   output [2:0]           ddr3_ba,//output [2:0] bank地址
   output                 ddr3_ras_n,//output 行地址选通,低电平有效
   output                 ddr3_cas_n,//output 列地址选通,低电平有效
   output                 ddr3_we_n,//output 0-写允许,1-读允许
   output                 ddr3_reset_n,//output 复位信号,低电平有效
   output [0:0]           ddr3_ck_p,//output [0:0] 差分时钟p端
   output [0:0]           ddr3_ck_n,//output [0:0] 差分时钟n端
   output [0:0]           ddr3_cke,//output [0:0] 时钟有效信号,高电平有效
   output [0:0]           ddr3_cs_n,//output [0:0] 片选信号,低表示命令有效,否则命令屏蔽
   output [3:0]           ddr3_dm,//output [3:0] 数据掩码 一位控制一个字节
                                  //数据是32位,所以刚好是四位
   output [0:0]           ddr3_odt,//output [0:0] 片上终端使能,高电平有效
   // Inputs
   // Differential system clocks
   input                  sys_clk_p,//系统的差分时钟
   input                  sys_clk_n,
   output                 init_calib_complete,//output ddr3 初始化完成信号,高电平有效
   // System reset - Default polarity of sys_rst_n pin is Active Low.
   // System reset polarity will change based on the option 
   // selected in GUI.
   input                  sys_rst_n,//复位信号
   output                 clk,//mig ip核生成的ui_clk
   output                 read_ddr,
   output                 write_done
   );


  localparam ADDR_WIDTH            = 29;
  localparam DQ_WIDTH              = 32;
 
  localparam PAYLOAD_WIDTH         = DQ_WIDTH;

  localparam APP_DATA_WIDTH        = 2 * 4 * PAYLOAD_WIDTH;//256bits
  localparam APP_MASK_WIDTH        = APP_DATA_WIDTH / 8;//32

  // Wire declarations

  wire [ADDR_WIDTH-1:0]                 app_addr;//input [28:0]将要访问的DDR内存地址,具体位宽与用户生成IP核时的设置有关
  wire [2:0]                            app_cmd;//input [2:0]命令总线,3’b000表示写命令,3’b001表示读命令
  wire                                  app_en;//input命令使能信号,该信号有效且app_rdy有效时,命令才能被使用,高电平有效
  wire                                  app_rdy;
  wire [APP_DATA_WIDTH-1:0]             app_rd_data;
  wire                                  app_rd_data_end;
  wire                                  app_rd_data_valid;
  wire [APP_DATA_WIDTH-1:0]             app_wdf_data;//input [255:0]用户写入IP核的256bit数据
  wire                                  app_wdf_end;//input 该信号有效时,表示当前是一次DDR写突发的最后一个数据,高电平有效
  wire [APP_MASK_WIDTH-1:0]             app_wdf_mask;
  wire                                  app_wdf_rdy;
  wire                                  app_sr_active;
  wire                                  app_ref_ack;
  wire                                  app_zq_ack;
  wire                                  app_wdf_wren;

  wire                                  rst;
  wire [11:0]                           device_temp;
  

//***************************************************************************
wire wr_burst_finish;
wire rd_burst_finish;
wire rd_burst_req;
wire wr_burst_req;
wire[15:0] rd_burst_len;
wire[15:0] wr_burst_len;
wire[28:0] rd_burst_addr;
wire[28:0] wr_burst_addr;

wire[255 : 0] wr_burst_data;

mem_burst
#(
	.MEM_DATA_BITS(APP_DATA_WIDTH),
	.ADDR_BITS(ADDR_WIDTH)
)
mem_burst_m0
(   
    .rd_fifo_full(rd_fifo_full),
	  .wr_fifo_empty(wr_fifo_empty),
    .rst(rst),                                 
    .mem_clk(clk), //用户时钟                              
    .rd_burst_req(rd_burst_req),               
    .wr_burst_req(wr_burst_req),               
    .rd_burst_len(rd_burst_len),               
    .wr_burst_len(wr_burst_len),               
    .rd_burst_addr(rd_burst_addr),             
    .wr_burst_addr(wr_burst_addr),             
    .rd_burst_data_valid(rd_burst_data_valid), 
    .wr_burst_data_req(wr_fifo_read),          
    .rd_burst_data(rd_burst_data),             
    .wr_burst_data(wr_fifo_dout),              
    .rd_burst_finish(rd_burst_finish),         
    .wr_burst_finish(wr_burst_finish),                                 
    

    .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_mask(app_wdf_mask),
    .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_wdf_rdy(app_wdf_rdy),
    .init_calib_complete(init_calib_complete),
	.write_done(write_done)
);

mem_test
#(
	.MEM_DATA_BITS(APP_DATA_WIDTH),
	.ADDR_BITS(ADDR_WIDTH)
)
 mem_test_m0
(
	.rd_fifo_full(rd_fifo_full),
	.rst(rst),                                
	.mem_clk(clk), //用户时钟  
	.read_ddr(read_ddr),	
	.rd_burst_req(rd_burst_req),                       
	.wr_burst_req(wr_burst_req),                         
	.rd_burst_len(rd_burst_len),                   
	.wr_burst_len(wr_burst_len),                     
	.rd_burst_addr(rd_burst_addr),        
	.wr_burst_addr(wr_burst_addr),                    
	.wr_burst_data_req(wr_fifo_read),                     
	//.wr_burst_data(wr_burst_data),    
	.rd_burst_finish(rd_burst_finish),                    
	.wr_burst_finish(wr_burst_finish)
);

      
// 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.
//***************************************************************************

always @(posedge clk or negedge sys_rst_n) begin
    if (!sys_rst_n) begin
        app_wdf_wren_r1<=0;
        app_wdf_wren_r2<=0;
        app_wdf_wren_r3<=0;
        app_wdf_data_r1<=0;
        app_wdf_data_r2<=0;
        app_wdf_data_r3<=0;
    end
    else 
        app_wdf_wren_r1<=app_wdf_end;
        app_wdf_wren_r2<=app_wdf_wren_r1;
        app_wdf_wren_r3<=app_wdf_wren_r2;

        app_wdf_data_r1<=app_wdf_data;
        app_wdf_data_r2<=app_wdf_data_r1;
        app_wdf_data_r3<=app_wdf_data_r2;
end

DDR3_CONTROL u_DDR3_CONTROL
      (
        // Memory interface ports ddr端接口

       .ddr3_addr                      (ddr3_addr),//output [14:0] 行列地址
       .ddr3_ba                        (ddr3_ba),//output [2:0] bank地址
       .ddr3_cas_n                     (ddr3_cas_n),//output 列地址选通,低电平有效
       .ddr3_ck_n                      (ddr3_ck_n),//output [0:0] 差分时钟n端
       .ddr3_ck_p                      (ddr3_ck_p),//output [0:0] 差分时钟p端
       .ddr3_cke                       (ddr3_cke),//output [0:0] 时钟有效信号,高电平有效
       .ddr3_ras_n                     (ddr3_ras_n),//output 行地址选通,低电平有效
       .ddr3_we_n                      (ddr3_we_n),//output 0-写允许,1-读允许
       .ddr3_dq                        (ddr3_dq),//inout [31:0] 数据
       .ddr3_dqs_n                     (ddr3_dqs_n),//inout [3:0] 数据选取脉冲
       .ddr3_dqs_p                     (ddr3_dqs_p),//inout [3:0] 数据选取脉冲
       .ddr3_reset_n                   (ddr3_reset_n),//output 复位信号,低电平有效
       .init_calib_complete            (init_calib_complete),//output ddr3 初始化完成信号,高电平有效
       .ddr3_cs_n                      (ddr3_cs_n),//output [0:0] 片选信号,低表示命令有效,否则命令屏蔽
       .ddr3_dm                        (ddr3_dm),//output [3:0] 数据掩码 一位控制一个字节
                                                 //数据是32位,所以刚好是四位
       .ddr3_odt                       (ddr3_odt),//output [0:0] 片上终端使能,高电平有效
        // Application interface ports 用户端接口
       .app_addr                       (app_addr),//input [28:0]将要访问的DDR内存地址,具体位宽与用户生成IP核时的设置有关
       .app_cmd                        (app_cmd),//input [2:0]命令总线,3’b000表示写命令,3’b001表示读命令
       .app_en                         (app_en),//input命令使能信号,该信号有效且app_rdy有效时,命令才能被使用,高电平有效
       .app_wdf_data                   (app_wdf_data),//input [255:0]用户写入IP核的256bit数据
       .app_wdf_end                    (app_wdf_end),//input该信号有效时,表示当前是一次DDR写突发的最后一个数据,高电平有效
       .app_wdf_wren                   (app_wdf_wren),//input写数据有效信号,当app_wdf_rdy也为有效时,
                                                      //IP核才会接收到用户端发送的app_wdf_data,高电平有效
       .app_rd_data                    (app_rd_data),//output [255:0]从DDR中读出得数据,一次突发读出8个32bit数据
       .app_rd_data_end                (app_rd_data_end),//output指示当前数据是突发读写的最后一个周期的数据,
                                                         //这个信号与设置的用户时钟和DDR时钟的比例有关
       .app_rd_data_valid              (app_rd_data_valid),//output读出数据有效信号,高电平有效,表示从IP核中读出的数据有效
       .app_rdy                        (app_rdy),//output空闲信号,指示当前IP核的工作状态,只有该信号为高时,
                                                 //IP核才能正确的使用用户给出的命令,高电平有效
       .app_wdf_rdy                    (app_wdf_rdy),//output写空闲信号,IP核内部的写FIFO能够接收用户数据的标志,高电平有效
       
       .app_sr_req                     (1'b0),//input一系列的请求信号,一般为0 代表用户对MIG IP没有强力干预
       .app_ref_req                    (1'b0),//input
       .app_zq_req                     (1'b0),//input
       .app_sr_active                  (app_sr_active),//output上面请求的响应信号
       .app_ref_ack                    (app_ref_ack),//output
       .app_zq_ack                     (app_zq_ack),//output
       .ui_clk                         (clk),//output IP核提供给用户端使用的clk,和ddr3_ck_n/p的比例是1:2或者1:4
       .ui_clk_sync_rst                (rst),//output 是ui_clk的复位指示信号,ui_clk复位完成后拉低
      
       .app_wdf_mask                   (0),//input [31:0] 32bit数据掩码,每一位对应app_wdf_data的一个8bit数据
      
       
// System Clock Ports
       .sys_clk_p                       (sys_clk_p),
       .sys_clk_n                       (sys_clk_n),
       .device_temp                     (device_temp),
       `ifdef SKIP_CALIB
       .calib_tap_req                    (calib_tap_req),
       .calib_tap_load                   (calib_tap_load),
       .calib_tap_addr                   (calib_tap_addr),
       .calib_tap_val                    (calib_tap_val),
       .calib_tap_load_done              (calib_tap_load_done),
       `endif
      
       .sys_rst                          (sys_rst_n)
       );
// End of User Design top instance
  
endmodule

四、仿真结果

写操作时序:
在这里插入图片描述
读操作时序:
在这里插入图片描述
可以看到,写进去的数据和读出来的数据是一致的!!!

posted @ 2021-10-27 12:11  耐心的小黑  阅读(2329)  评论(1编辑  收藏  举报