系列文章目录
文章目录
一、DDR简介
在前文《详解SDRAM基本原理以及FPGA实现读写控制》中我们学会了SDRAM的基本原理以及读写操作时序,本文讲解的DDR3全称为“Double Data Rate 3”(双倍数据速率第三代),它是一种用于计算机和其他设备的随机存取存储器(RAM)技术。
1.1 什么是 SDRAM、DDR、DDR2、DDR3
SDRAM、DDR、DDR2和DDR3都是不同类型的动态随机存取存储器(DRAM)技术,它们在数据传输速率、功耗、电压和性能方面有所不同。以下是这几种内存的区别:
-
SDRAM(Synchronous DRAM):
- 同步动态随机存取存储器,是最早的一种同步内存技术,它与系统总线同步工作。
- SDRAM 需要两个时钟周期来传输数据,因此数据传输速率相对较低。
-
DDR SDRAM(Double Data Rate Synchronous DRAM):
- 双倍数据速率同步动态随机存取存储器,简称DDR。
- DDR 内存在每个时钟周期的上升沿和下降沿都能传输数据,因此数据传输速率是SDRAM的两倍。
- DDR 内存的电压通常为 2.5V 或 3.3V。
-
DDR2 SDRAM:
- DDR2 是 DDR 的后继产品,具有更高的数据传输速率和更低的电压。
- DDR2 内存的电压为 1.8V,比 DDR 的 2.5V 或 3.3V 低,有助于降低功耗。
- DDR2 内存的起始频率为 400 MHz,最高可达 1200 MHz。
-
DDR3 SDRAM:
- DDR3 是 DDR2 的后继产品,进一步提高了数据传输速率和降低了电压。
- DDR3 内存的电压为 1.5V,比 DDR2 的 1.8V 更低,进一步降低了功耗。
- DDR3 内存的起始频率为 800 MHz,最高可达 2133 MHz 或更高。
-
主要区别:
- 数据传输速率:DDR3 > DDR2 > DDR > SDRAM。
- 电压:DDR3 (1.5V) < DDR2 (1.8V) < DDR (2.5V/3.3V) < SDRAM。
- 功耗:随着电压的降低和制造工艺的改进,DDR3 的功耗最低。
- 性能:DDR3 提供了最高的性能,其次是 DDR2 和 DDR,SDRAM 性能最低。
- 兼容性:新一代的内存技术通常不兼容旧一代的内存插槽,例如 DDR3 内存不能在只支持 DDR2 的主板上使用。
1.2 SDRAM、DDR、DDR2、DDR3核心频率、工作频率以及等效频率的计算
从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的时钟来读写数据。
1.3 DDR3带宽以及容量的计算
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
二、MIG IP核的介绍
前面我们介绍了DDR3的一些知识,对DDR3有了一个大致了解,那么我们怎么去使用DDR3呢?与SDRAM不同的是DDR3 的时序非常复杂,用户直接编写DDR3的控制代码是相当耗时以及性能得不到保证,那么绝大部分开发者都会使用芯片厂家提供的控制器来操作DDR3。
MIG(Memory Interface Generators) IP 核是Xilinx提供给7系以及以上的用户来实现 DDR3 的读写控制器。MIG有AXI接口版本以及app接口的版本,本次我们讲解app接口的MIG,AXI接口的后续在讲。app接口的MIG系统框图如下所示:
- 最右侧的DDR2/DDR3 SDRAM为物理上的DDR3存储器。
- 中间的7 Series FPGAs Memory Interface Solution 就是上面所说的MIG IP核,它与DDR3控制器通过右边以ddr开头命名的信号相连接。
- 最左侧是我们用户自己的逻辑,使用以app开头的信号与MIG IP核相连接。
对于用户来说,只有正确的控制app这些信号才能操作MIG,还要负责分配正确的DDR3管脚,其他不用关心。。MIG内部负责产生具体的DDR3操作时序,并直接操作DDR3读写。
三、MIG 用户接口信号介绍
从上面系统框图来看,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 | 输出 | 请求校准阻抗匹配信号 |
四、MIG 操作时序
4.1 命令操作时序
当用户逻辑app_en信号被断言且app_rdy信号从MIG被断言时,MIG接受命令。每当app_rdy被取消断言时,MIG将忽略该命令。用户逻辑需要将app_en与有效命令和地址值沿着保持为高电平,直到app_rdy被断言,如下图所示:
只有当app_rdy 与 app_en同时为高时,命令和地址才会被写入到MIG,如图红色的位置,这类似于AXI里的握手信号。
4.2 写数据时序
首先需要检查app_wdf_rdy,该信号为高表明此时IP核数据接收处于准备状态,可以接收用户发过来的数据,在当前时钟拉高写使能(app_wdf_wren),同时给出写数据这样加上发起的写命令操作就可以成功向IP核写数据,具体时序如下图所示:
如上图所示,写数据有三种情形均可以正确写入:
- 写数据时序和写命令时序发生在同一拍
- 写数据时序比写命令时序提前一拍
- 写数据时序比写命令时序至多延迟两拍
4.3 读数据时序
用户发出读命令后,用户只需等待数据有效信号(app_rd_data_valid)拉高,为高表明此时数据总线上的数据是有效的返回数据,有效读数据要晚若干周期才出现在数据总线上。如下图所示:
五、配置 MIG 步骤
打开IP库搜索MIG,然后打开,第一个界面是MIG的一些简要说明,直接点NEXT就行。
第二个界面主要就是自定义MIG名称,以及MIG核数量和是否用AXI接口的MIG,本次实验先用默认的app接口,所以不选AXI4。
第三个界面选择兼容的FPGA器件,这里不用就不选择
第四个界面选择控制什么类型的DDR,这里选择DDR3
第五个界面选择DDR3具体的物理参数
- Clock Period:MIG输出给DDR3物理运行的时钟频率,DDR3基于此时钟双沿采样,因此DDR3的实际传输数据速率为800M。
- PHY to Controller Clock Ratio:DDR3物理芯片运行时钟和MIG IP核的用户端(FPGA)的时钟之比;一般有 4:1 和 2:1 两个选项,本次实验选 4:1。由于 DDR 芯片的运行时钟是 400MHz,因此 MIG IP 核的用户时钟(ui_clk)就是100MHz。
- Memory Type:DDR3 储存器类型选择。默认选择 Component,即贴片式的DDR。SODIMM为笔记本的DDR,RDIMM为服务器的DDR,UDIMM是台式机用的DDR。
- Memory Part:DDR3具体型号,这里选择开发板上的MT41J12816-125,如果没有找到自己开发板的型号,可以选择相近的芯片型号或者点击Create Custom Part来自定义芯片内部参数。
- Memory Voltage:DDR电压,上文可知DDR3的电压为1.5V,所以这里固定1.5V。
- Data Width:数据位宽选择,因为是开发板上有2块ddr3,所以这里位宽选择32。
- ECC:校验使能,数据位宽为 72 位的时候才能使用。
- Data Mask:数据屏蔽管脚使能
- Number of Bank Machines:是用来对具体的BANK使用几个Bank Machine来单独控制的,选择多了控制效率就会高,相应的占用的资源也多,本次芯片内部有8个bank,这里就选择4,每一个Bank Machine来控制2个BANK
- ORDERING: 该信号用来决定MIG控制器是否可以对它收到的指令进行重新排序,选择Normal则允许,Strict则禁止。本次选择Normal,从而获得更高效率。
第六个界面选择MIG的设置
- Input Clock Period:输入给MIG核的时钟,MIG通过此时钟分频或者倍频出其它时钟,这里选择200M
- Read Burst Type and Length:突发类型选择顺序突发,且突发长度只支持8
- Output Driver Impdance Control:输出阻抗控制,这里选择默认的RZQ/7即可
- RTT:终端电阻,选择默认的RZQ/4即可
- Controller Chip Select Pin:片选引脚使能
- BANK_ROW_COLUMN:寻址方式选择第二种
选择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 读写控制代码编写
6.1 系统框图
本次测试有两个模块,一个MIG核控制模块,一个读写数据控制模块,具体系统框图如下所示:
读写数据控制模块,主要产生想要写入ddr的数据以及读写地址还有读写的长度给MIG控制模块,MIG控制模块收到这些数后产生MIG核的读写时序来读写DDR。
6.2 读写数据控制代码
`timescale 1ns / 1ps
module ddr_rw_data_ctrl(
input ui_clk , //MIG给出的用户使用时钟
input ui_rst , //时钟复位信号,高电平有效
output [1:0] rw_cmd , //读写命令,写0读1
output [9:0] rw_len , //读写数据长度
output [27:0] rw_addr , //读写地址
output rw_valid , //读写地址有效信号
input rw_ready , //MIG控制给出的ready信号,拉高表示准备接受读写地址
output [255:0] wr_data , //需要写入DDR的数据
output wr_data_valid , //写入DDR数据的有效信号
output wr_data_last //本次写入的最后一个数据
);
/***************parameter*************/
localparam rw_num = 512; //读写的个数
localparam IDLE = 5'b00001,
WRITE = 5'b00010,
WRITE_WAITE = 5'b00100,
READ = 5'b01000,
READ_WAITE = 5'b10000;
/***************mechine***************/
reg [4:0] cur_state ;
reg [4:0] next_state ;
/***************reg*******************/
reg [1:0] r_rw_cmd ;
reg [9:0] r_rw_len ;
reg [27:0] r_rw_addr ;
reg r_rw_valid ;
reg [255:0] r_wr_data ;
reg r_wr_data_valid ;
reg r_wr_data_last ;
reg [9:0] wait_cnt ; //等待读数据个数计数器
reg [9:0] wr_data_cnt ; //写数据个数计数器
/***************assign****************/
assign rw_cmd = r_rw_cmd ;
assign rw_len = r_rw_len ;
assign rw_addr = r_rw_addr ;
assign rw_valid = r_rw_valid ;
assign wr_data = r_wr_data ;
assign wr_data_valid = r_wr_data_valid;
assign wr_data_last = r_wr_data_last ;
/***************always****************/
always @(posedge ui_clk or posedge ui_rst) begin
if(ui_rst == 1'b1)
cur_state <= IDLE;
else
cur_state <= next_state;
end
always @(*) begin
case (cur_state)
IDLE :begin
if(rw_ready == 1'b1) //如果下游准备好接受命令,则跳转到写状态
next_state <= WRITE;
else
next_state <= IDLE;
end
WRITE : begin
if(wr_data_cnt == rw_num - 1) //写入了设定的数据长度后跳转到写等待
next_state <= WRITE_WAITE;
else
next_state <= WRITE;
end
WRITE_WAITE :begin //如果下游再次拉高ready信号,表示已经写完成了,此时跳转到read状态
if(rw_ready == 1'b1)
next_state <= READ;
else
next_state <= WRITE_WAITE;
end
READ : begin //等待数据长度后,跳转到读等待状态
if(wait_cnt == rw_num - 1)
next_state <= READ;
else
next_state <= READ_WAITE;
end
READ_WAITE : begin //下游再次拉高ready信号,表示已经读完成,跳转到IDEL
if(rw_ready == 1'b1)
next_state <= IDLE;
else
next_state <= READ_WAITE;
end
default: next_state <= IDLE;
endcase
end
always @(posedge ui_clk or posedge ui_rst) begin
if(ui_rst == 1'b1)
wait_cnt <= 'd0;
else if(cur_state == READ) //等待计数器
wait_cnt <= wait_cnt + 1'b1;
else
wait_cnt <= 'd0;
end
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;
r_rw_valid <= 'd0;
end
else if((cur_state == IDLE) && (next_state == WRITE))begin //如果准备跳转到写状态,表示下游拉高了ready信号,此时给出写命令,以及写长度,写地址,同时拉高valid信号
r_rw_cmd <= 'd0;
r_rw_len <= rw_num;
r_rw_addr <= 'd0;
r_rw_valid <= 'd1;
end
else if((cur_state == WRITE_WAITE) && (next_state == READ))begin//同理
r_rw_cmd <= 'd1;
r_rw_len <= rw_num;
r_rw_addr <= 'd0;
r_rw_valid <= 'd1;
end
else begin
r_rw_cmd <= 'd0;
r_rw_len <= 'd0;
r_rw_addr <= 'd0;
r_rw_valid <= 'd0;
end
end
always @(posedge ui_clk or posedge ui_rst) begin
if(ui_rst == 1'b1)
r_wr_data_valid <= 1'b0;
else if(wr_data_cnt == rw_num - 1)
r_wr_data_valid <= 1'b0;
else if(rw_valid && rw_ready) //如果地址信号握手成功,则写入rw_num个数据
r_wr_data_valid <= 1'b1;
else
r_wr_data_valid <= r_wr_data_valid;
end
always @(posedge ui_clk or posedge ui_rst) begin
if(ui_rst == 1'b1)
r_wr_data <= 'd21;
else if(wr_data_cnt == rw_num - 1)
r_wr_data <= 'd21;
else if(r_wr_data_valid) //数据从21开始,每次累加1
r_wr_data <= r_wr_data + 1'b1;
else
r_wr_data <= r_wr_data;
end
always @(posedge ui_clk or posedge ui_rst) begin
if(ui_rst == 1'b1)
wr_data_cnt <= 'd0;
else if(wr_data_cnt == rw_num - 1)
wr_data_cnt <= 'd0;
else if(r_wr_data_valid) //每写一次,写数据计数器累加1
wr_data_cnt <= wr_data_cnt + 1'b1;
else
wr_data_cnt <= wr_data_cnt;
end
endmodule
6.3 MIG 控制代码
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(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
七、仿真
7.1 添加仿真文件
打开MIG的Example Design
拿出设计例程里的仿真文件,把里面的控制模块换成自己的顶层文件,如下所示:
7.2 打开仿真观察波形
直接开始仿真,大概运行到150us即可
找到init_calib_complete信号,拉高表示DDR初始化完成。
找到app_wdf_wren信号,此时红线表示写入成功,地址从0开始累加8,数据从0开始 然后到21,22开始累加。
找到app_rd_valid信号,表示数读有效,数据也是从21开始累加的读出来。仿真完成