FPGA实现ddr3读写控制

yummy 阅读:132 2024-05-24 11:54:02 评论:0

1MIG DDR3 IP核的介绍

图片1.png 

下面是用户侧的信号接口及说明:

图片2.png 

图片3.png 

图片4.png

其中的输入/输出是相对于MIG IP核来说的,例如ui_clk就是output给用户侧的用户时钟。

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

图片5.png 

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

图片6.png 

写数据的情况有3种:

 

写命令与写数据发生在同一时钟周期

写数据先于写命令发生(不一定是图上的一个时钟周期,因为数据是先写到了FIFO)发生在同一时钟周期

写数据落后于写命令发生,但不能超过两个时钟周期

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

 

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

图片7.png 

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

2MIG DDR3 IP核的配置

打开 IP Catalog 在搜索栏输入 mig(Memory Interface Generator),双击直接打开

3、读写测试模块

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

 

写入一定量的数据(可设置,默认512个)到DDR3,写入地址从0开始

从地址0开始读取之前写入DDR3的数据,同时判断读、写数据是否一致

循环上两个步骤,即写、读、写、读····

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

 

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

 

图片8.png 

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

图片9.png 

写数据的情况有3种:

 

写命令与写数据发生在同一时钟周期

写数据先于写命令发生(不一定是图上的一个时钟周期,因为数据是先写到了FIFO)发生在同一时钟周期

写数据落后于写命令发生,但不能超过两个时钟周期

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

 

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

图片10.png 

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

3.1PLL模块

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

 

3.2DDR3仿真模型

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

3.3、读写测试模块

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

图片11.png 

IDLE:初始状态,等MIG IP核初始化完成后跳转到写数据状态WRITE

WRITE:写数据状态,在这个状态向MIG IP核写入一定量的数据(测试为512个)。当写入最后一个数据时,同步跳转到等待状态WAIT

WAIT:过渡状态,仅维持一个周期

READ:读数据状态,在这个状态从MIG IP核读取一定量的数据(测试为512个)。当读取最后一个数据时,同步跳转到初始状态IDLE。开始新一轮的写、读过程

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

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

 

//**************************************************************************

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仿真模型与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

 

 

4、Testbench及仿真结果

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

4.1、Testbench

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

//**************************************************************************

// *** 描述 : 对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

 


本文 zblog模板 原创,转载保留链接!网址:http://www.xn--zqqs03dbu6a.cn/?id=61

可以去百度分享获取分享代码输入这里。
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。