详解DDR3原理以及使用Xilinx MIG IP核(app 接口)实现DDR3读写测试

系列文章目录

(1)详解SDRAM基本原理以及FPGA实现读写控制


文章目录

  • 系列文章目录
  • 一、DDR简介
    • [1.1 什么是 SDRAM、DDR、DDR2、DDR3](#1.1 什么是 SDRAM、DDR、DDR2、DDR3)
    • [1.2 SDRAM、DDR、DDR2、DDR3核心频率、工作频率以及等效频率的计算](#1.2 SDRAM、DDR、DDR2、DDR3核心频率、工作频率以及等效频率的计算)
    • [1.3 DDR3带宽以及容量的计算](#1.3 DDR3带宽以及容量的计算)
  • [二、MIG IP核的介绍](#二、MIG IP核的介绍)
  • [三、MIG 用户接口信号介绍](#三、MIG 用户接口信号介绍)
  • [四、MIG 操作时序](#四、MIG 操作时序)
    • [4.1 命令操作时序](#4.1 命令操作时序)
    • [4.2 写数据时序](#4.2 写数据时序)
    • [4.3 读数据时序](#4.3 读数据时序)
  • [五、配置 MIG 步骤](#五、配置 MIG 步骤)
  • [六、MIG 读写控制代码编写](#六、MIG 读写控制代码编写)
    • [6.1 系统框图](#6.1 系统框图)
    • [6.2 读写数据控制代码](#6.2 读写数据控制代码)
    • [6.3 MIG 控制代码](#6.3 MIG 控制代码)
  • 七、仿真
    • [7.1 添加仿真文件](#7.1 添加仿真文件)
    • [7.2 打开仿真观察波形](#7.2 打开仿真观察波形)

一、DDR简介

在前文《详解SDRAM基本原理以及FPGA实现读写控制》中我们学会了SDRAM的基本原理以及读写操作时序,本文讲解的DDR3全称为"Double Data Rate 3"(双倍数据速率第三代),它是一种用于计算机和其他设备的随机存取存储器(RAM)技术。

1.1 什么是 SDRAM、DDR、DDR2、DDR3

SDRAM、DDR、DDR2和DDR3都是不同类型的动态随机存取存储器(DRAM)技术,它们在数据传输速率、功耗、电压和性能方面有所不同。以下是这几种内存的区别:

  1. SDRAM(Synchronous DRAM)

    • 同步动态随机存取存储器,是最早的一种同步内存技术,它与系统总线同步工作。
    • SDRAM 需要两个时钟周期来传输数据,因此数据传输速率相对较低。
  2. DDR SDRAM(Double Data Rate Synchronous DRAM)

    • 双倍数据速率同步动态随机存取存储器,简称DDR。
    • DDR 内存在每个时钟周期的上升沿和下降沿都能传输数据,因此数据传输速率是SDRAM的两倍。
    • DDR 内存的电压通常为 2.5V 或 3.3V。
  3. DDR2 SDRAM

    • DDR2 是 DDR 的后继产品,具有更高的数据传输速率和更低的电压。
    • DDR2 内存的电压为 1.8V,比 DDR 的 2.5V 或 3.3V 低,有助于降低功耗。
    • DDR2 内存的起始频率为 400 MHz,最高可达 1200 MHz。
  4. DDR3 SDRAM

    • DDR3 是 DDR2 的后继产品,进一步提高了数据传输速率和降低了电压。
    • DDR3 内存的电压为 1.5V,比 DDR2 的 1.8V 更低,进一步降低了功耗。
    • DDR3 内存的起始频率为 800 MHz,最高可达 2133 MHz 或更高。
  5. 主要区别

    • 数据传输速率: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核写数据,具体时序如下图所示:

如上图所示,写数据有三种情形均可以正确写入:

  1. 写数据时序和写命令时序发生在同一拍
  2. 写数据时序比写命令时序提前一拍
  3. 写数据时序比写命令时序至多延迟两拍

4.3 读数据时序

用户发出读命令后,用户只需等待数据有效信号(app_rd_data_valid)拉高,为高表明此时数据总线上的数据是有效的返回数据,有效读数据要晚若干周期才出现在数据总线上。如下图所示:

五、配置 MIG 步骤

打开IP库搜索MIG,然后打开,第一个界面是MIG的一些简要说明,直接点NEXT就行。

第二个界面主要就是自定义MIG名称,以及MIG核数量和是否用AXI接口的MIG,本次实验先用默认的app接口,所以不选AXI4。

第三个界面选择兼容的FPGA器件,这里不用就不选择

第四个界面选择控制什么类型的DDR,这里选择DDR3

第五个界面选择DDR3具体的物理参数

  1. Clock Period:MIG输出给DDR3物理运行的时钟频率,DDR3基于此时钟双沿采样,因此DDR3的实际传输数据速率为800M。
  2. PHY to Controller Clock Ratio:DDR3物理芯片运行时钟和MIG IP核的用户端(FPGA)的时钟之比;一般有 4:1 和 2:1 两个选项,本次实验选 4:1。由于 DDR 芯片的运行时钟是 400MHz,因此 MIG IP 核的用户时钟(ui_clk)就是100MHz。
  3. Memory Type:DDR3 储存器类型选择。默认选择 Component,即贴片式的DDR。SODIMM为笔记本的DDR,RDIMM为服务器的DDR,UDIMM是台式机用的DDR。
  4. Memory Part:DDR3具体型号,这里选择开发板上的MT41J12816-125,如果没有找到自己开发板的型号,可以选择相近的芯片型号或者点击Create Custom Part来自定义芯片内部参数。
  5. Memory Voltage:DDR电压,上文可知DDR3的电压为1.5V,所以这里固定1.5V。
  6. Data Width:数据位宽选择,因为是开发板上有2块ddr3,所以这里位宽选择32。
  7. ECC:校验使能,数据位宽为 72 位的时候才能使用。
  8. Data Mask:数据屏蔽管脚使能
  9. Number of Bank Machines:是用来对具体的BANK使用几个Bank Machine来单独控制的,选择多了控制效率就会高,相应的占用的资源也多,本次芯片内部有8个bank,这里就选择4,每一个Bank Machine来控制2个BANK
  10. ORDERING: 该信号用来决定MIG控制器是否可以对它收到的指令进行重新排序,选择Normal则允许,Strict则禁止。本次选择Normal,从而获得更高效率。

第六个界面选择MIG的设置

  1. Input Clock Period:输入给MIG核的时钟,MIG通过此时钟分频或者倍频出其它时钟,这里选择200M
  2. Read Burst Type and Length:突发类型选择顺序突发,且突发长度只支持8
  3. Output Driver Impdance Control:输出阻抗控制,这里选择默认的RZQ/7即可
  4. RTT:终端电阻,选择默认的RZQ/4即可
  5. Controller Chip Select Pin:片选引脚使能
  6. BANK_ROW_COLUMN:寻址方式选择第二种

选择MIG参考时钟以及复位

  1. System Clock:选择上一个界面选择的时钟来源,如果是外部单端晶振进来,就选择Single-Ended;如果是外部差分晶振进来,就选择Differential;如果是内部锁相环产生,就选择No Buffer,这里我们是内部PLL产生,所以选择No Buffer。

  2. Reference Clock:MIG IP 核参考时钟。IP 核参考时钟要求是200Mhz,而MIG IP 核的系统时钟刚好也使用了200Mhz的系统时钟,所以可以选择Use System Clock这个选项

  3. System Reset Polarity:复位信号电平选择,这里选择低有效

下一个界面是配置阻抗匹配,默认50Ohms即可

下一个界面是选择DDR管脚,如果只是单纯仿真,选上面就好,如果是下板验证,就要选择下面这个根据实际开发板管脚选择

下一个界面是选择一些信号引脚,这里全都选择No connect即可,后续没有配置了,一路点next就完成了MIG核的配置

六、MIG 读写控制代码编写

6.1 系统框图

本次测试有两个模块,一个MIG核控制模块,一个读写数据控制模块,具体系统框图如下所示:

读写数据控制模块,主要产生想要写入ddr的数据以及读写地址还有读写的长度给MIG控制模块,MIG控制模块收到这些数后产生MIG核的读写时序来读写DDR。

6.2 读写数据控制代码

c 复制代码
`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 控制代码

c 复制代码
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开始累加的读出来。仿真完成

相关推荐
贾saisai3 小时前
Xilinx系FPGA学习笔记(四)VIO、ISSP(Altera)及串口学习
笔记·学习·fpga开发
行者..................5 小时前
FPGA 时序逻辑 组合逻辑 连接 语句
fpga开发·fpga
廊桥遗梦72811 小时前
FPGA开发:条件语句 × 循环语句
算法·fpga开发
cckkppll12 小时前
vivado 创建时间约束2
fpga开发
北京太速科技股份有限公司17 小时前
太速科技-1路万兆光纤SFP+和1路千兆网络 FMC子卡模块
fpga开发
楠了个难1 天前
UART串口通信——FPGA学习笔记9
笔记·学习·fpga开发
FPGA狂飙1 天前
1分钟 快速掌握 双向信号(inout信号)
fpga开发·verilog·fpga·xilinx
通信小小昕1 天前
基于I2S的音频ADC_DAC的_FPGA的驱动
fpga开发·音频·adc·fifo·dac·i2s
十三啊嘞2 天前
Xilinx FPGA 原语解析(三):OBUFDS差分输出缓冲器(示例源码及仿真)
fpga开发
廊桥遗梦7282 天前
FPGA开发:模块 × 实例化
fpga开发