本篇结合目前正在做的项目,用S10控制4篇DDR进行数据通讯,从DDR概念原理 到当前主流的DDR4 Verilog 驱动测试的实现,内容有参考网上的进行修改,篇幅可能比较长,实用性强,本人一点点走下来,结合网络资源与自身实践,特总结本篇 DDR4 入门实践篇,真心希望帮助到有需要的每一个人。

本文篇幅过长,请耐心看完,建议分几次看完,效果更佳!

一、内容简介

1.内容简介

2.预备知识

3.DDR4 工作原理

4.DDR4 测试工程讲解

二、预备知识

在学习DDR4之前首先了解几个知识点:

1.SRAM:静态随机存储器,存取速度快,但是容量小,掉电后数据会丢失,制作成本较高,通常用来作为快取(Cache)记忆体使用。

2.DRAM:动态随机存储器,必须不断地重新加强电位差,否则电位差将降低至无法有足够的能量表现每一个记忆单元处于何种状态。价格比SRAM便宜,访问速度较慢,耗电量大,常用作计算机的内存使用。

3.FLASH:闪存,存取速度慢,容量大,掉电后数据不会丢失。

4.SSRAM:同步静态随机存取存储器,对于SSRAM的所有访问都将在时钟的上升沿/下降沿启动。地址、数据输入和其他控制信号均与时钟信号相关。

5.SDRAM:同步动态随机存取存储器。

那么,DDR属于什么类型呢?

6.DDR:说到DDR,估计绝大多数人直接会想到的是电脑里面的存储器,但实际上DDR指的是:Double Data Rate(双倍速率),其实是一种快速实现的技术。

DDR4:第四代双倍速率同步动态随机存取存储器,即 DDR4 SDRAM。在时钟上升沿和下降沿均进行数据采样,这样一个时钟周期就可以采样两个数据,理论读写速度比以前一个周期采样一个数据速度快了2倍。

三、DDR4的工作原理

根据 Intel FPGA 手册 和相关博客,列出DDR4 相关引脚功能说明如下

图1 电源、地、配置引脚说明

图2 DDR4 ODT ZQ配置引脚

图3 FPGA 中使用 DDR4 时主要使用的引脚

本文主要实现对DDR4的逻辑控制,因此主要关注 图3 所示引脚。

和绝大多数存储器一样,DDR4中最最关注的也是 地址信号和数据信号 。如上所示DDR4芯片中共有20根地址线(包括1根Bank Group选通信号线、2根Bank信号线和17根地址线)和16根数据线。

根据芯片所用的地址线和数据线,可以很容易算出DDR4芯片的寻址空间(最大容量):

(2^20) * 16 = 1048576 * 16 = 2048KB = 2MB;

在实际的使用中,上述所示的DDR的最大容量可以达到1GB,这就使用到了 分时复用 的技术。

具体实现步骤如下:

1.将存储空降分成两块,分别为 BANK GROUP0 和 BANK GROUP1 ,再用1根地址线表示,也就是 BG ;

2.将每一个BANK GROUP 分成4个BANK小区域,分别为 BANK0 、BANK1、BANK2、BANK3 ,并使用两根地址线对其编码;

3.剩余的17根地址线,留下1根表示行地址使能信号;

在第一次传输时 ,行地址使能,剩下16根地址线,可表示的行地址范围:

2^16 = 65536 bit = 64 kb;

在第二次传输时 ,行地址禁用,剩下16根地址线,留10根作为列地址线,剩下6根用来表示读写状态/刷新状态等复用功能。

4.这样的一个BANK就有 64K * 2^10 = 64M个地址,

每个地址数据线为16bit,则一个BANK的容量为 64M * 16bit = 1024 Mb,

两个BANK GROUP的容量为:1024Mb * 2 * 4 = 1024MB = 1GB 。

这样就实现了DDR4的存储原理和寻址方式。这样看来其实原理还是挺简单的。有兴趣的话自己照着推一遍,可以加深理解。

四、DDR4 Verilog HDL设计

软件平台:Intel Quartus20.1

硬件设备:Intel Stratix 10 系列开发板

(手册显示 Intel DDR4 IP 貌似仅支持Arria 10和Stratix 10)

DDR4 IP:External Memory Interfaces Intel Stratix 10 FPGA IP

搭建工程步骤如下:

1.在新建的工程中添加 External Memory Interfaces Intel Stratix 10 FPGA IP ,根据Intel官方User Guide配置IP核的参数;

Documentation: External Memory Interfaces​www.intel.com/content/www/us/en/programmable/support/literature/lit-external-memory-interface.html

根据手册结合自己需求设置参数主要有以下几大类:

General 参数设置

General 中参数主要关注的是Interface-Configuration:选择Hard PHY and Hard Controller,这样可以通过Avalon总线接口控制DDR4的读写。Avalon总线相关内容可以查看以下链接:

Memory 参数设置

Memory format:Component,离散存储器,选择这个即可;

DQ width :数据位宽64bit;

DQ pins per DQS group:每个DQS组的数据位宽为8bit,根据DDR发出的DQS信号来判断在什么时候接收读出来的数据。如果是写的话,就正好相反,DDR根据北桥发出的DQS信号来触发数据的接收。

Row address width:行地址宽度为16bit;

Column address width:列地址宽度10bit;

Bank address width:bank地址位宽2bit,可编码4个bank;

Bank group width:Bank group的位宽1bit,可编码两个Bank group;

Data mask:指示接口是否使用数据掩码(DM)引脚,每个DQS组有一个DM引脚。

剩余参数组的选择:结合手册大部分选择了默认,想具体了解可以查看IP Core手册。

配置好的IP Core PORT

配置好的 DDR4 IP Core PIN

2.调用配置好的 DDR4 IP Core

例化配置好的IP Core

//DDR4 IP Instantiation
    ddr_ip u_ddr_ip (
        .local_reset_req     (_connected_to_local_reset_req_),     //   input,    width = 1,    local_reset_req.local_reset_req
        .local_reset_done    (_connected_to_local_reset_done_),    //  output,    width = 1, local_reset_status.local_reset_done
        .pll_ref_clk         (_connected_to_pll_ref_clk_),         //   input,    width = 1,        pll_ref_clk.clk
        .oct_rzqin           (_connected_to_oct_rzqin_),           //   input,    width = 1,                oct.oct_rzqin
        .mem_ck              (_connected_to_mem_ck_),              //  output,    width = 1,                mem.mem_ck
        .mem_ck_n            (_connected_to_mem_ck_n_),            //  output,    width = 1,                   .mem_ck_n
        .mem_a               (_connected_to_mem_a_),               //  output,   width = 17,                   .mem_a
        .mem_act_n           (_connected_to_mem_act_n_),           //  output,    width = 1,                   .mem_act_n
        .mem_ba              (_connected_to_mem_ba_),              //  output,    width = 2,                   .mem_ba
        .mem_bg              (_connected_to_mem_bg_),              //  output,    width = 1,                   .mem_bg
        .mem_cke             (_connected_to_mem_cke_),             //  output,    width = 1,                   .mem_cke
        .mem_cs_n            (_connected_to_mem_cs_n_),            //  output,    width = 1,                   .mem_cs_n
        .mem_odt             (_connected_to_mem_odt_),             //  output,    width = 1,                   .mem_odt
        .mem_reset_n         (_connected_to_mem_reset_n_),         //  output,    width = 1,                   .mem_reset_n
        .mem_par             (_connected_to_mem_par_),             //  output,    width = 1,                   .mem_par
        .mem_alert_n         (_connected_to_mem_alert_n_),         //   input,    width = 1,                   .mem_alert_n
        .mem_dqs             (_connected_to_mem_dqs_),             //   inout,    width = 8,                   .mem_dqs
        .mem_dqs_n           (_connected_to_mem_dqs_n_),           //   inout,    width = 8,                   .mem_dqs_n
        .mem_dq              (_connected_to_mem_dq_),              //   inout,   width = 64,                   .mem_dq
        .mem_dbi_n           (_connected_to_mem_dbi_n_),           //   inout,    width = 8,                   .mem_dbi_n
        .local_cal_success   (_connected_to_local_cal_success_),   //  output,    width = 1,             status.local_cal_success
        .local_cal_fail      (_connected_to_local_cal_fail_),      //  output,    width = 1,                   .local_cal_fail
        .emif_usr_reset_n    (_connected_to_emif_usr_reset_n_),    //  output,    width = 1,   emif_usr_reset_n.reset_n
        .emif_usr_clk        (_connected_to_emif_usr_clk_),        //  output,    width = 1,       emif_usr_clk.clk
        .amm_ready_0         (_connected_to_amm_ready_0_),         //  output,    width = 1,         ctrl_amm_0.waitrequest_n
        .amm_read_0          (_connected_to_amm_read_0_),          //   input,    width = 1,                   .read
        .amm_write_0         (_connected_to_amm_write_0_),         //   input,    width = 1,                   .write
        .amm_address_0       (_connected_to_amm_address_0_),       //   input,   width = 26,                   .address
        .amm_readdata_0      (_connected_to_amm_readdata_0_),      //  output,  width = 512,                   .readdata
        .amm_writedata_0     (_connected_to_amm_writedata_0_),     //   input,  width = 512,                   .writedata
        .amm_burstcount_0    (_connected_to_amm_burstcount_0_),    //   input,    width = 7,                   .burstcount
        .amm_byteenable_0    (_connected_to_amm_byteenable_0_),    //   input,   width = 64,                   .byteenable
        .amm_readdatavalid_0 (_connected_to_amm_readdatavalid_0_)  //  output,    width = 1,                   .readdatavalid
    );

3. 实现DDR4 IP控制逻辑的编写,主要是通过Avalon总线实现DDR4的用户读写逻辑设计。Avalon总线基础需要自行补充

Verilog实现读写逻辑设计思路(代码根据Avalon总线机制编写),这里说一下思路:

写逻辑:由于设置数据位宽为512bit,在这里使用计数器赋值,具体的写情况结合Avalon机制和自己想实现的写逻辑进行写操作;

读逻辑:每次发起读使能后,等到avalon_mm_waitrequest释放后,读有效。

4.本来想给大家展示一个根据手册做的modelsim结果,电脑配置不太好,没跑出来。直接上板子看吧!SignalTap观测结果如下:

SignalTap观测DDR4读写

SignalTap观测DDR4写计数器

SignalTap观测DDR4读计数器

用熟练以后,会发现DDR4用于大数据流缓存非常方便。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐