1.什么是FIFO?
FIFO 是英文 First In First Out 的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加 1 完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
2 .什么情况下用FIFO?
FIFO 一般用于不同时钟域之间的数据传输,比如 FIFO 的一端时 AD 数据采集,另一端时计算机的 PCI 总线,假设其 AD 采集的速率为 16 位 100K SPS ,那么每秒的数据量为 100K×16bit=1.6Mbps, 而 PCI 总线的速度为 33MHz ,总线宽度 32bit, 其最大传输速率为 1056Mbps, 在两个不同的时钟域间就可以采用 FIFO 来作为数据缓冲。另外对于不同宽度的数据接口也可以用 FIFO ,例如单片机位 8 位数据输出,而 DSP 可能是 16 位数据输入,在单片机与 DSP 连接时就可以使用 FIFO 来达到数据匹配的目的。
3 .FIFO的一些重要参数
FIFO 的宽度:也就是英文资料里常看到的 THE WIDTH ,它只的是 FIFO 一次读写操作的数据位,就像 MCU 有 8 位和 16 位, ARM 32 位等等, FIFO 的宽度在单片成品 IC 中是固定的,也有可选择的,如果用 FPGA 自己实现一个 FIFO ,其数据位,也就是宽度是可以自己定义的。
FIFO 的深度 : THE DEEPTH ,它指的是 FIFO 可以存储多少个 N 位的数据(如果宽度为 N )。如一个 8 位的 FIFO ,若深度为 8 ,它可以存储 8 个 8 位的数据,深度为 12 ,就可以存储 12 个 8 位的数据, FIFO 的深度可大可小,个人认为 FIFO 深度的计算并无一个固定的公式。在 FIFO 实际工作中,其数据的满 / 空标志可以控制数据的继续写入或读出。在一个具体的应用中也不可能由一些参数算数精确的所需 FIFO 深度为多少,这在写速度大于读速度的理想状态下是可行的,但在实际中用到的 FIFO 深度往往要大于计算值。一般来说根据电路的具体情况,在兼顾系统性能和 FIFO 成本的情况下估算一个大概的宽度和深度就可以了。而对于写速度慢于读速度的应用, FIFO 的深度要根据读出的数据结构和读出数据的由那些具体的要求来确定。
满标志 : FIFO 已满或将要满时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的写操作继续向 FIFO 中写数据而造成溢出( overflow )。
空标志 : FIFO 已空或将要空时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的读操作继续从 FIFO 中读出数据而造成无效数据的读出( underflow )。
读时钟 :读操作所遵循的时钟,在每个时钟沿来临时读数据。
写时钟 :写操作所遵循的时钟,在每个时钟沿来临时写数据。
读指针 :指向下一个读出地址。读完后自动加 1 。
写指针 :指向下一个要写入的地址的,写完自动加 1 。
读写指针其实就是读写的地址,只不过这个地址不能任意选择,而是连续的。
4 . FIFO的分类
根均 FIFO 工作的时钟域,可以将 FIFO 分为同步 FIFO 和异步 FIFO 。同步 FIFO 是指读时钟和写时钟为同一个时钟。在时钟沿来临时同时发生读写操作。异步 FIFO 是指读写时钟不一致,读写时钟是互相独立的。
5 . FIFO设计的难点
FIFO 设计的难点在于怎样判断 FIFO 的空 / 满状态。为了保证数据正确的写入或读出,而不发生益处或读空的状态出现,必须保证 FIFO 在满的情况下,不能进行写操作。在空的状态下不能进行读操作。怎样判断 FIFO 的满 / 空就成了 FIFO 设计的核心问题。由于同步 FIFO 几乎很少用到,这里只描述异步 FIFO 的空 / 满标志产生问题。
在用到触发器的设计中,不可避免的会遇到亚稳态的问题(关于亚稳态这里不作介绍,可查看相关资料)。在涉及到触发器的电路中,亚稳态无法彻底消除,只能想办法将其发生的概率将到最低。其中的一个方法就是使用格雷码。格雷码在相邻的两个码元之间只由一位变换 ( 二进制码在很多情况下是很多码元在同时变化 ) 。这就会避免计数器与时钟同步的时候发生亚稳态现象。但是格雷码有个缺点就是只能定义 2^n 的深度,而不能像二进制码那样随意的定义 FIFO 的深度,因为格雷码必须循环一个 2^n ,否则就不能保证两个相邻码元之间相差一位的条件,因此也就不是真正的各雷码了。第二就是使用冗余的触发器,假设一个触发器发生亚稳态的概率为 P ,那么两个及联的触发器发生亚稳态的概率就为 P 的平方。但这回导致延时的增加。亚稳态的发生会使得 FIFO 出现错误,读 / 写时钟采样的地址指针会与真实的值之间不同,这就导致写入或读出的地址错误。由于考虑延时的作用,空 / 满标志的产生并不一定出现在 FIFO 真的空 / 满时才出现。可能 FIFO 还未空 / 满时就出现了空 / 满标志。这并没有什么不好,只要保证 FIFO 不出现 overflow or underflow 就 OK 了。
很多关于 FIFO 的文章其实讨论的都是空 / 满标志的不同算法问题。
在 Vijay A. Nebhrajani 的《异步 FIFO 结构》一文中,作者提出了两个关于 FIFO 空 / 满标志的算法。
第一个算法:构造一个指针宽度为 N+1 ,深度为 2^N 字节的 FIFO (为便方比较将格雷码指针转换为二进制指针)。当指针的二进制码中最高位不一致而其它 N 位都相等时, FIFO 为满(在 Clifford E. Cummings 的文章中以格雷码表示是前两位均不相同,而后两位 LSB 相同为满,这与换成二进制表示的 MSB 不同其他相同为满是一样的)。当指针完全相等时, FIFO 为空。这也许不容易看出,举个例子说明一下:一个深度为 8 字节的 FIFO 怎样工作(使用已转换为二进制的指针)。 FIFO_WIDTH=8 , FIFO_DEPTH= 2^N = 8 , N = 3 ,指针宽度为 N+1=4 。起初 rd_ptr_bin 和 wr_ptr_bin 均为 “0000” 。此时 FIFO 中写入 8 个字节的数据。 wr_ptr_bin =“1000” , rd_ptr_bin=“0000” 。当然,这就是满条件。现在,假设执行了 8 次的读操作,使得 rd_ptr_bin =“1000” ,这就是空条件。另外的 8 次写操作将使 wr_ptr_bin 等于 “0000” ,但 rd_ptr_bin 仍然等于 “1000” ,因此 FIFO 为满条件。
显然起始指针无需为 “0000” 。假设它为 “0100” ,并且 FIFO 为空,那么 8 个字节会使 wr_ptr_bin =“1100” , , rd_ptr_bin 仍然为 “0100” 。这又说明 FIFO 为满。
在 Vijay A. Nebhrajani 的这篇《异步 FIFO 结构》文章中说明了怎样运用格雷码来设置空满的条件,但没有说清为什么深度为 8 的 FIFO 其读写指针要用 3+1 位的格雷码来实现,而 3+1 位的格雷码可以表示 16 位的深度,而真实的 FIFO 只有 8 位,这是怎么回事?而这个问题在 Clifford E. Cummings 的文章中得以解释。三位格雷码可表示 8 位的深度,若在加一位最为 MSB ,则这一位加其他三位组成的格雷码并不代表新的地址,也就是说格雷码的 0100 表示表示 7 ,而 1100 仍然表示 7 ,只不过格雷码在经过一个以 0 位 MSB 的循环后进入一个以 1 为 MSB 的循环,然后又进入一个以 0 位 MSB 的循环,其他的三位码仍然是格雷码,但这就带来一个问题,在 0100 的循环完成后,进入 1000 ,他们之间有两位发生了变换,而不是 1 位,所以增加一位 MSB 的做法使得该码在两处: 0100~1000 , 1100~0000 有两位码元发生变化,故该码以不是真正的格雷码。增加的 MSB 是为了实现空满标志的计算。 Vijay A. Nebhrajani 的文章用格雷码转二进制,再转格雷码的情况下提出空满条件,仅过两次转换,而 Clifford E. Cummings 的文章中直接在格雷码条件下得出空满条件。其实二者是一样的,只是实现方式不同罢了。
第二种算法: Clifford E. Cummings 的文章中提到的 STYLE. #2 。它将 FIFO 地址分成了 4 部分,每部分分别用高两位的 MSB 00 、 01 、 11 、 10 决定 FIFO 是否为 going full 或 going empty ( 即将满或空 ) 。如果写指针的高两位 MSB 小于读指针的高两位 MSB 则 FIFO 为 “ 几乎满 ” ,
若写指针的高两位 MSB 大于读指针的高两位 MSB 则 FIFO 为 “ 几乎空 ” 。
在 Vijay A. Nebhrajani 的《异步 FIFO 结构》第三部分的文章中也提到了一种方法,那就是方向标志与门限。设定了 FIFO 容量的 75% 作为上限,设定 FIFO 容量的 25% 为下限。当方向标志超过门限便输出满 / 空标志,这与 Clifford E. Cummings 的文章中提到的 STYLE. #2 可谓是异曲同工。他们都属于保守的空满判断。其实这时输出空满标志 FIFO 并不一定真的空 / 满。
说到此,我们已经清楚地看到, FIFO 设计最关键的就是产生空 / 满标志的算法的不同产生了不同的 FIFO 。但无论是精确的空满还是保守的空满都是为了保证 FIFO 工作的可靠。
6 .关于 FIFO的一点的思考
关于 FIFO 丢数据的问题,其实各位对同一个问题的理解有偏差,才造成了相互误解。如果在理想状况下(时钟同步不回出现错码), FIFO 由读写指针控制是不会丢数的,(这不是废话吗,现实中哪来的理想状况!)且慢,我的意思是说丢数据并不是读写谁快谁慢造成的,在正确的设置空满标志算法的情况下,数据的 overflow 和 underflow 是不会发生的。而往往现实中因为亚稳态的存在,才出现了丢数的情况,也就是说是只要读写时钟不同步,在采样的过程中采样出错,使得本该是 0100 的变成了 1101 等等,就会出现读写的错误,我们称其为丢数,其原因就是在时钟同步指针的时候出现亚稳态,由于二进制码加 1 的时候很多位同时变化,所以很容易出现亚稳态。因此才用格雷码将此问题发生的概率比降到最小,其次用多余的触发器使其概率进一步降低,也就是说错误难免,但我们可以将其发生的概率降到最低,并且在出现错误时也不会错的态离谱(详见 Vijay A. Nebhrajani 的《异步 FIFO 结构》第二篇)。
二进制码指针并非不好用,在前面也提到了它有自身的优势,由于通过设置握手信号,指针可以有多位同时变化,二进制指针每次移动可以跳跃过任意的长度,这样给 FIFO 的某些功能的实现带来了方便 ( 例如,硬件直接控制 FIFO 从缓存的数据流中丢弃一个出错的包 ) ;而格雷码指针一般只能做递增或递减的移动。设置握手信号虽然可以保证指针不出错,但这样你来我往的经过三四个回合才能开始传数据,所以对于高速的场合就不适用了。
FIFO 是英文 First In First Out 的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加 1 完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
2 .什么情况下用FIFO?
FIFO 一般用于不同时钟域之间的数据传输,比如 FIFO 的一端时 AD 数据采集,另一端时计算机的 PCI 总线,假设其 AD 采集的速率为 16 位 100K SPS ,那么每秒的数据量为 100K×16bit=1.6Mbps, 而 PCI 总线的速度为 33MHz ,总线宽度 32bit, 其最大传输速率为 1056Mbps, 在两个不同的时钟域间就可以采用 FIFO 来作为数据缓冲。另外对于不同宽度的数据接口也可以用 FIFO ,例如单片机位 8 位数据输出,而 DSP 可能是 16 位数据输入,在单片机与 DSP 连接时就可以使用 FIFO 来达到数据匹配的目的。
3 .FIFO的一些重要参数
FIFO 的宽度:也就是英文资料里常看到的 THE WIDTH ,它只的是 FIFO 一次读写操作的数据位,就像 MCU 有 8 位和 16 位, ARM 32 位等等, FIFO 的宽度在单片成品 IC 中是固定的,也有可选择的,如果用 FPGA 自己实现一个 FIFO ,其数据位,也就是宽度是可以自己定义的。
FIFO 的深度 : THE DEEPTH ,它指的是 FIFO 可以存储多少个 N 位的数据(如果宽度为 N )。如一个 8 位的 FIFO ,若深度为 8 ,它可以存储 8 个 8 位的数据,深度为 12 ,就可以存储 12 个 8 位的数据, FIFO 的深度可大可小,个人认为 FIFO 深度的计算并无一个固定的公式。在 FIFO 实际工作中,其数据的满 / 空标志可以控制数据的继续写入或读出。在一个具体的应用中也不可能由一些参数算数精确的所需 FIFO 深度为多少,这在写速度大于读速度的理想状态下是可行的,但在实际中用到的 FIFO 深度往往要大于计算值。一般来说根据电路的具体情况,在兼顾系统性能和 FIFO 成本的情况下估算一个大概的宽度和深度就可以了。而对于写速度慢于读速度的应用, FIFO 的深度要根据读出的数据结构和读出数据的由那些具体的要求来确定。
满标志 : FIFO 已满或将要满时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的写操作继续向 FIFO 中写数据而造成溢出( overflow )。
空标志 : FIFO 已空或将要空时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的读操作继续从 FIFO 中读出数据而造成无效数据的读出( underflow )。
读时钟 :读操作所遵循的时钟,在每个时钟沿来临时读数据。
写时钟 :写操作所遵循的时钟,在每个时钟沿来临时写数据。
读指针 :指向下一个读出地址。读完后自动加 1 。
写指针 :指向下一个要写入的地址的,写完自动加 1 。
读写指针其实就是读写的地址,只不过这个地址不能任意选择,而是连续的。
4 . FIFO的分类
根均 FIFO 工作的时钟域,可以将 FIFO 分为同步 FIFO 和异步 FIFO 。同步 FIFO 是指读时钟和写时钟为同一个时钟。在时钟沿来临时同时发生读写操作。异步 FIFO 是指读写时钟不一致,读写时钟是互相独立的。
5 . FIFO设计的难点
FIFO 设计的难点在于怎样判断 FIFO 的空 / 满状态。为了保证数据正确的写入或读出,而不发生益处或读空的状态出现,必须保证 FIFO 在满的情况下,不能进行写操作。在空的状态下不能进行读操作。怎样判断 FIFO 的满 / 空就成了 FIFO 设计的核心问题。由于同步 FIFO 几乎很少用到,这里只描述异步 FIFO 的空 / 满标志产生问题。
在用到触发器的设计中,不可避免的会遇到亚稳态的问题(关于亚稳态这里不作介绍,可查看相关资料)。在涉及到触发器的电路中,亚稳态无法彻底消除,只能想办法将其发生的概率将到最低。其中的一个方法就是使用格雷码。格雷码在相邻的两个码元之间只由一位变换 ( 二进制码在很多情况下是很多码元在同时变化 ) 。这就会避免计数器与时钟同步的时候发生亚稳态现象。但是格雷码有个缺点就是只能定义 2^n 的深度,而不能像二进制码那样随意的定义 FIFO 的深度,因为格雷码必须循环一个 2^n ,否则就不能保证两个相邻码元之间相差一位的条件,因此也就不是真正的各雷码了。第二就是使用冗余的触发器,假设一个触发器发生亚稳态的概率为 P ,那么两个及联的触发器发生亚稳态的概率就为 P 的平方。但这回导致延时的增加。亚稳态的发生会使得 FIFO 出现错误,读 / 写时钟采样的地址指针会与真实的值之间不同,这就导致写入或读出的地址错误。由于考虑延时的作用,空 / 满标志的产生并不一定出现在 FIFO 真的空 / 满时才出现。可能 FIFO 还未空 / 满时就出现了空 / 满标志。这并没有什么不好,只要保证 FIFO 不出现 overflow or underflow 就 OK 了。
很多关于 FIFO 的文章其实讨论的都是空 / 满标志的不同算法问题。
在 Vijay A. Nebhrajani 的《异步 FIFO 结构》一文中,作者提出了两个关于 FIFO 空 / 满标志的算法。
第一个算法:构造一个指针宽度为 N+1 ,深度为 2^N 字节的 FIFO (为便方比较将格雷码指针转换为二进制指针)。当指针的二进制码中最高位不一致而其它 N 位都相等时, FIFO 为满(在 Clifford E. Cummings 的文章中以格雷码表示是前两位均不相同,而后两位 LSB 相同为满,这与换成二进制表示的 MSB 不同其他相同为满是一样的)。当指针完全相等时, FIFO 为空。这也许不容易看出,举个例子说明一下:一个深度为 8 字节的 FIFO 怎样工作(使用已转换为二进制的指针)。 FIFO_WIDTH=8 , FIFO_DEPTH= 2^N = 8 , N = 3 ,指针宽度为 N+1=4 。起初 rd_ptr_bin 和 wr_ptr_bin 均为 “0000” 。此时 FIFO 中写入 8 个字节的数据。 wr_ptr_bin =“1000” , rd_ptr_bin=“0000” 。当然,这就是满条件。现在,假设执行了 8 次的读操作,使得 rd_ptr_bin =“1000” ,这就是空条件。另外的 8 次写操作将使 wr_ptr_bin 等于 “0000” ,但 rd_ptr_bin 仍然等于 “1000” ,因此 FIFO 为满条件。
显然起始指针无需为 “0000” 。假设它为 “0100” ,并且 FIFO 为空,那么 8 个字节会使 wr_ptr_bin =“1100” , , rd_ptr_bin 仍然为 “0100” 。这又说明 FIFO 为满。
在 Vijay A. Nebhrajani 的这篇《异步 FIFO 结构》文章中说明了怎样运用格雷码来设置空满的条件,但没有说清为什么深度为 8 的 FIFO 其读写指针要用 3+1 位的格雷码来实现,而 3+1 位的格雷码可以表示 16 位的深度,而真实的 FIFO 只有 8 位,这是怎么回事?而这个问题在 Clifford E. Cummings 的文章中得以解释。三位格雷码可表示 8 位的深度,若在加一位最为 MSB ,则这一位加其他三位组成的格雷码并不代表新的地址,也就是说格雷码的 0100 表示表示 7 ,而 1100 仍然表示 7 ,只不过格雷码在经过一个以 0 位 MSB 的循环后进入一个以 1 为 MSB 的循环,然后又进入一个以 0 位 MSB 的循环,其他的三位码仍然是格雷码,但这就带来一个问题,在 0100 的循环完成后,进入 1000 ,他们之间有两位发生了变换,而不是 1 位,所以增加一位 MSB 的做法使得该码在两处: 0100~1000 , 1100~0000 有两位码元发生变化,故该码以不是真正的格雷码。增加的 MSB 是为了实现空满标志的计算。 Vijay A. Nebhrajani 的文章用格雷码转二进制,再转格雷码的情况下提出空满条件,仅过两次转换,而 Clifford E. Cummings 的文章中直接在格雷码条件下得出空满条件。其实二者是一样的,只是实现方式不同罢了。
第二种算法: Clifford E. Cummings 的文章中提到的 STYLE. #2 。它将 FIFO 地址分成了 4 部分,每部分分别用高两位的 MSB 00 、 01 、 11 、 10 决定 FIFO 是否为 going full 或 going empty ( 即将满或空 ) 。如果写指针的高两位 MSB 小于读指针的高两位 MSB 则 FIFO 为 “ 几乎满 ” ,
若写指针的高两位 MSB 大于读指针的高两位 MSB 则 FIFO 为 “ 几乎空 ” 。
在 Vijay A. Nebhrajani 的《异步 FIFO 结构》第三部分的文章中也提到了一种方法,那就是方向标志与门限。设定了 FIFO 容量的 75% 作为上限,设定 FIFO 容量的 25% 为下限。当方向标志超过门限便输出满 / 空标志,这与 Clifford E. Cummings 的文章中提到的 STYLE. #2 可谓是异曲同工。他们都属于保守的空满判断。其实这时输出空满标志 FIFO 并不一定真的空 / 满。
说到此,我们已经清楚地看到, FIFO 设计最关键的就是产生空 / 满标志的算法的不同产生了不同的 FIFO 。但无论是精确的空满还是保守的空满都是为了保证 FIFO 工作的可靠。
6 .关于 FIFO的一点的思考
关于 FIFO 丢数据的问题,其实各位对同一个问题的理解有偏差,才造成了相互误解。如果在理想状况下(时钟同步不回出现错码), FIFO 由读写指针控制是不会丢数的,(这不是废话吗,现实中哪来的理想状况!)且慢,我的意思是说丢数据并不是读写谁快谁慢造成的,在正确的设置空满标志算法的情况下,数据的 overflow 和 underflow 是不会发生的。而往往现实中因为亚稳态的存在,才出现了丢数的情况,也就是说是只要读写时钟不同步,在采样的过程中采样出错,使得本该是 0100 的变成了 1101 等等,就会出现读写的错误,我们称其为丢数,其原因就是在时钟同步指针的时候出现亚稳态,由于二进制码加 1 的时候很多位同时变化,所以很容易出现亚稳态。因此才用格雷码将此问题发生的概率比降到最小,其次用多余的触发器使其概率进一步降低,也就是说错误难免,但我们可以将其发生的概率降到最低,并且在出现错误时也不会错的态离谱(详见 Vijay A. Nebhrajani 的《异步 FIFO 结构》第二篇)。
二进制码指针并非不好用,在前面也提到了它有自身的优势,由于通过设置握手信号,指针可以有多位同时变化,二进制指针每次移动可以跳跃过任意的长度,这样给 FIFO 的某些功能的实现带来了方便 ( 例如,硬件直接控制 FIFO 从缓存的数据流中丢弃一个出错的包 ) ;而格雷码指针一般只能做递增或递减的移动。设置握手信号虽然可以保证指针不出错,但这样你来我往的经过三四个回合才能开始传数据,所以对于高速的场合就不适用了。