FPGA的片上存储资源bram简单好用,时序清晰,要不是总容量往往就几十Mb谁愿意用DDR呀······
害,言归正传,因为设计需要存储1477x1800x3 双精度浮点复数这样的大号矩阵,所以只能放到DDR上去进行读写。之前在网上找了好多资料,但发现都没有一个很完整的教程教你怎么使用DDR控制器IP核MIG(Memory Interface Generator),所以写了这篇文章主要希望能帮初学者快速上手MIG的使用以实现DDR读写。
介绍MIG之前,我觉得有必要先对DDR做一个介绍,DDR SDRAM(Double Data Rate Synchronous Dynamic Random Access Memory,实际上还分为DDR SDRAM,DDR2 SDRAM,DDR3 SDRAM,DDR4 SDRAM,主要是数据预取prefetch和工作频率的不同,感兴趣的大家可以自己查),搭眼一看,这玩意本质上不就是数字集成电路里学的DRAM嘛(电容存储,会漏电,时不时需要刷新blablabla······),而double data rate说的是他在clock的上升沿和下降沿都会进行数据读写,设想如果用户逻辑侧的时钟频率和DDR的工作频率之比为1:4的话,用户侧的一个clk, 那么DDR实际上进行了4*2(上下沿)=8次读写操作。
DDR3的内部是如上图的存储阵列组成,数据存储在单元格中,在检索你想要的数据时,先指定一个行(Row),再指定一个列(Column),我们就可以准确地找到所需要的单元格,这就是内存芯片寻址的基本原理。对于内存,这个单元格可称为存储单元,常见存储单元位宽有4bit,8bit,16bit,那么这个表格(存储阵列)就是逻辑 Bank(Logical Bank),而一颗DDR芯片上有若干个bank,一般内存芯片厂家在芯片上是标明容量的,我们可以看芯片上的标识知道,这个芯片有几个逻辑BANK,每个逻辑bank的位宽是多少,每个逻辑BANK内有多少单元格(CELL),eg.比如64MB和128MB内存条常用的**64Mbit的芯片(注意不是Mb,1byte=8bit,所以除以8才是Mb)**就有如下三种结构形式:
①16 M x 4 (4 M x 4 x 4 banks) [16M X 4]
②8 M x 8 (2 M x 8 x 4 banks) [8M X 8]
③4 M x 16 (1 M x 16 x 4 banks) [4M X 16]
即一颗DDR芯片的单元格数(逻辑bank数 X 每个逻辑bank的单元格数)X 每个单元格的位宽(bit)。
16bit位宽的芯片意味着你给内存一个地址,内存会给你一个16bit的数据到数据线上,但实际中我们的数据往往不止16bit,例如双精度浮点数(64bit),那么就需要用4块DDR芯片拼接成一个64位宽的DDR,只是这4块DDR共用一个地址线,每个取出的16bit数据再拼成一个64bit的数据,那么就相当于每个地址可以存一个64位宽的数据,而这样一个64位宽的数据集合就是一个物理bank,也经常叫Rank。大家可以参考这篇文章说的很详细:
64bit数据存储形式
接下来说说MIG核是干什么的,放一张DDR工作的状态图:
DDR上电后需要控制其初始化,ZQ校准,激活bank,而后读写等一系列过程,这其中还要配置模式寄存器,控制DDR refresh、precharge,考虑怎么样将8/16bit位宽的DDR芯片拼接成更大位宽等等问题。这对于用户侧是十分不友好的。所以就需要DDR控制器提供一个用户友好的接口,而MIG核做的就是这件事情
左边是用户侧读写逻辑,经由MIG核生成DDR需要的信号控制读写。所以,我们只需要把关注点放在MIG核的用户侧接口信号,从官方文档里可以找到接口说明。
需要重点关注的信号用红框标注出来了,根据英文也能猜出大概是干嘛的,接下来就可以生成IP核了,这里不得不说一下大家FPGA最好选用Xilinx的亲儿子,之前用的一款adm-pcie 7v3,参考资料少,问题多,而且DDR的管脚还要自己去配,属实折腾人······这次用的是ultrascale+系列的亲儿子kcu116,简直不要太舒服哈哈。
这里说一下系统时钟,因为我的板子时钟源有一个300MHz的差分时钟,所以就刚好用它作为MIG的输入时钟,实际上MIG会利用分频倍频将这个差分时钟变成我们想要的DDR工作频率,第二张图里的memory interface speed指的就是DDR的工作频率,而frequency ratio指的就是DDR工作频率:用户逻辑频率,再加上上下沿传输,意味着用户侧一个clk可以给DDR读写8个数据。reference input就是刚才所说的外接时钟源。底下的data_width指的是我们要存的数据位宽,例如双精度浮点数64,这时候我们可以发现无论是app_rd_data还是app_wdf_data都变成了512位,,刚好和64是8:1的关系,也就是我们所说的用户侧一个clk可以读写8个数据,所以我们在写自己的读写逻辑时,如果是连续地址读写,那么每次的地址数据应该+8。CAS latency指的是DDR在读取时,列地址选中后数据传输放大到I/O口这个过程本身就有的一个延时,相当于我们给了读地址后到得到读数据的延时,这涉及到DDR的工作原理这里不作深究我们可以随便设个值。
这里说一下memory address map,大家注意到有row-bank-colum,row-colum-bank,bank-row-column几种数据地址映射机制,就是我们在进行存储时,是按照先填写column,第一行的column写满后跳到下一个bank的第一行去存储,8个bank的第一行都填满后,再跳转到下一行存储,其它几种同理。进一步深究MIG核的代码发现一个有趣的事情,例如row-column-bank的地址映射机制, 这里的addr并不是单纯的row-column-bank的排列顺序,不由得想到一个问题,我们在设计自己的逻辑时,一个app_rd_data或者一个app_wdf_data要占用8个地址所以每次地址都是+8,即+4’b1000,相当于刚好给bank+1从而实现了bank 的跳转,因此Xilinx官方已经帮我们考虑了这些问题我们可以只管给addr+4’b1000其他的不用操心。
处理完这些就可以点击完成生成IP核。等IP核综合完成,这个时候推荐大家直接右键生成example design,因为你只是生成例化了控制器,但还没有和DDR芯片连接,生成example design便可以直接开始设计自己的读写逻辑。打开example design之后可以看到代码结构,只需要在u_example_tb.v里修改即可。
接下来就是读写时序的设计了。
写命令与写地址
如上图所示①,②,③情况,只有在③时刻app_en和app_rdy同时为高电平app_cmd(命令)和(app_addr)地址才有效,所以当需要app_cmd,app_addr有效时app_en必须保持到app_rdy为高电平才有效。
写时序
如上图所示①,②,③种情况,写命令和写数据直接存在三种逻辑关系。
1、①表示写命令(app_cmd),写当前地址(app_addr)和写数据(app_wdf_data)以及写控制信号(app_en,app_rdy,app_wdf_rdy,app_wdf_wr