DDR4读写测试(二):基本读写测试

2024-01-23 06:59
文章标签 基本 测试 读写 ddr4

本文主要是介绍DDR4读写测试(二):基本读写测试,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上次基本讲了怎么配置MIG的IP,这次继续翻译手册PG150,根据其提供用户端的app接口的读写模式,针对每种模式进行最基本的读写测试。


MIG核用户app接口信号定义

写RTL前需要了解些什么?

  • 还需了解什么?知道个app接口定义不就好了????

但似乎在(一):MIG IP核配置中并未提地址的事情,所以本文再探讨下地址的问题。

我们知道,一个RAM中一个地址对应存储一个数据。但是问题来了,在KCU116评估板上有两颗DDR4的颗粒,都是256Mb*16的,也就是总容量为256Mb*16*2=8Gb,但是app接口提供的地址位宽为28bit,数据位宽256,也就是说对应的数据量为(2^28)*256bit=64Gb,显然64不等于8,那岂不是地址长了?高位地址都没用到?

综上,我们需要了解使用的这个IP的app接口的地址的含义。

  • 遇事不决,Product Guide!

在文档PG150中的123-128页中特地说明地址的定义,由于KCU116评估板上采用两颗DDR4的颗粒,并且在之前的MIG的配置中采用“ROW COLUMN BANK”映射形式(当然可以按照需要选择不同的映射形式),故可以直接看PG150的124页[1]。

DDR4 “ROW_COLUMN_BANK” Mapping

由上图可以知道,app_addr包括{rank, logical_rank, row, column-3, bank, bank_group, 3},那么问题又来了,以上这些是指什么意思?

这就和DDR的结构有关系了,笔者简单画了个DDR4颗粒的结构图(当然画的比较简单抽象),如下所示:

DDR4颗粒的简单结构

由上图可以很清楚的看到,一颗DDR4的颗粒由若干Bank Group组成,每个Bank Group又由若干Bank组成,然后一个Bank由若干行列的矩阵组成,某行某列对应存储某个数据。当然DDR4颗粒会采用3DS(3-Dimensional Stack)堆叠方式来提升单颗的容量,上图只表示为一层时的情况,即logical_rank为1。当然对于某些DDR4内存条来说不只一颗DDR4颗粒,所以又会出现多个颗粒组成一个RANK,比如假定四颗数据位宽为16的DDR4颗粒组成一个RANK,但DDR4内存条上有八颗DDR4颗粒,那么RANK就是2。

了解这些之后,再查询DDR4的颗粒手册,板子采用MT40A256M16GE-075E,参数如下图所示[2]:

MT40A256M16GE-075E参数

由上可以得知,各参数位宽为:rank = 1,logical_rank = 1,row = 15,column = 10, bank = 2,bank_group = 1。由于rank和logical_rank均为1,按照图“DDR4 “ROW_COLUMN_BANK” Mapping”可知app_addr位宽不包括rank和logical_rank,那么累加其余位宽可得28,正好和app_addr位宽一致!

app接口的地址定义清楚了,那么为什么数据位宽为256,为颗粒16*2的两倍,这时需要注意的是,由两个DDR4颗粒组成一个rank,数据总位宽为32bit,即上述app_addr每个地址对应数据位宽即为32bit,而该MIG中采用Burst Length 8,即app接口的数据位宽为256bit,但经过MIG后分成八次32bit存入相应的地址中,简单计算可得(2^28)*32bit = 8Gb,正好是两个DDR4颗粒的总容量

  • 破案了!

由上可知,并非说app接口的每个地址对应256bit的数据,而是只对应32bit,所以在读写数据的过程中,故需要注意地址的对齐,即app_addr[addr_width-1:3]中的地址可作为256bit数据的读写地址,而低三位[2:0]作为每个256bit分为8个32bit数据的存储地址。

  • 在什么情况下读写DDR4的效率最高?

前文说过DDR4相较于DDR3的最大改进之处即提出Bank Group结构,而每个Group可以独立工作,即如果前后对DDR的操作位于不同的Group里,那么后者不必等带前者执行完即可独立执行读写操作。那么本文采用的这颗DDR具有2个Bank Group,也就是说在最理想的情况下只要前后两次读操作或者写操作在不同的Group,即可获得最大性能。

上文得到的app_addr = {row, column-3, bank, bank_group, 3},即保证相邻两次读或写操作的app_addr[3]不一致,即可获得最高的读写效率。

上仿真?

本文暂不进行DDR4读写的仿真试验,具体仿真可详见:

  • 小勇奋战:DDR3自建仿真平台(一)--成功拉起init_calib_complete信号
  • 小勇奋战:DDR3自建仿真平台(二)--用户端读写仿真测试
  • 小勇奋战:DDR3的亲戚DDR4--依旧是跑仿真

测试一:简单顺序写读测试

接下来就是进行简单的读写测试,读写的时许图如下图所示[3],RTL按照时许图写即可。

DDR4 写操作时序图

DDR4 读操作时序图

  • 万物皆可状态机!

笔者采用状态机简单写了DDR4的写读的RTL如后附录所示,大概逻辑是:每次先向连续八个地址写入八个数据,再从这八个地址将数据读出,然后这个流程一直循环!

之后就是综合加Debug布局布线生成bit下载到 KCU116 评估板上观察结果!

  • 直接看测试结果!

下图即循环写读流程中一次写读数据过程,分为1-4步可见下图。

一次写读DDR4的Debug结果

依次向28'h8F81178 - 28'h8F811B0中八个地址写入111111... - 88888.....等八个数据。

写命令和写数据结果

之后再依次向28'h8F81178 - 28'h8F811B0中八个地址读出111111... - 88888.....等八个数据。

读命令和读数据结果

  • 上板测试成功!

由上测试可知,在地址对齐的情况下,连续向多个地址写入数据并再次读出,读写一致,即完成对DDR4的读写测试。同时可以发现,读出数据的顺序与读地址的顺序一致。

  • 读写8个太少,我再多点呢?

代码逻辑不变,只是将单次写读长度设为64,即单次写读256bit * 64 = 2048 bytes数据,结果如下图。

连续写读64*256bit数据结果

对DDR4连续进行64次读写操作,部分结果如下两张图所示,经过写读比较发现,两者数据一致,似乎日常使用问题不大。

写数据通道

读数据通道

对于该MIG的性能在文档PG150已有说明,如下图即该控制器对总线利用率的情况[4],或许在其他设计中,可以根据下图情况进行合理设计以达到最大利用率。当然,在文档中还有很多性能参数的介绍,这里不再赘述。

DDR总线效率

测试二:读写延时等测试

对于用户app接口来说,写数据的时候,命令通道和写数据通道的前后延时最大不超过2个时钟周期,在文档PG150已有说明,若下图所示[5],即在允许的范围内,写命令通道可以不与写数据通道一一对齐。

用户app接口写时许图

那么问题来了,我写数据输出到数据真正写入DDR的延时是多少?以及读数据的命令输出到该地址的数据被读出,中间又有多长的延时?

似乎问题在IP的配置过程中已有回答,如下图所示,即写延时14个时钟周期,读延时18个时钟周期,那么果真如此吗?

MIG核的Basic界面

由于写延时14,读延时18,在现在的代码逻辑下不能进行测试,故只测试读延时。

下图即读延时的测试结果,的确如IP配置中所说,从读命令发出到数据被读出,中间历经18个时钟周期,诚不欺我!

读延时测试结果

测试三:问题测试

  • 前面说了,用户app接口的命令通道的地址是根据DDR4的存储结构映射而来的,在使用app接口进行数据的读写时需要注意地址对齐,但是,我就不对齐,你拿我怎样?

如果地址不对齐(从低位地址直接累加),测试结果如下,光这锯齿般的读写就说明效率不高,更不用说数据不对。

地址不对齐,试试就逝世

  • 那我对齐地址,但是无视Bank Group、Bank、Row、Column等,读写的地址反复横跳、变换无常又会怎样?

地址反复横跳的最差测试结果如下图所示(同一Bank里读写),虽说写的数据和读出来的数据一致,但是可以明显的看到命令通道的ready信号会拉低,说明由于读写地址在同一bank group里等问题会出现一定的延时,DDR控制器处理不过来,从而导致读写效率的降低。

最差读写测试情况

总结

使用MIG的app接口进行读写操作时,需注意地址的映射关系,需注意地址对齐,顺序读写DDR4的效率最高!

注:上述顺序读写即选择“ROW COLUMN BANK”映射,以app_addr[n-1:3] <= app_addr[n-1:3] + 1顺序地址情况下,这样即可做到前后读写数据的地址位于不同的Bank Group里,以最大效率利用DDR的带宽。


以上即DDR4基本读写测试的全部内容,似乎前面关于地址的问题放到(一)中更为合适,不过先就这样放吧。可能上述测试比较简单,之后有时间再深入研究下DDR的其他问题。

因为目前只会使用MIG对DDR进行读写操作,对于DDR的原理结构等尚未非常了解,如有不对之处,还望批评指正~


附录1-DDR4写读测试源码

    //代码写的粗糙,仅作循环写读测试使用//--------信号定义------------localparam          WR_LENGTH   =   8'd8; //每次循环写读测试中数据长度 8*256bitreg     [15:0]      state_ns;reg     [15:0]      state_cs;localparam          IDLE        =   16'h1,WR_DDR      =   16'h2,WAIT        =   16'h4,RD_DDR      =   16'h8,PROC_END    =   16'h10;reg                 process_start;wire                write_ddr_end;wire                wait_ddr_end;wire                read_ddr_end;wire                process_end;reg     [7:0]       write_ddr_cnt;reg     [7:0]       wait_ddr_cnt;reg     [7:0]       read_ddr_cnt;reg     [15:0]      process_cnt;//命令通道reg                 ddr4_app_en;reg     [27:0]      ddr4_app_addr;reg     [2:0]       ddr4_app_cmd;//写数据通道reg                 ddr4_app_wdf_wren;reg                 ddr4_app_wdf_end;reg     [255:0]     ddr4_app_wdf_data;reg     [31:0]      ddr4_app_wdf_mask;//----------CMD--------------always @ (posedge ddr4_clk or negedge ddr4_rst_n) beginif(!ddr4_rst_n) beginstate_cs    <=  IDLE;endelse beginstate_cs    <=  state_ns;endendalways @ (*) begincase(state_cs)IDLE:beginif (process_start) beginstate_ns    =   WR_DDR;endelse beginstate_ns    =   IDLE;endendWR_DDR:beginif (write_ddr_end) beginstate_ns    =   WAIT;endelse beginstate_ns    =   WR_DDR;endendWAIT:beginif (wait_ddr_end) beginstate_ns    =   RD_DDR;endelse beginstate_ns    =   WAIT;endendRD_DDR:beginif (read_ddr_end) beginstate_ns    =   PROC_END;endelse beginstate_ns    =   RD_DDR;endendPROC_END:beginif (process_end) beginstate_ns    =   IDLE;endelse beginstate_ns    =   PROC_END;endenddefault:beginstate_ns        =   IDLE;endendcaseend//流程计数always @ (posedge ddr4_clk or negedge ddr4_rst_n) beginif(!ddr4_rst_n) beginprocess_start   <=  1'b0;endelse if (state_ns == IDLE) beginprocess_start   <=  c0_ddr4_init_complete_i & c0_ddr4_app_rdy_i & c0_ddr4_app_wdf_rdy_i;endelse beginprocess_start   <=  1'b0;endendalways @ (posedge ddr4_clk or negedge ddr4_rst_n) beginif(!ddr4_rst_n) beginwrite_ddr_cnt   <=  8'h0;endelse if ((state_ns == WR_DDR) && c0_ddr4_app_rdy_i && ddr4_app_en) beginwrite_ddr_cnt   <=  write_ddr_cnt + 8'h1;endelse if (state_ns == PROC_END) beginwrite_ddr_cnt   <=  8'h0;endendassign write_ddr_end = (write_ddr_cnt == (WR_LENGTH - 8'h1));always @ (posedge ddr4_clk or negedge ddr4_rst_n) beginif(!ddr4_rst_n) beginwait_ddr_cnt    <=  8'h0;endelse if (state_ns == WAIT) beginwait_ddr_cnt    <=  wait_ddr_cnt + 8'h1;endelse if (state_ns == PROC_END) beginwait_ddr_cnt    <=  8'h0;endendassign wait_ddr_end = (wait_ddr_cnt == 8'hF);always @ (posedge ddr4_clk or negedge ddr4_rst_n) beginif(!ddr4_rst_n) beginread_ddr_cnt    <=  8'h0;endelse if ((state_ns == RD_DDR) && c0_ddr4_app_rdy_i && ddr4_app_en) beginread_ddr_cnt    <=  read_ddr_cnt + 8'h1;endelse if (state_ns == PROC_END) beginread_ddr_cnt    <=  8'h0;endendassign read_ddr_end = (read_ddr_cnt == (WR_LENGTH - 8'h1));always @ (posedge ddr4_clk or negedge ddr4_rst_n) beginif(!ddr4_rst_n) beginprocess_cnt     <=  16'h0;endelse if (state_ns == PROC_END) beginprocess_cnt     <=  process_cnt + 16'h1;endelse if (state_ns == IDLE) beginprocess_cnt     <=  16'h0;endendassign process_end = (process_cnt == 16'hFF);//读写指令always @ (posedge ddr4_clk or negedge ddr4_rst_n) beginif(!ddr4_rst_n) beginddr4_app_en                 <=  1'b0;ddr4_app_cmd                <=  3'b000;endelse begincase(state_ns)WR_DDR:beginif (c0_ddr4_app_rdy_i) beginddr4_app_en     <=  1'b1;ddr4_app_cmd    <=  3'b000;endelse beginddr4_app_en     <=  1'b0;ddr4_app_cmd    <=  3'b000;endendRD_DDR:beginif (c0_ddr4_app_rdy_i) beginddr4_app_en     <=  1'b1;ddr4_app_cmd    <=  3'b001;endelse beginddr4_app_en     <=  1'b0;ddr4_app_cmd    <=  3'b001;endenddefault:beginddr4_app_en         <=  1'b0;ddr4_app_cmd        <=  3'b000;endendcaseendend//读写地址always @ (posedge ddr4_clk or negedge ddr4_rst_n) beginif(!ddr4_rst_n) beginddr4_app_addr               <=  28'h0;endelse begincase(state_ns)IDLE:beginddr4_app_addr       <=  28'h0;endWR_DDR:beginif (c0_ddr4_app_rdy_i && ddr4_app_en) beginddr4_app_addr   <=  ddr4_app_addr + 28'h8;     //地址对齐endelse beginddr4_app_addr   <=  ddr4_app_addr;endendWAIT:beginddr4_app_addr       <=  28'h0;endRD_DDR:beginif (c0_ddr4_app_rdy_i && ddr4_app_en) beginddr4_app_addr   <=  ddr4_app_addr + 28'h8;     //地址对齐endelse beginddr4_app_addr   <=  ddr4_app_addr;endendPROC_END:beginddr4_app_addr       <=  28'h0;enddefault:beginddr4_app_addr       <=  28'h0;endendcaseendend//写数据always @ (posedge ddr4_clk or negedge ddr4_rst_n) beginif(!ddr4_rst_n) beginddr4_app_wdf_wren           <=  1'b0;ddr4_app_wdf_end            <=  1'b0;ddr4_app_wdf_mask           <=  32'h0;endelse begincase(state_ns)WR_DDR:beginif (c0_ddr4_app_wdf_rdy_i) beginddr4_app_wdf_wren   <=  1'b1;ddr4_app_wdf_end    <=  1'b1;ddr4_app_wdf_mask   <=  32'h0;endelse beginddr4_app_wdf_wren   <=  1'b0;ddr4_app_wdf_end    <=  1'b0;ddr4_app_wdf_mask   <=  32'h0;end                enddefault:beginddr4_app_wdf_wren   <=  1'b0;ddr4_app_wdf_end    <=  1'b0;ddr4_app_wdf_mask   <=  32'h0;endendcaseendendalways @ (posedge ddr4_clk or negedge ddr4_rst_n) beginif(!ddr4_rst_n) beginddr4_app_wdf_data       <=  256'h0;endelse if (state_ns == IDLE) beginddr4_app_wdf_data       <=  {(64){4'b1}};endelse if((state_ns == WR_DDR) && c0_ddr4_app_wdf_rdy_i && ddr4_app_wdf_wren) beginddr4_app_wdf_data       <=  ddr4_app_wdf_data + {(64){4'b1}};endelse if (state_ns == WAIT) beginddr4_app_wdf_data       <=  256'h0;endend

参考

  1. ^Xilinx手册PG150的第124页
  2. ^"MT40A256M16-075E" datasheet 第2页 MT40A256M16GE-075E AUT | Micron Technologies, Inc
  3. ^Xilinx手册PG150的第133页、第136页
  4. ^Xilinx手册PG150的第20页
  5. ^Xilinx手册PG150的第133页

这篇关于DDR4读写测试(二):基本读写测试的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/635581

相关文章

Python中多线程和多进程的基本用法详解

《Python中多线程和多进程的基本用法详解》这篇文章介绍了Python中多线程和多进程的相关知识,包括并发编程的优势,多线程和多进程的概念、适用场景、示例代码,线程池和进程池的使用,以及如何选择合适... 目录引言一、并发编程的主要优势二、python的多线程(Threading)1. 什么是多线程?2.

SpringBoot中整合RabbitMQ(测试+部署上线最新完整)的过程

《SpringBoot中整合RabbitMQ(测试+部署上线最新完整)的过程》本文详细介绍了如何在虚拟机和宝塔面板中安装RabbitMQ,并使用Java代码实现消息的发送和接收,通过异步通讯,可以优化... 目录一、RabbitMQ安装二、启动RabbitMQ三、javascript编写Java代码1、引入

Nginx设置连接超时并进行测试的方法步骤

《Nginx设置连接超时并进行测试的方法步骤》在高并发场景下,如果客户端与服务器的连接长时间未响应,会占用大量的系统资源,影响其他正常请求的处理效率,为了解决这个问题,可以通过设置Nginx的连接... 目录设置连接超时目的操作步骤测试连接超时测试方法:总结:设置连接超时目的设置客户端与服务器之间的连接

MyBatis-Flex BaseMapper的接口基本用法小结

《MyBatis-FlexBaseMapper的接口基本用法小结》本文主要介绍了MyBatis-FlexBaseMapper的接口基本用法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具... 目录MyBATis-Flex简单介绍特性基础方法INSERT① insert② insertSelec

JAVA调用Deepseek的api完成基本对话简单代码示例

《JAVA调用Deepseek的api完成基本对话简单代码示例》:本文主要介绍JAVA调用Deepseek的api完成基本对话的相关资料,文中详细讲解了如何获取DeepSeekAPI密钥、添加H... 获取API密钥首先,从DeepSeek平台获取API密钥,用于身份验证。添加HTTP客户端依赖使用Jav

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

Python实现高效地读写大型文件

《Python实现高效地读写大型文件》Python如何读写的是大型文件,有没有什么方法来提高效率呢,这篇文章就来和大家聊聊如何在Python中高效地读写大型文件,需要的可以了解下... 目录一、逐行读取大型文件二、分块读取大型文件三、使用 mmap 模块进行内存映射文件操作(适用于大文件)四、使用 pand

C# 读写ini文件操作实现

《C#读写ini文件操作实现》本文主要介绍了C#读写ini文件操作实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录一、INI文件结构二、读取INI文件中的数据在C#应用程序中,常将INI文件作为配置文件,用于存储应用程序的

使用Python进行文件读写操作的基本方法

《使用Python进行文件读写操作的基本方法》今天的内容来介绍Python中进行文件读写操作的方法,这在学习Python时是必不可少的技术点,希望可以帮助到正在学习python的小伙伴,以下是Pyth... 目录一、文件读取:二、文件写入:三、文件追加:四、文件读写的二进制模式:五、使用 json 模块读写

C#实现文件读写到SQLite数据库

《C#实现文件读写到SQLite数据库》这篇文章主要为大家详细介绍了使用C#将文件读写到SQLite数据库的几种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下... 目录1. 使用 BLOB 存储文件2. 存储文件路径3. 分块存储文件《文件读写到SQLite数据库China编程的方法》博客中,介绍了文