从底层结构开始学习FPGA----FIFO IP核及其关键参数介绍

本文转载自:孤独的单刀的CSDN博客

注:本文由作者授权转发,如需转载请联系作者本人

此文仅仅对xilinx FIFO IP的参数做了详细解读,关于IP核的定制与使用方法请移步:从底层结构开始学习FPGA----FIFO IP的定制与测试

1、FIFO简介
FIFO 的全称是 First In First Out,即先进先出,指的是对数据的存储具有先进先出特性的一个缓存器。FIFO与RAM 和 ROM 的区别是没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,使用起来简单方便,由此带来的缺点就是不能像 RAM 和 ROM 那样可以由地址线决定读取或写入某个指定的地址。

FIFO常被用于数据的缓存,或者高速异步数据的交互也即所谓的跨时钟域信号传递,比如 DDR 的数据读写。又或者不同位宽数据的转换,比如写入16位宽的数据,再读取8位宽的数据。

根据读、写操作是否使用同一个时钟可以把FIFO分为两类:

同步FIFO:读写时钟是同一个时钟,在时钟沿来临时同时发生读写操作。常用于同步时钟下的数据缓存。
异步FIFO:读写时钟不一样,读写时钟相互独立。常用于不同时钟域下的数据传输,因为时钟不同的话,在数据采集的时候可能会出现亚稳态的情况,导致数据传输错误,使用异步 FIFO 能够将不同时钟域中的数据同步到所需的时钟域中,使得数据传输稳定。

异步FIFO由于使用的异步时钟,所以其内部被划分为了两个时钟域--读时钟域和写时钟域。而同步FIFO的话,则只有一个时钟信号,所以其IP核内部只有一个时钟域。

2、Xilinx FIFO IP核
xilinx 的vivado提供了免费的IP----FIFO Generate供我们使用,如下:

2.1、接口
FIFO IP核提供了两种对外接口:

Native接口:即原始的默认接口,包括读使能、写使能,输入、输出,空、满等一系列的信号

AXI4接口: AXI4接口其实是Native接口的再封装,可以实现AXI4,AXI3,AXI4-Lite和AXI4-Streaming。下图可以看到,AXI4接口的FIFO除了数据的写入、读取是采用的经典AXI4握手协议外,像空、满等信号就是直接对外输出的。关于AXI4接口,我们这里不展开说,后面有机会再更新。

2.2、实现方式
FPGA内部资源丰富,所以FIFO IP核的实现也可以采用不同的资源形式:

Block RAM:BRAM资源,这是FPGA底层内嵌的存储资源,不管可以实现FIFO,同时还可以实现RAM、ROM
Distributed RAM:分布式RAM,本质是底层逻辑资源LUT,
Shift Register:移位寄存器,本质仍是底层逻辑资源LUT,
Built-in FIFO:嵌入式FIFO,相当于BRAM资源外部封装了FIFO相关的接口

虽然可以使用不同的资源来实现FIFO给我们提供了很大的灵活性,但是缺点也是存在的----由于实现方式的不同,不同类型组成的FIFO可以实现的功能也不一致。

比如下表(来自FIFO IP核定制界面)中的Built-in FIF资源组成的异步FIFO,可以实2、3、4、5,但是无法实现功能1----不同的读、写数据位宽。

2.3、FIFO IP核性能
FIFO IP核的性能我们一般直接量化为其可以运行的最高频率,根据实现资源的不同和使用FPGA器件的不同,其性能差异很大。

限于篇幅,我们这里不举例了,有兴趣可以请查阅《pg057-fifo-generator》第2章的Performance。基本的规律就是,Virtex-7器件的FIFO>Kintex-7器件的FIFO>Artix-7器件的FIFO。

2.4、写操作
要实现FIFO的正确写操作就需要了解与写入操作有关的信号和写入的时序。以下是与写操作相关的信号。

wr_clk:写时钟信号
wr_en:写使能信号,高电平有效
din:要写入FIFO的数据
almost_full:几乎满信号,高电平有效,有效时表示FIFO只能写入最后一个数据了
full:满信号,高电平有效,有效时表示FIFO已满,不能再写入数据了
wr_ack:写响应信号,可选可不选,可配置高电平或低电平有效;有效时表示进行了一次成功的写FIFO操作
overflow:上溢信号,可选可不选,可配置高电平或低电平有效;有效时表示在FIFO处于已满状态时进行了一次写操作

在了解写时序之前,有必要说明的一点是:数据的改变不是瞬时的,而是需要一定的时间,这个时间就是Tco。比如下图中:

在周期①,已经将FIFO写满了,所以full信号会变为1,由于Tco的存在,full信号的变化不是瞬时的,而是成斜坡状,这种需要时间的从0到1后续我个人把它称为拉高。
在周期②,已经将FIFO写满了,所以wr_alkc信号不会再响应,会变为0,由于Tco的存在,wr_alkc信号的变化不是瞬时的,而是成斜坡状,这种需要时间的从1到0后续我个人把它称为拉低。

了解了这两点后,我们再来看下写操作的时序图: (绿色表示写入成功,红色表示写入失败)

①:写使能wr_en为0,输入din无数据
②:写使能wr_en为1,输入din为D1;wr_ack被拉高,表示写入数据D1成功
③:写使能wr_en为1,输入din为D2;wr_ack为1,表示写入数据D2成功;almost_full拉高,表示FIFO几乎满(还可以写一个数据)
④:写使能wr_en为1,输入din为D3;wr_ack为1,表示写入数据D3成功;almost_full为1,表示FIFO几乎满;full拉高,表示FIFO已满,不能在写数据了
⑤:写使能wr_en为1,输入din为D4;wr_ack被拉低,表示写入数据D4不成功;almost_full为1,表示FIFO几乎满;full为1,表示FIFO已满;overflow被拉高,表示出现了写满(在FIFO满状态下进行写操作)操作
⑥:写使能wr_en为1,输入din为D5;wr_ack为0,表示写入数据D5不成功;almost_full为1,表示FIFO几乎满;full为1,表示FIFO已满;overflow为1,表示出现了写满(在FIFO满状态下进行写操作)操作
后续:对FIFO进行读操作后,重复进行写操作

2.5、读操作
要实现FIFO的正确读操作就需要了解与读取操作有关的信号和读取的时序。以下是与读操作相关的信号。

rd_clk:读时钟信号
rd_en:读使能信号,高电平有效
dout:从FIFO中读出的数据
almost_empty:几乎空信号,高电平有效,有效时表示FIFO只能读出最后一个数据了
empty:空信号,高电平有效,有效时表示FIFO已空,不能再读取数据了
valid:
标准模式:有效信号,可选可不选,可配置高电平或低电平有效;有效时表示此时读取的数据dout有效,当进行了一次无效的读取操作(空状态下读)时,此信号会被拉低
前显模式:有效信号,可选可不选,可配置高电平或低电平有效;有效时表示此时读取的数据dout有效,只要FIFO中还有至少一个数据可读时,valid就是有效的;当FIFO中没有数据可读后会被拉低
underflow:下溢信号,可选可不选,可配置高电平或低电平有效;有效时表示在FIFO处于已空状态时进行了一次读操作
读操作比较特殊,分为了两种模式:标准模式(Standard)和前显模式(First-Word Fall-Through,FWFT)。

标准模式是默认模式,发出一次读指令,在一定的延迟后才会读到第一个数据。而前显模式则是在发出读取指令之前,就会把下一个会被读出的数据放到输出总线上。

前显模式看着好像很好,数据的输出没什么延迟,但是我建议你不要使用前显模式,因为前显模式的特殊性,导致其很多地方都会很奇怪,很违反直觉。比如前显模式的实际深度会比设定的要多,数据计数的准确性也会变差。

(1)标准模式(绿色表示读取成功,红色表示读取失败)

①:读使能rd_en为0,输出dout无数据
②:读使能rd_en为1,输出dout数据D0;valid被拉高,表示dout的数据是有效数据
③:读使能rd_en为1,输出dout数据D1;valid为1,表示dout的数据是有效数据;almost_empty被拉高,表示FIFO几乎空,仅剩一个数据
④:读使能rd_en为1,输出dout数据D2;valid为1,表示dout的数据是有效数据;almost_empty为1,表示FIFO几乎空;empty被拉高,表示FIFO空,所有数据都被读完了
⑤:读使能rd_en为1,输出dout无数据;valid被拉低,表示dout的数据是无效数据;almost_empty为1,表示FIFO几乎空;empty为1,表示FIFO空;underflow被拉高,表示在空状态下对FIFO进行了读取操作
后续:对FIFO进行写操作后,重复进行读操作

(2)前显模式 (绿色表示读取成功,红色表示读取失败)

①:读使能rd_en为0,输出dout数据D0;valid为1,表示dout的数据是有效数据
②:读使能rd_en为0,输出dout数据D0;valid为1,表示dout的数据是有效数据
③:读使能rd_en为1,输出dout数据D1;valid为1,表示dout的数据是有效数据
④:读使能rd_en为1,输出dout数据D2;valid为1,表示dout的数据是有效数据
⑤:读使能rd_en为1,输出dout数据D3;valid为1,表示dout的数据是有效数据;almost_empty被拉高,表示FIFO几乎空,只有最后一个数据了
⑥:读使能rd_en为1,输出dout无数据;valid被拉低,表示dout的数据是无效数据;almost_empty为1,表示FIFO几乎空;empty被拉高,表示FIFO空,一个数据都没有了
⑦:读使能rd_en为1,输出dout无数据;valid为0,表示dout的数据是无效数据;almost_empty为1,表示FIFO几乎空;empty位1,表示FIFO空;underflow被拉高,表示在FIFO空状态下进行了读操作
后续:对FIFO进行写操作后,重复进行读操作

需要注意的是,不同于标准模式,前显模式的空信号会落后最后一个数据一个时钟周期(在标准模式,周期④读取了最后一个数据,同时空信号也被拉高;而在前显模式,周期⑤读取了最后一个数据,但是空信号却在周期⑥被拉高,落后了一个时钟周期)。

正由于前显模式的空信号时序的特殊性,使得其实际深度会比设计深度多2。

最后需要注意的是,前显模式的empty信号会在写入第一个数据的后两个时钟周期才会被拉低。

2.6、可编程信号
在读、写操作章节我们了解了几乎空、几乎满、空、满这四个指示FIFO中数据量的信号,但是在实际的应用中,有时候我们也需要在一些特定的时候获取FIFO的信息。比如我设计了一个深度为100的FIFO,希望能在FIFO中的数据量达到10个后就开始输出到后级,而在其数据量不满10时,希望不要对FIFO进行读取操作。这个时候FIFO的可编程空(满)信号就派上用场了。

可编程满 (prog_full):可用来指示当前FIFO已经到达用户设定的可编程满阈值。例如设定可编程满阈值为80,FIFO深度为100,则当FIFO中数据大于或等于80个后,prog_full信号会被拉高,直到FIFO中数据数量少于80才会拉低。
可编程空 (prog_empty) :可用来指示当前FIFO已经到达用户设定的可编程空阈值。例如设定可编程空阈值为20,FIFO深度为100,则当FIFO中数据小于或等于20个后,prog_empty信号会被拉高,直到FIFO中数据数量大于20才会拉低。
当FIFO中的数据量到达可编程的阈值时,仍可以对FIFO继续读写,这些信号仅仅是作为一个指示信号来指示用户在某些特定条件下对FIFO进行操作。

注意:prog_full的置位会有1个时钟周期的延迟,而prog_full的释放则延迟更长,具体取决于读写时钟关系。

2.6.1、可编程满信号Programmable Full
可编程满信号Programmable Full 提供了4类设置模式供我们使用:

①:Single threshold constant:
用户直接在IP核生成界面指定一个阈值,一旦指定则不可更改,除非重新生成FIFO IP核。这种方法相对于②,资源消耗更少,当灵活性较差。
② :Single threshold with dedicated input port:
用户通过prog_full_thresh信号来指定一个阈值,这个阈值可以在FIFO复位时更改。这种方法相对于①,资源消耗更多,但是获得了更大的灵活性。
③ :Assert and negate threshold constants (provides hysteresis)
用户直接在IP核生成界面指定2个阈值(一个置位,另一个失效),一旦指定则不可更改,除非重新生成FIFO IP核。这种方法相对于④,资源消耗更少,当灵活性较差。
④ :Assert and negate thresholds with dedicated input ports (provides hysteresis)
用户通过prog_full_thresh_assert信号来指定置位阈值,通过prog_full_thresh_negate信号来指定失效阈值。这2个阈值可以在FIFO复位时更改。这种方法相对于③,资源消耗更多,但是获得了更大的灵活性。

下图是仅仅设定了单个可编程空阈值的时序图:

①:在FIFO数据量为6时,实际数据量达到阈值7,可编程满信号prog_full被拉高;其后FIFO数据一直大于等于阈值7,所以prog_full持续为1
②:FIFO数据小于阈值7,可编程满信号prog_full被拉低
注意:wr_data_count计数量有一个周期的延迟,当其为6时,FIFO中的实际数据量为7,所以为6时prog_full就会被拉高。后续此条不赘述

下图是同时设定了可编程空置位阈值和失效阈值的时序图:

①:在FIFO数据量为9时,达到置位阈值10,可编程满信号prog_full被拉高;
②:FIFO数据小于失效阈值7,可编程满信号prog_full被拉低
注意:满置位值一定要大于满失效值。

2.6.2、可编程空信号Programmable Empty
可编程空信号Programmable Empty的使用方法基本与可编程满信号一致,所以不详细介绍了,只看两张时序图:

①:在周期①之前,FIFO数据量为4,等于可编程空的阈值4,所以prog_empty一直为高。到周期①FIFO中数量从4变为5,不在满足可编程空条件,prog_empty被拉低
②:当前FIFO中数据量为5,且进行了一次读操作,所以触及可编程空的阈值4,prog_empty被拉高

①:在周期①之前,FIFO数据量大于置位阈值7,所以prog_empty一直为高。到周期①FIFO数据量变为11,满足失效阈值10,所以prog_empty被拉低
②:当前FIFO中数据量为8,且进行了一次读操作,所以触及可编程空的置位阈值7,prog_empty被拉高
注意:空置位值一定要小于空失效值。

2.7、数据计数
在某些应用场景下,我们可能希望可以实时追踪FIFO中的数据个数。需要注意的是:

同步FIFO的数据计数是准确的,应该读写时钟相同,不需要做跨时钟同步。
异步FIFO的计数值是不够准确的,这是FIFO的实现方法决定的,因为读写时钟域下的数据计数同步到对方时钟域需要一定的时间。所以异步FIFO的计数不建议精确使用,只可粗略进行判断,例如半空、半满、1/4空、1/4满等。

尽管异步时钟域下的数据计数是不准确的,但却不会对FIFO功能造成影响。因为读计数和写计数都是一种保守的计数,这样做的目的是为了防止出现读空和写满,从而引发读写错误。

写时钟的数据计数会保守地报告写入FIFO中的数据量,不会小于实际数据量,就是说这个数据可能会比实际的大,以保证用户不会对FIFO进行写空操作。比如说FIFO深度100,写时钟计数值为100,这个时候用户就会认为FIFO已满从而停止对FIFO进行写操作。而实际数据量可能是98,尽管最后2个空间不会被使用,损失了2%的性能,但并不会影响FIFO的功能。所以说写计数是保守的。

读时钟的数据计数会保守地报告读出FIFO中的数据量,不会大于实际数据量,就是说这个数据可能会比实际的小,以保证用户不会对FIFO进行读空操作。比如说FIFO深度100,读时钟计数值为0,这个时候用户就会认为FIFO已空从而停止对FIFO进行读操作。而实际数据量可能是2,尽管最后2个空间不会被使用,损失了2%的性能,但并不会影响FIFO的功能。所以说读计数是保守的。

关于异步FIFO这部分的设计原理可以参考:关于异步FIFO设计,这7点你必须要搞清楚

2.8、非对称读写位宽
转换读写位宽也是FIFO具备的一个重要的功能,我们可以一次写入16bit的数据,而一次只读出4bit的数据。读、写数据的位宽可以不同,但是必须满足比例关系:1:8, 1:4, 1:2, 1:1, 2:1, 4:1, 8:1。

在非对称的读写位宽情况下,一个必须要搞清楚的事就是数据的排序问题。

(1)写位宽 > 读位宽

如果写入位宽8,读取位宽2,当我写入的数据是00011011,那么第一个读出来的数据是11还是00?

下图展示了写入8bit,读取2bit的数据排序情况,结果是读取是从高为MSB到低位LSB的,写入11000111,分4次读出了11--00--01--11。

而空信号的行为也是与整个FIFO的数据量挂钩的。如下,写入8bit数据后,empty即失效,表示当前FIFO不空。当分4次,一次读取2bit,共读出8bit数据后,FIFO又恢复了空状态。

(2)写位宽 < 读位宽

同样的,如果写入位宽2,读取位宽8,当我分四次写入的数据分别是01 00 11 10,那么第一个读出来的数据是01001110还是10110001?

下图展示了写入2bit,读取8bit的数据排序情况,结果是第一次写入的数据01会在读取的最高位MSB,而第4次写入的数据10则会在读取的最低位LSB。

而空信号的行为也是与整个FIFO的数据量挂钩的。如下,分4次共写入8bit数据后,empty即失效,表示当前FIFO不空。当一次读取8bit数据后,FIFO又恢复了空状态。

2.9、复位
在FPGA配置IP核后,在对FIFO进行操作前,必须对其复位。所以千万不要在上电后马上使用FIFO,而应该现将其复位,并在复位完成数个周期后才开始使用FIFO。在复位时时钟信号必须正常工作。

shift register FIFO和built-in FIFO的复位信号是不可选的,即一定存在的。对于shift register FIFO和7系列的built-in FIFO,Xilinx只提供了异步复位;而对于UltraScale,复位是同步复位信号,但提供了w_rst_busy和rd_rst_busy输出信号表示FIFO是否已经复位完毕。

​ Block RAM FIFO 和 Distributed RAM FIFO的复位信号是可选的。对于共用时钟的FIFO,选择同步复位和异步复位的区别主要在于是否复位信号是否和共用时钟同步;当选择异步复位时,可以Enable Safety Circuit,顾名思义就是让电路更安全,即通过复位完成信号wr_rst_busy和rd_rst_busy来表示是否FIFO已经复位完成。

下图是一个带有安全电路的异步复位时序,我们平常使用FIFO时应该要遵守这个时序:

2.10、实际深度
特别需要注意的一点是,由于实现方式的不同,设计的FIFO的实际深度可能会与期望值不同,我们可以在Vivado FIFO IP核的定制界面来获取实际深度的信息:

3、总结与参考
同步FIFO的使用会比较简单,因为其没有异步FIFO所需要的同步时钟域导致的延迟问题
不同资源实现的FIFO在各个功能特性上会有细微的区别,使用之前一定要认真测试,总结时序
FIFO的使用要注意实际深度
异步FIFO的计数器不是精确的,只能作为大致的参考,以实现例如半空半满、1/4空满等判断
可编程空满的实现是依赖计数器的,其值同样不够精确

参考资料1:pg057-fifo-generator

参考资料2:ug473_7Series_Memory_Resources

最新文章

最新文章