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空满判断的核心流程。你看一遍,基本就掌握了:
这张图把二进制和格雷码的判断路径都画出来了。你跟着走一遍,思路就清晰了。
4.5 总结一下
空满判断这件事,说白了就是「比指针」。但怎么比、用什么编码比,决定了你的FIFO能不能稳定工作。
- 二进制指针:简单直观,但跨时钟域同步有风险。适合同步FIFO或深度较小的异步FIFO。
- 格雷码指针:跨时钟域安全,但满判断逻辑稍复杂。我强烈推荐异步FIFO用这个。
- 深度为2^n:工程中最常见,判断逻辑可以优化得很高效。
最后说一句——不管你用哪种方案,仿真一定要覆盖「空满边界」的情况。我曾经漏测了「满标志刚好在边界处抖动」的场景,结果芯片回来后偶尔会丢数据。嗯,那次的教训挺深刻的。
可可精品资料库