FPGA实现ddr3读写控制
1、MIG DDR3 IP核的介绍
下面是用户侧的信号接口及说明:
其中的输入/输出是相对于MIG IP核来说的,例如ui_clk就是output给用户侧的用户时钟。
DDR3 的读或者写都包含写命令操作,其中写操作命令(app_cmd)的值等于 0,读操作 app_cmd 的值 等于 1。首先来看写命令时序,如下图所示。首先检查 app_rdy,为高则表明此时 IP 核命令接收处于准备好 状态,可以接收用户命令,在当前时钟拉高 app_en,同时发送命令(app_cmd)和地址(app_addr),此时命令和地址被写入。
写数据的时序,如下图所示:
写数据的情况有3种:
写命令与写数据发生在同一时钟周期
写数据先于写命令发生(不一定是图上的一个时钟周期,因为数据是先写到了FIFO)发生在同一时钟周期
写数据落后于写命令发生,但不能超过两个时钟周期
结合上图,写时序总结如下:首先需要检查 app_wdf_rdy,该信号为高表明此时 IP 核数据接收处于准备完成状态,可以接收用户发过来的数据,在当前时钟拉高写使能(app_wdf_wren),给出写数据 (app_wdf_data)。这样加上发起的写命令操作就可以成功向 IP 核写数据。
接着来看读数据,如下图所示:
读时序比较简单,发出读命令后,用户只需等待数据有效信号(app_rd_data_valid)拉高,为高表明此 时数据总线上的数据是有效的返回数据。需要注意的是,在发出读命令后,有效读数据要晚若干周期才出现在数据总线上 (延迟的时间不定)。
2、MIG 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),此时命令和地址被写入。
写数据的时序,如下图所示:
写数据的情况有3种:
写命令与写数据发生在同一时钟周期
写数据先于写命令发生(不一定是图上的一个时钟周期,因为数据是先写到了FIFO)发生在同一时钟周期
写数据落后于写命令发生,但不能超过两个时钟周期
结合上图,写时序总结如下:首先需要检查 app_wdf_rdy,该信号为高表明此时 IP 核数据接收处于准备完成状态,可以接收用户发过来的数据,在当前时钟拉高写使能(app_wdf_wren),给出写数据 (app_wdf_data)。这样加上发起的写命令操作就可以成功向 IP 核写数据。
接着来看读数据,如下图所示:
读时序比较简单,发出读命令后,用户只需等待数据有效信号(app_rd_data_valid)拉高,为高表明此 时数据总线上的数据是有效的返回数据。需要注意的是,在发出读命令后,有效读数据要晚若干周期才出现在数据总线上 (延迟的时间不定)。
3.1、PLL模块
本来仿真是不需要PLL模块的(直接写时钟激励就行),但是考虑到等下还需要上板测试,而我手头的开发板主频50M,MIG的工作时钟却是200M,所以需要PLL模块生成工作时钟。具体配置略。
3.2、DDR3仿真模型
在生成的example design中提供了DDR3的仿真模型,共两个文件:
3.3、读写测试模块
读写测试模块生成对MIG IP核的控制时序,并使用一个状态机来实现循环写、读的过程。状态机如下:
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://xn--zqqs03dbu6a.cn/?id=61
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。