赞
踩
在前文《详解SDRAM基本原理以及FPGA实现读写控制》中我们学会了SDRAM的基本原理以及读写操作时序,本文讲解的DDR3全称为“Double Data Rate 3”(双倍数据速率第三代),它是一种用于计算机和其他设备的随机存取存储器(RAM)技术。
SDRAM、DDR、DDR2和DDR3都是不同类型的动态随机存取存储器(DRAM)技术,它们在数据传输速率、功耗、电压和性能方面有所不同。以下是这几种内存的区别:
SDRAM(Synchronous DRAM):
DDR SDRAM(Double Data Rate Synchronous DRAM):
DDR2 SDRAM:
DDR3 SDRAM:
主要区别:
从DDR1开始,后续迭代的DDR速度都越来越快,其速度提高的原因就是Prefetch(预读取)。什么是预读取? Prefetch是指内存在处理数据请求时,不仅读取请求的数据,还会预先读取额外的数据到缓存中。这种机制可以提高内存的效率和性能。
我们知道DDR在时钟周期的上沿和下沿都能传输数据,所以传输率比SDRAM快了一倍,这就说DDR上升沿传输一位数据,下降沿传输一位数据,在一个时钟周期内一共传输2bit数据,这儿是2bit是指2倍芯片位宽的数据。
DDR2一次性可以从存储单元预读取4bit的数据,然后在时钟上升沿和下降沿发送出去,因此DDR2需要2个时钟周期才能完成传输。
DDR3一次性可以从存储单元预读取8bit的数据,然后在时钟上升沿和下降沿发送出去,因此DDR3需要4个时钟周期才能完成传输。
核心频率是指DDR物理内部的运行时钟频率,因为是双沿传输,所以DDR的工作频率是2倍核心频率。再由上面可知,DDR2预读取4bit需要2个时钟,因此DDR2的等效频率也就是实际的数据传输速率就等于工作频率*2。常见的DDR频率表如下:
例如DDR3-1333是指核心频率为166MHZ,工作频率=1662=333MHZ,因为DDR3预读取8bit数据需要4个周期,所以等效频率=3334约等于1333MHZ。
在用户控制方面来看,如果我想让DDR3跑等效800M数据速率,这是双沿的速率,所以实际上DDR3 IO速率为400M,因为DDR3预读取8bit需要4个时钟周期,所以用户操作逻辑只需要400/4=100M的时钟来读写数据。
DDR带宽=等效频率 * 位宽。 例如:位宽64位的DDR3-800的带宽=800M * 64bit=51.2Gbit/s=6.4GB/s。对于用户操作读写的数据位宽=800M * 64bit/100M=512bit。因此用户只需要用100M的时钟操作512位的数据,即可让DDR3跑800M 64位的速率。
以MT41K256M16TW-107为例计算DDR3容量,打开芯片手册如下:
BANK的数据位宽位3位,因此BANK数量=2 ^ 3=8;行地址位宽为15位,因此一个BANK一行的存储单元数量 = 2 ^ 15=32768=32K;列地址位宽为10,因此一个BANK一列的存储单元数量=2 ^ 10 = 1024 =1K;数据位宽为16位,所以这片DDR3总的容量= 8 ✖ 32768 ✖ 1024 ✖ 16 = 4294967296 bit = 4096Mb = 512MB
前面我们介绍了DDR3的一些知识,对DDR3有了一个大致了解,那么我们怎么去使用DDR3呢?与SDRAM不同的是DDR3 的时序非常复杂,用户直接编写DDR3的控制代码是相当耗时以及性能得不到保证,那么绝大部分开发者都会使用芯片厂家提供的控制器来操作DDR3。
MIG(Memory Interface Generators) IP 核是Xilinx提供给7系以及以上的用户来实现 DDR3 的读写控制器。MIG有AXI接口版本以及app接口的版本,本次我们讲解app接口的MIG,AXI接口的后续在讲。app接口的MIG系统框图如下所示:
对于用户来说,只有正确的控制app这些信号才能操作MIG,还要负责分配正确的DDR3管脚,其他不用关心。。MIG内部负责产生具体的DDR3操作时序,并直接操作DDR3读写。
从上面系统框图来看,MIG提供给用户的接口有很多,但是对于用户来说,不是所有信号都必须使用,我们先来看每个信号的意思,信号方向是相对于MIG来说的。具体如下表所示:
信号名称 | 信号方向 | 信号说明 |
ui_clk | 输出 | MIG输出的用户时钟,必须是DDR3 IO时钟的1/2或者1/4 |
ui_clk_sync_rst | 输出 | 用户时钟复位信号,高电平有效 |
init_calib_complete | 输出 | DDR3初始化完成信号,高电平有效, |
app_addr[ADDR_WIDTH – 1:0] | 输入 | 用户地址输入,地址的位宽ADDR_WIDTH等于RANK位宽+BANK位宽+ROW位宽+COL位宽 |
app_en | 输入 | MIG IP核命令写入使能,高电平有效。写命令时需要拉高该信号 |
app_cmd[2:0] | 输入 | 控制命令信号;读:001;写:000。其他值保留。 |
app_rdy | 输出 | MIG IP核准备接收读写命令,高电平有效。 |
app_wdf_wren | 输入 | MIG IP核数据写使能,高电平有效 |
app_wdf_mask[APP_MASK_WIDTH – 1:0] | 输入 | 数据掩码信号,指示当前写数据那些位有效 |
app_wdf_rdy | 输出 | MIG IP核准备接收数据信号,高电平有效。 |
app_wdf_end | 输入 | 指示当前写入下是最后一个数据,高电平有效。 |
app_wdf_data [APP_DATA_WIDTH-1:0] | 输入 | 需要写入的数据 |
app_rd_data[APP_DATA_WIDTH – 1:0] | 输出 | MIG从DDR3读出来的数据 |
app_rd_data_valid | 输出 | 读数据有效信号,高电平有效 |
app_rd_data_end | 输出 | 指示当前读出的数据是最后一个数据,高电平有效 |
app_sz | 输入 | 保留信号,置0 |
app_sr_req | 输入 | 保留信号,置0 |
app_sr_active | 输出 | 保留信号 |
app_ref_req | 输入 | 刷新请求信号 |
app_ref_ack | 输出 | 刷新请求响应信号 |
app_zq_req | 输出 | 请求校准阻抗匹配信号 |
当用户逻辑app_en信号被断言且app_rdy信号从MIG被断言时,MIG接受命令。每当app_rdy被取消断言时,MIG将忽略该命令。用户逻辑需要将app_en与有效命令和地址值沿着保持为高电平,直到app_rdy被断言,如下图所示:
只有当app_rdy 与 app_en同时为高时,命令和地址才会被写入到MIG,如图红色的位置,这类似于AXI里的握手信号。
首先需要检查app_wdf_rdy,该信号为高表明此时IP核数据接收处于准备状态,可以接收用户发过来的数据,在当前时钟拉高写使能(app_wdf_wren),同时给出写数据这样加上发起的写命令操作就可以成功向IP核写数据,具体时序如下图所示:
如上图所示,写数据有三种情形均可以正确写入:
用户发出读命令后,用户只需等待数据有效信号(app_rd_data_valid)拉高,为高表明此时数据总线上的数据是有效的返回数据,有效读数据要晚若干周期才出现在数据总线上。如下图所示:
打开IP库搜索MIG,然后打开,第一个界面是MIG的一些简要说明,直接点NEXT就行。
第二个界面主要就是自定义MIG名称,以及MIG核数量和是否用AXI接口的MIG,本次实验先用默认的app接口,所以不选AXI4。
第三个界面选择兼容的FPGA器件,这里不用就不选择
第四个界面选择控制什么类型的DDR,这里选择DDR3
第五个界面选择DDR3具体的物理参数
第六个界面选择MIG的设置
选择MIG参考时钟以及复位
System Clock:选择上一个界面选择的时钟来源,如果是外部单端晶振进来,就选择Single-Ended;如果是外部差分晶振进来,就选择Differential;如果是内部锁相环产生,就选择No Buffer,这里我们是内部PLL产生,所以选择No Buffer。
Reference Clock:MIG IP 核参考时钟。IP 核参考时钟要求是200Mhz,而MIG IP 核的系统时钟刚好也使用了200Mhz的系统时钟,所以可以选择Use System Clock这个选项
System Reset Polarity:复位信号电平选择,这里选择低有效
下一个界面是配置阻抗匹配,默认50Ohms即可
下一个界面是选择DDR管脚,如果只是单纯仿真,选上面就好,如果是下板验证,就要选择下面这个根据实际开发板管脚选择
下一个界面是选择一些信号引脚,这里全都选择No connect即可,后续没有配置了,一路点next就完成了MIG核的配置
本次测试有两个模块,一个MIG核控制模块,一个读写数据控制模块,具体系统框图如下所示:
读写数据控制模块,主要产生想要写入ddr的数据以及读写地址还有读写的长度给MIG控制模块,MIG控制模块收到这些数后产生MIG核的读写时序来读写DDR。
module mig_ctrl(
input ui_clk ,
input ui_rst ,
//DDR3 app interface
input init_calib_complete ,
input app_rdy ,
input app_wdf_rdy ,
output [27:0] app_addr ,
output [2:0] app_cmd ,
output app_en ,
output [255:0] app_wdf_data ,
output app_wdf_end ,
output app_wdf_wren ,
input [255:0] app_rd_data ,
input app_rd_data_end ,
input app_rd_data_valid ,
// user logic
input [1:0] rw_cmd , //用户给出的读写命令
input [9:0] rw_len , //用户给出的读写长度
input [27:0] rw_addr , //用户给出的读写地址
input rw_valid , //以上信息有效信号
output rw_ready , //读写准备信号
input [255:0] wr_data , //用户给出的写数据
input wr_data_valid , //写数据有效信号
input wr_data_last , //最后一个写数据
output [255:0] rd_data , //从ddr读出的数据
output rd_data_valid //从ddr读数据有效信号
);
/***************function**************/
/***************parameter*************/
localparam INIT = 5'b00001;
localparam IDLE = 5'b00010;
localparam WAITE = 5'b00100;
localparam WRITE = 5'b01000;
localparam READ = 5'b10000;
/***************port******************/
/***************mechine***************/
reg [4:0] cur_state ;
reg [4:0] next_state ;
/***************reg*******************/
reg [27:0] r_app_addr ;
reg [2:0] r_app_cmd ;
reg r_app_en ;
reg r_app_wdf_wren ;
reg r_rw_ready ;
reg r_rd_data_valid ;
reg [255:0] r_rd_data ;
reg [1:0] r_rw_cmd ;
reg [9:0] r_rw_len ;
reg [27:0] r_rw_addr ;
reg r_rw_active ;
reg [9:0] write_cnt ; //写数据计数器
reg [9:0] read_cnt ; //读数据计数器
reg [3:0] waite_cnt ; //等待计数器
/***************wire******************/
wire rw_active ; //握手成功信号
wire wr_fifo_empty ;
wire wr_fifo_full ;
wire wr_mig_valid ; //写数据成功信号
wire rd_mig_valid ; //读数据成功信号
wire [255:0] fifo_rdata ; //fifo读出的数据
/***************component*************/
//例化一个FIFO来缓存写数据
wddr_data_fifo u_wddr_data_fifo (
.clk (ui_clk ),
.din (wr_data ),
.wr_en (wr_data_valid ),
.rd_en (wr_mig_valid ),
.dout (fifo_rdata ),
.full (wr_fifo_full ),
.empty (wr_fifo_empty )
);
/***************assign****************/
assign app_addr = r_app_addr ;
assign app_cmd = r_app_cmd ;
assign app_en = r_app_en & app_rdy; //当app_rdy信号拉高时候,给出app_en信号
assign app_wdf_data = fifo_rdata ; //fifo读出的数据直接给写ddr数据
assign app_wdf_end = app_wdf_wren ; //app_wdf_end和app_wdf_wren一样的
assign app_wdf_wren = r_app_wdf_wren & app_rdy & app_wdf_rdy ; //app_rdy 和 app_wdf_rdy都拉高时,可以写数据
assign rw_ready = r_rw_ready ;
assign rd_data_valid = r_rd_data_valid;
assign rd_data = r_rd_data ;
assign rw_active = rw_valid & rw_ready; //读写握手信号
assign wr_mig_valid = app_en & app_wdf_rdy & app_wdf_wren & (cur_state == WRITE);//一次写入有效
assign rd_mig_valid = app_en &(cur_state == READ); //一次读有效
/***************always****************/
always @(posedge ui_clk or posedge ui_rst) begin
if (ui_rst == 1'b1) begin
r_rw_cmd <= 'd0;
r_rw_len <= 'd0;
r_rw_addr <= 'd0;
end
else if(rw_active == 1'b1)begin //读写握手成功, 把地址,命令,长度暂存下来
r_rw_cmd <= rw_cmd ;
r_rw_len <= rw_len ;
r_rw_addr <= rw_addr;
end
else begin
r_rw_cmd <= r_rw_cmd ;
r_rw_len <= r_rw_len ;
r_rw_addr <= r_rw_addr;
end
end
always @(posedge ui_clk or posedge ui_rst) begin //握手信号打一拍,来同步上面暂存的信号
if (ui_rst == 1'b1) begin
r_rw_active <= 1'b0;
end
else begin
r_rw_active <= rw_active;
end
end
always @(posedge ui_clk or posedge ui_rst) begin
if (ui_rst == 1'b1) begin
cur_state <= INIT;
end
else begin
cur_state <= next_state;
end
end
always @(*) begin
case (cur_state)
INIT : next_state = init_calib_complete ? IDLE : INIT; //ddr初始化完成后跳转到空闲状态
IDLE : begin
if(rw_active == 1'b1) //在空闲状态,握手成功后,根据命令来跳转是读还是写
if(rw_cmd == 'd0)
next_state <= WAITE;
else
next_state <= READ;
else
next_state <= IDLE;
end
WAITE : begin
if(waite_cnt == 10) //等待10个周期再去写,让fifo里有一定的数
next_state <= WRITE;
else
next_state <= WAITE;
end
WRITE : begin
if(write_cnt == r_rw_len - 2)
next_state <= IDLE;
else
next_state <= WRITE;
end
READ : begin
if(read_cnt == r_rw_len - 2)
next_state <= IDLE;
else
next_state <= READ;
end
default : next_state <= IDLE;
endcase
end
always @(posedge ui_clk or posedge ui_rst) begin
if(ui_rst == 1'b1)
waite_cnt <= 'd0;
else if(cur_state == WAITE) //写等待计数器
waite_cnt <= waite_cnt + 1'b1;
else
waite_cnt <= 'd0;
end
always @(posedge ui_clk or posedge ui_rst) begin
if (ui_rst == 1'b1) begin
r_app_cmd <= 'd0;
r_app_en <= 'd0;
end
else if(cur_state == WRITE)begin //写状态下 命令为0
r_app_cmd <= 'd0;
r_app_en <= 'd1;
end
else if(cur_state == READ)begin //写状态下 命令为1
r_app_cmd <= 'd1;
r_app_en <= 'd1;
end
else begin
r_app_cmd <= 'd0;
r_app_en <= 'd0;
end
end
always @(posedge ui_clk or posedge ui_rst) begin
if (ui_rst == 1'b1) begin
r_app_addr <= 'd0;
end
else if(r_rw_active == 1'b1)begin //握手成功,拿出起始地址
r_app_addr <= r_rw_addr;
end
else if(wr_mig_valid == 1'b1)begin //每写一个数据后,地址+8
r_app_addr <= r_app_addr + 'd8;
end
else if(rd_mig_valid == 1'b1)begin //每读一个数据后,地址+8
r_app_addr <= r_app_addr + 'd8;
end
else
r_app_addr <= r_app_addr;
end
always @(posedge ui_clk or posedge ui_rst) begin
if (ui_rst == 1'b1) begin
r_app_wdf_wren <= 1'b0;
end
else if(cur_state == WRITE) begin
r_app_wdf_wren <= 1'b1;
end
else
r_app_wdf_wren <= 1'b0;
end
always @(posedge ui_clk or posedge ui_rst) begin
if (ui_rst == 1'b1) begin
r_rd_data <= 'd0;
r_rd_data_valid <= 'd0;
end
else begin
r_rd_data <= app_rd_data ;
r_rd_data_valid <= app_rd_data_valid;
end
end
always @(posedge ui_clk or posedge ui_rst) begin
if(ui_rst == 1'b1)
r_rw_ready <= 'd0;
else if(rw_active == 1'b1) //握手成功后拉低ready信号,等待下一次握手
r_rw_ready <= 'd0;
else if(cur_state == IDLE)
r_rw_ready <= 'd1;
else
r_rw_ready <= 'd0;
end
always @(posedge ui_clk or posedge ui_rst) begin
if(ui_rst == 1'b1)
write_cnt <= 'd0;
else if(cur_state == IDLE)
write_cnt <= 'd0;
else if(wr_mig_valid == 1'b1) //写数据个数计数器
write_cnt <= write_cnt + 1'b1;
else
write_cnt <= write_cnt;
end
always @(posedge ui_clk or posedge ui_rst) begin
if(ui_rst == 1'b1)
read_cnt <= 'd0;
else if(cur_state == IDLE)
read_cnt <= 'd0;
else if(rd_mig_valid == 1'b1) //读数据个数计数器
read_cnt <= read_cnt + 1'b1;
else
read_cnt <= read_cnt;
end
endmodule
module mig_ctrl(
input ui_clk ,
input ui_rst ,
//DDR3 app interface
input init_calib_complete ,
input app_rdy ,
input app_wdf_rdy ,
output [27:0] app_addr ,
output [2:0] app_cmd ,
output app_en ,
output [255:0] app_wdf_data ,
output app_wdf_end ,
output app_wdf_wren ,
input [255:0] app_rd_data ,
input app_rd_data_end ,
input app_rd_data_valid ,
// user logic
input [1:0] rw_cmd , //用户给出的读写命令
input [9:0] rw_len , //用户给出的读写长度
input [27:0] rw_addr , //用户给出的读写地址
input rw_valid , //以上信息有效信号
output rw_ready , //读写准备信号
input [255:0] wr_data , //用户给出的写数据
input wr_data_valid , //写数据有效信号
input wr_data_last , //最后一个写数据
output [255:0] rd_data , //从ddr读出的数据
output rd_data_valid //从ddr读数据有效信号
);
/***************function**************/
/***************parameter*************/
localparam INIT = 5'b00001;
localparam IDLE = 5'b00010;
localparam WAITE = 5'b00100;
localparam WRITE = 5'b01000;
localparam READ = 5'b10000;
/***************port******************/
/***************mechine***************/
reg [4:0] cur_state ;
reg [4:0] next_state ;
/***************reg*******************/
reg [27:0] r_app_addr ;
reg [2:0] r_app_cmd ;
reg r_app_en ;
reg r_app_wdf_wren ;
reg r_rw_ready ;
reg r_rd_data_valid ;
reg [255:0] r_rd_data ;
reg [1:0] r_rw_cmd ;
reg [9:0] r_rw_len ;
reg [27:0] r_rw_addr ;
reg r_rw_active ;
reg [9:0] write_cnt ; //写数据计数器
reg [9:0] read_cnt ; //读数据计数器
reg [3:0] waite_cnt ; //等待计数器
/***************wire******************/
wire rw_active ; //握手成功信号
wire wr_fifo_empty ;
wire wr_fifo_full ;
wire wr_mig_valid ; //写数据成功信号
wire rd_mig_valid ; //读数据成功信号
wire [255:0] fifo_rdata ; //fifo读出的数据
/***************component*************/
//例化一个FIFO来缓存写数据
wddr_data_fifo u_wddr_data_fifo (
.clk (ui_clk ),
.din (wr_data ),
.wr_en (wr_data_valid ),
.rd_en (app_wdf_wren ),
.dout (fifo_rdata ),
.full (wr_fifo_full ),
.empty (wr_fifo_empty )
);
/***************assign****************/
assign app_addr = r_app_addr ;
assign app_cmd = r_app_cmd ;
assign app_en = r_app_en & app_rdy; //当app_rdy信号拉高时候,给出app_en信号
assign app_wdf_data = fifo_rdata ; //fifo读出的数据直接给写ddr数据
assign app_wdf_end = app_wdf_wren ; //app_wdf_end和app_wdf_wren一样的
assign app_wdf_wren = r_app_wdf_wren & app_rdy & app_wdf_rdy ; //app_rdy 和 app_wdf_rdy都拉高时,可以写数据
assign rw_ready = r_rw_ready ;
assign rd_data_valid = r_rd_data_valid;
assign rd_data = r_rd_data ;
assign rw_active = rw_valid & rw_ready; //读写握手信号
assign wr_mig_valid = app_en & app_wdf_rdy & app_wdf_wren & (cur_state == WRITE);//一次写入有效
assign rd_mig_valid = app_en &(cur_state == READ); //一次读有效
/***************always****************/
always @(posedge ui_clk or posedge ui_rst) begin
if (ui_rst == 1'b1) begin
r_rw_cmd <= 'd0;
r_rw_len <= 'd0;
r_rw_addr <= 'd0;
end
else if(rw_active == 1'b1)begin //读写握手成功, 把地址,命令,长度暂存下来
r_rw_cmd <= rw_cmd ;
r_rw_len <= rw_len ;
r_rw_addr <= rw_addr;
end
else begin
r_rw_cmd <= r_rw_cmd ;
r_rw_len <= r_rw_len ;
r_rw_addr <= r_rw_addr;
end
end
always @(posedge ui_clk or posedge ui_rst) begin //握手信号打一拍,来同步上面暂存的信号
if (ui_rst == 1'b1) begin
r_rw_active <= 1'b0;
end
else begin
r_rw_active <= rw_active;
end
end
always @(posedge ui_clk or posedge ui_rst) begin
if (ui_rst == 1'b1) begin
cur_state <= INIT;
end
else begin
cur_state <= next_state;
end
end
always @(*) begin
case (cur_state)
INIT : next_state = init_calib_complete ? IDLE : INIT; //ddr初始化完成后跳转到空闲状态
IDLE : begin
if(r_rw_active == 1'b1) //在空闲状态,握手成功后,根据命令来跳转是读还是写
if(r_rw_cmd == 'd0)
next_state <= WAITE;
else
next_state <= READ;
else
next_state <= IDLE;
end
WAITE : begin
if(waite_cnt == 10) //等待10个周期再去写,让fifo里有一定的数
next_state <= WRITE;
else
next_state <= WAITE;
end
WRITE : begin
if(write_cnt == r_rw_len - 2)
next_state <= IDLE;
else
next_state <= WRITE;
end
READ : begin
if(read_cnt == r_rw_len - 2)
next_state <= IDLE;
else
next_state <= READ;
end
default : next_state <= IDLE;
endcase
end
always @(posedge ui_clk or posedge ui_rst) begin
if(ui_rst == 1'b1)
waite_cnt <= 'd0;
else if(cur_state == WAITE) //写等待计数器
waite_cnt <= waite_cnt + 1'b1;
else
waite_cnt <= 'd0;
end
always @(posedge ui_clk or posedge ui_rst) begin
if (ui_rst == 1'b1) begin
r_app_cmd <= 'd0;
r_app_en <= 'd0;
end
else if(cur_state == WRITE)begin //写状态下 命令为0
r_app_cmd <= 'd0;
r_app_en <= 'd1;
end
else if(cur_state == READ)begin //写状态下 命令为1
r_app_cmd <= 'd1;
r_app_en <= 'd1;
end
end
always @(posedge ui_clk or posedge ui_rst) begin
if (ui_rst == 1'b1) begin
r_app_addr <= 'd0;
end
else if(r_rw_active == 1'b1)begin //握手成功,拿出起始地址
r_app_addr <= r_rw_addr;
end
else if(wr_mig_valid == 1'b1)begin //每写一个数据后,地址+8
r_app_addr <= r_app_addr + 'd8;
end
else if(rd_mig_valid == 1'b1)begin //每读一个数据后,地址+8
r_app_addr <= r_app_addr + 'd8;
end
else
r_app_addr <= r_app_addr;
end
always @(posedge ui_clk or posedge ui_rst) begin
if (ui_rst == 1'b1) begin
r_app_wdf_wren <= 1'b0;
end
else if(cur_state == WRITE) begin
r_app_wdf_wren <= 1'b1;
end
else
r_app_wdf_wren <= 1'b0;
end
always @(posedge ui_clk or posedge ui_rst) begin
if (ui_rst == 1'b1) begin
r_rd_data <= 'd0;
r_rd_data_valid <= 'd0;
end
else begin
r_rd_data <= app_rd_data ;
r_rd_data_valid <= app_rd_data_valid;
end
end
always @(posedge ui_clk or posedge ui_rst) begin
if(ui_rst == 1'b1)
r_rw_ready <= 'd0;
else if(rw_active == 1'b1) //握手成功后拉低ready信号,等待下一次握手
r_rw_ready <= 'd0;
else if(cur_state == IDLE)
r_rw_ready <= 'd1;
else
r_rw_ready <= 'd0;
end
always @(posedge ui_clk or posedge ui_rst) begin
if(ui_rst == 1'b1)
write_cnt <= 'd0;
else if(cur_state == IDLE)
write_cnt <= 'd0;
else if(wr_mig_valid == 1'b1) //写数据个数计数器
write_cnt <= write_cnt + 1'b1;
else
write_cnt <= write_cnt;
end
always @(posedge ui_clk or posedge ui_rst) begin
if(ui_rst == 1'b1)
read_cnt <= 'd0;
else if(cur_state == IDLE)
read_cnt <= 'd0;
else if(rd_mig_valid == 1'b1) //读数据个数计数器
read_cnt <= read_cnt + 1'b1;
else
read_cnt <= read_cnt;
end
endmodule
打开MIG的Example Design
拿出设计例程里的仿真文件,把里面的控制模块换成自己的顶层文件,如下所示:
直接开始仿真,大概运行到150us即可
上图找到init_calib_complete信号,拉高表示DDR初始化完成。
上图找到app_wdf_wren信号,此时红线表示写入成功,地址从0开始每次累加8,数据从21开始累加1。
上图找到最后一个写数据,因为从21开始累加,写512个,所以最后一个写数据是21+511=532符合设计,地址是从0开始累加8,所以最后一个写地址是511*8=4088,也符合设定,写完最后一个数后从地址0开始读。
从写入读命令以及读地址开始,等一段时间后app_rd_valid信号拉高,表示读出来的数据有效,读出的数据也是从21开始,然后22、23一直累加的出来。
找到最后一个读地址是4088,后面最后一个读数据是532,与前面我们写入的地址以及数据相同,至此DDR3的仿真读写验证成功。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。