4、FIFO的空满判断:二进制指针的空满判断、格雷码指针的空满判断、深度为2^n的FIFO空满判断

空满判断,是FIFO设计里最核心、也最容易翻车的地方。我刚开始做FIFO的时候,觉得这还不简单?写地址和读地址比一比就行了。结果呢?第一次仿真就出了大问题——空标志没拉高,数据被读穿了。后来我才明白,这里面的门道比想象中多得多。

4.1 二进制指针的空满判断

先说说最直观的二进制指针。写指针wptr和读指针rptr都是二进制数,每写入一个数据,wptr加1;每读出一个数据,rptr加1。

空标志的判断:当wptr == rptr时,FIFO为空。这个好理解,读写指针指向同一个位置,说明没有数据可读。

满标志的判断:当wptr == rptr + DEPTH时,FIFO为满。也就是说,写指针比读指针多走了整整一圈。

但问题来了——你怎么知道wptr比rptr多走了一圈,而不是它们刚好相等?

举个例子:假设FIFO深度是8,wptr=3,rptr=3。这可能是空,也可能是满(写指针写了一圈又回到了3)。

这就是二进制指针的歧义问题。说白了,你没法区分「空」和「满」这两种状态。

解决方案:扩展一位指针位宽。

把指针位宽从log2(DEPTH)扩展到log2(DEPTH)+1。多出来的最高位用来标记「圈数」。

比如深度8的FIFO,指针用4位表示。最高位是圈数标志,低3位是地址。

  • 空:wptr == rptr(圈数和地址都相等)
  • 满:wptr[3] != rptr[3] 且 低3位相等

我在一个项目里用过这种方案,当时觉得挺完美的。直到后来发现——二进制指针跨时钟域同步时,多比特变化会导致亚稳态问题。嗯,这里要注意,多比特同步的风险很大。

4.2 格雷码指针的空满判断

为什么用格雷码?因为格雷码相邻两个值只有1比特变化。跨时钟域同步时,亚稳态的概率大大降低。我个人的习惯是,只要涉及异步FIFO,指针一定用格雷码。

格雷码的空判断:和二进制一样,wptr_gray == rptr_gray 就是空。

格雷码的满判断:这个就有点讲究了。你不能直接比大小,因为格雷码不是顺序编码。

正确的做法是:

  • 把格雷码转回二进制
  • 再用二进制的方式判断满

或者用另一种技巧——直接比较格雷码的位模式。我记得有个经典方法:

// 格雷码满判断条件
// 假设指针位宽为N(含扩展位)
// 满条件:wptr_gray[N-1] != rptr_gray[N-1] 且
//         wptr_gray[N-2] != rptr_gray[N-2] 且
//         wptr_gray[N-3:0] == rptr_gray[N-3:0]

说白了,就是最高两位取反,低位全部相等。这个规律我当年推导了好久才记住。

小技巧:如果你不想手动推导格雷码的满条件,可以直接把格雷码转二进制再判断。虽然多了一级转换逻辑,但代码可读性更好,也不容易出错。

4.3 深度为2^n的FIFO空满判断

为什么专门说2^n深度?因为实际工程中,90%以上的FIFO深度都是2的幂次。你想想看,地址位宽刚好是整数,指针回绕逻辑也简单。

对于深度为2^n的FIFO,空满判断可以做得非常简洁:

状态 二进制指针条件 格雷码指针条件
空 wptr == rptr wptr_gray == rptr_gray
满 (wptr ^ rptr) == (1 << n) wptr_gray[N-1:N-2] == ~rptr_gray[N-1:N-2] 且 低位相等

这里n = log2(DEPTH)。满条件用异或操作,非常高效。

我曾经在一个高速数据采集项目里,FIFO深度设成了1024(2^10)。用格雷码指针,满判断只用了几个门电路,延迟极小。那个项目跑到了500MHz,空满标志一点问题都没有。

避坑指南:

  • 我曾经犯过一个错——格雷码指针同步后,直接用同步后的值做空满判断。结果发现空标志偶尔会晚一个周期才拉高。后来才意识到,同步本身有延迟,空满判断应该用同步前的本地指针。
  • 还有一个坑:深度不是2^n时,格雷码的满条件会变得很复杂。我建议非2^n深度尽量用二进制指针,或者把深度补齐到2^n。

4.4 核心逻辑流程图

下面这张图,是我自己总结的FIFO空满判断的核心流程。你看一遍,基本就掌握了:

FIFO空满判断核心流程 开始空满判断 指针类型:二进制还是格雷码? 二进制 格雷码 扩展一位指针位宽 空:wptr == rptr 满:wptr == rptr + DEPTH 格雷码转二进制(可选) 空:wptr_gray == rptr_gray 满:高位取反,低位相等 输出空/满标志

这张图把二进制和格雷码的判断路径都画出来了。你跟着走一遍,思路就清晰了。

4.5 总结一下

空满判断这件事,说白了就是「比指针」。但怎么比、用什么编码比,决定了你的FIFO能不能稳定工作。

  • 二进制指针:简单直观,但跨时钟域同步有风险。适合同步FIFO或深度较小的异步FIFO。
  • 格雷码指针:跨时钟域安全,但满判断逻辑稍复杂。我强烈推荐异步FIFO用这个。
  • 深度为2^n:工程中最常见,判断逻辑可以优化得很高效。

最后说一句——不管你用哪种方案,仿真一定要覆盖「空满边界」的情况。我曾经漏测了「满标志刚好在边界处抖动」的场景,结果芯片回来后偶尔会丢数据。嗯,那次的教训挺深刻的。

可可精品资料库