本文转载自: FPGA的现今未微信公众号
注:本文由作者授权转发,如需转载请联系作者本人
RAM是FPGA设计中使用非常广泛的一个组件,几乎每一个项目中都会用到。所以用好RAM也是FPGA设计的基本技能。本文主要从工程角度出发,阐述RAM在工程中的实际问题。
1、什么是RAM
RAM(random access memory),随机存储器,最基本的功能就是通过控制读写地址来实现RAM中存放数据的输出和写入。
2、RAM的特性
说明:本文中所指的RAM都是指FPGA中的RAM,且所举例均以Xilinx为例,Altera类似。
要利用好FPGA中的RAM,首先要了解RAM的一些属性,这是如何用好RAM的前提。本节将从RAM的分类、接口时序、配置、工作频率、读写冲突以及RAM在FPGA中的位置等方面说明。
(1)、RAM的分类
从RAM的组成资源来讲,RAM分成DRAM、BRAM、URAM。
DRAM:分布式ram,ram主要是由LUT和REG组成,适用于少量数据的存放,比如2Kbit(具体需要根据项目的实际情况而定)以下。
BRAM:就是我们常说的Block RAM,它是由FPGA中的硬core资源组成,RAMB18E2或者RAMB36E2,这种RAM是用的最多的一种。最主要的好处的是用了FPGA内部的硬core资源,性能相对好,也不会占用太多的LUT和REG。
URAM:这是xilinx高端器件中新增加的一种RAM,和BRAM类似,不同的是它的单位容量更大(288kbit),适合做更大的缓存。
从端口来讲,RAM又成为:
signal port ram:通过一个口来实现读和写;
simple dual port ram:简单双口ram,即一个口只读,一个口只写;
true dual port ram:真双口ram,有2个读口和2个写口;
从实际应用来讲,用的最多的还是简单双口ram,主要是用于数据的缓存,真双口ram在一写多读的表项设计中,使用相对会较多。
(2)、接口时序
RAM的接口时序主要是Native和AXI,其中Native类型的接口在FPGA的内部设计中用的比较多,接口相对简单,这里主要说明用的比较多的Native接口。
写接口时序如下图所示:
主要的信号有写使能,写地址和写数据。数据a写入地址00、数据a1写入地址01……不同的配置下可能有其他的接口信号,但时了解这3个信号基本上就足够应用大部分的场景了。
在介绍读接口的时序前,先介绍一个基本的工程经验:为了提升性能,或者说提升FPGA工作的主频,FPGA RAM中输出的数据,在使用之前一定要用寄存器输出。从RAM中出来直接使用的话,组合逻辑会比较大,影响频率。那如何做寄存器输出呢?有2种方式:
方式A:IP catalog例化RAM的时候选择,如下图所示,有2个选择:
这2个选择是什么意思了?那需要了解下RAM的基本结构,xilinx官网提供的资料PG058有说明,如下图所示:
该图来自xilinx官网资料PG058
Block Memory本身带有2个寄存器,其中左边红色的寄存器位于Block RAM Primitives内部,右边的寄存器位于MUX选择之后。这2个寄存器分别对应IP catalog中的2个选择。加了寄存器有什么好处?这个后面说。这里需要说明的是,每加一个寄存器,输出的数据就要延迟一个时钟周期。如果2个寄存器都不加,BRAM的读接口时序如下图所示,其中a来之地址00,a1来之地址01……
方式B:代码中直接对从RAM输出的数据打1拍或者2拍以后再使用,在接口时序上和方式A是一样的。
方式A和方式B,那种好?一般官方建议是采用方式A,但是作者在实际项目中采用B方式比较多,主要是时序清晰可控,效果也不错。
(3)、配置
在使用RAM之前,我们需要有一个概念,就是这个RAM的深度是多少?位宽是多少bit?这里只讨论读写位宽相同的情况。为什么呢?因为不同的宽度&深度的配置所需要的硬件资源是不一样的,在实际工程中,我们要寻找解决问题所需消耗的最小资源是多少。
在Xilinx的FPGA中,有RAM的基本单元为M18K和M36K,单位是bit,一个M36K包含了2个M18K。我们以M18K为例,如下表所示:
出自xilinx官方文档PG058
换句话说,你需要的任何位宽&深度的RAM都是以上几种配置组合而成的。比如我需要一个宽度=4bit,深度=4K的ram,那么这个RAM需要一个M18K(不是M16K,因为带有ECC的功能),比如位宽=36bit,深度=512,也是需要消耗一个M18K。
在实际应用中,需要根据具体的设计,利用最小的资源灵活的把上述的各种规格拼接起来,来满足你的设计要求。如果自己实在搞不清楚需要多少RAM的时候,在用IP catalog例化RAM的时候,可以查看,比如16bit&3K深度的RAM,就需要3个M18K。
(4)、工作频率
这里说的工作频率是指设计中的RAM可以工作的时钟频率。我们知道一个系统的工作时钟频率取决于2个寄存器之间最长的逻辑时延。RAM内部本身是不包括寄存器的,所以工程上建议:1、写入RAM的写使能、写地址和写数据都用寄存器打一拍后再送到RAM的内部;2、读RAM的时候,读出的数据也要打一拍(具体如何打拍,参考(2)接口时序部分说明)以后再使用。也就是说,保证进入RAM的信号是寄存器输出,从RAM中输出的信号通过寄存器输出后再使用。
这一建议对于大多数的工程都是可以满足时序要求的。有的时候我们的工程频率不高,比如125M,器件也还行,RAM的输入和输出不采用寄存器输出,也不是完全不可以,具体问题需要具体分析。对于高频的工程,比如300M时钟以上,除了严格满足上述的建议外,最好还考虑下逻辑的布局。
(5)、读写冲突
读写冲突是指RAM在同一时刻同时读写同一个地址,那会存在一个问题,读出的值是新写入的值还是原来存放在RAM中的值了?供应商的RAM是考虑了读写冲突情况,比如例化RAM的时候会让你选择哪种模式:
如果出现了读写冲突,选择如何告警。
但是不提倡在使用RAM的时候出现读写冲突的情况。实际工程中,我们在使用一块RAM的时候,要从设计上做到先写后读,比如RAM作为缓存的时候,一定是先写入数据后,再读出数据。如果无法做到先读后写,比如表项的查询,那么我们要从设计上保证最终读出的数据是该地址写入的最新数据。具体的项目需要具体分析,总之我们尽量从设计上去规避读写冲突的出现。
(6)、RAM中FPGA中的位置
为什么要知道RAM在FPGA中的位置了?主要还是为了更好的利用RAM,先看看RAM在FPGA中的位置,如下图所示:
红色框中就是RAM,RAM在FPGA内部呈“条”状,在芯片内部均衡分部,我们知道这个以后有2个好处:
第一:因为RAM的位置固定,从架构上限制了一些场景 ,比如一次要使用几十个上百个RAM的设计,可能就不是一个很好的设计,因为一次使用太多的RAM的逻辑占用的芯片面积太大,就不方便布局布线,那么时序也会相对较差。
第二:RAM不能全部用完,在资源评估的时候要预留一部分,预留多少,不同的芯片也会有不同,建议不超过80%,理由同上。
3、RAM的使用场景
场景1:表项
某设计输入的数据带一个8bit的user_id,指示该数据属于哪一个user,现在要求统计每个user收到的数据的个数,统计到1024*1024*1024即可,超过了统计计数器的最大值的话,可以翻转。
简单粗暴的方法,定义256个32bit的计数器,来一个数据后,先看user_id是多少,然后相应的计数器加1。如果user_id的不是8bit,是12bit,支持4K个user_id了?那这个方法可能就不好用了。即使是采用数组的方式,那也需要4K*30bit = 120K的reg,太浪费资源。
如下图所示,可以采用RAM来存放各个user_id的计数值,用user_id作为ram的地址,将各个user_id对应的计数值写入到RAM对应的地址中。每来一个数据,根据该数据的user_id读取ram中的值,然后将该值加1后再写回ram中。
场景2:缓存buffer
在报文处理中经常也会用ram来作为缓存。比如在报文的结尾处用1个bit指示该报文是一个正常的报文还是有错误的报文。如果是正常的报文,就写入缓存中然后发送给后级模块,如果是错误的报文,需要丢弃。
如上图所示:假如一个报文写入的地址空间是从地址1——地址100,如果是一个正常的报文,那么读逻辑就从地址1开始读取报文后发送给后级模块。如果不是一个正确的报文,常见的做法就是,将写地址的值从101跳回到1,即下次来一个报文后,把原来的报文覆盖,从而实现对错误报文的丢弃。
当然,使用ram的地方还有很多很多,上面的2个例子,只是最基本的场景。了解前面ram的基本特性后,那么在其他场景下使用ram也必然游刃有余。
4、RAM使用的注意事项
(1)、性能
前面已经详细的说明了影响性能的相关因素,这里再列出来想强调的是,RAM的输入输出一定要通过寄存器,记住这点,性能上不会有太大问题。
(2)、资源
实际在项目中,我们需要弄清楚2个问题,方案设计上需要多大的buffer?这么大的buffer需要多少块RAM?后一个问题前面已经详细说明了,前一个问题就只需要注意一点:别溢出了。写的时候控制好地址和使能信号,保证不翻转,读的时候先写后读。
(3)、校验
FPGA中的RAM存在一定的软失效或者器件故障(reg和LUT也一样),对于可靠性要求不严格的场景,不需要对RAM中的数据进行校验。而对可靠性要求非常高的场景,就需要校验了。
常见的校验有2种,奇偶校验或者ECC,根据相关研究,如果出现失效,大概率(好像是90%以上)是1bit错误。对于这种错误,奇偶和ECC校验都可行。通常的做法是在数据将要写入RAM之前,先计算奇偶校验位或者ECC校验位,和原始数据一起写入RAM中,读数据的时候,再对读出的数据做检验,如果出现错误,上告系统软件,让上层业务决定如何处理。另外,当前供应商的IP本身已经支持ECC校验。
(4)、RAM的复位和初始化
首先要明确一点,RAM中存放的内容是不会复位的。我们在例化RAM的时候,输入的复位信号,仅仅是复位RAM中的输出寄存器。那这就会带来一个问题:系统运行一段时间后复位的话,RAM的值并不是我们想要的初始值。怎么办?可以分2种情况考虑:
RAM做为表项:可以在复位后,先自动对RAM做一次初始化操作(即先写一遍RAM),使得RAM中的值变成我们想要的初始值。
RAM作为缓存:要从方案上保证对RAM的操作是先写后读,那就不会有上述的问题了。如果确实无法保证,也可以参考上述对RAM初始化操作一次的方案。
以上介绍的都是关于的RAM的一些工程实践基础,更深入的信息可以参考xilinx的官方文档,ug573:ultrascale-memory-resources。文章中有不对的地方,也欢迎留言一起交流讨论。