2. 读写指针的本质:指针的二进制计数原理,指针回绕(Wrap Around)机制,格雷码与二进制码的对比

2.1 指针到底是什么?

先问大家一个问题:FIFO 的读写指针,本质上是什么?

说白了,就是一个计数器。写指针每写入一个数据就加 1,读指针每读出一个数据也加 1。但这里有个关键点——这个计数器不是无限大的,它受限于 FIFO 的深度。

我刚开始做 FIFO 设计时,总觉得指针就是个地址。后来踩了坑才明白:指针的本质是“模 N 计数器”,N 就是 FIFO 的深度。它记录的是“已经写了/读了多少个数据”,而不是“数据存在哪个地址”。

核心理解: 写指针 = 写入次数 % 深度,读指针 = 读出次数 % 深度。

2.2 二进制计数原理

假设 FIFO 深度为 8,那么指针就是一个 3 位的二进制计数器。从 000 开始,每操作一次加 1:

000 → 001 → 010 → 011 → 100 → 101 → 110 → 111 → 000 → ...

嗯,这里要注意:当计数器从 111 回到 000 时,就发生了回绕(Wrap Around)。

为什么用二进制?因为二进制加法器是数字电路里最成熟的模块,面积小、速度快。我在一个高速 FIFO 项目中试过用格雷码做加法,结果发现加法器比二进制复杂得多,功耗反而上去了。

2.3 回绕机制——FIFO 的灵魂

回绕机制解决了一个核心问题:如何判断 FIFO 是满还是空?

你想想看,如果指针从 000 加到 111 再回到 000,那 000 这个状态到底代表“空”还是“满”?

答案是:加一个额外的标志位。

我习惯的做法是:指针的位宽比深度多 1 位。比如深度 8 的 FIFO,指针用 4 位。低 3 位是真正的地址,最高位用来标记回绕次数。

指针值(4位) 低3位地址 回绕次数 含义
0000 000 0 空(初始状态)
1000 000 1 满(回绕一次后回到地址0)
0101 101 0 正常写入5个数据
1101 101 1 回绕一次后写入5个数据

判断空的条件:读写指针完全相等(包括最高位)。
判断满的条件:读写指针的低 3 位相等,但最高位相反。

我的经验: 回绕标志位一定要用格雷码同步到另一个时钟域。我曾经因为直接用二进制同步,导致满标志判断出错,数据被覆盖了。那一次 debug 花了我整整两天。

2.4 格雷码 vs 二进制码——为什么需要格雷码?

二进制计数器有个致命问题:多位同时翻转。

比如从 011(3)到 100(4),3 个 bit 同时变化。在跨时钟域同步时,采样到的值可能是 000、001、010、100……任何一个中间态。这会导致空/满判断瞬间出错。

格雷码就解决了这个问题:每次只变化 1 个 bit。

十进制 二进制 格雷码
0 000 000
1 001 001
2 010 011
3 011 010
4 100 110
5 101 111
6 110 101
7 111 100

你看,格雷码相邻两个值之间只有 1 位不同。同步时最多错 1 个 bit,不会出现完全错误的值。

注意: 格雷码虽然同步安全,但做加法比二进制复杂。我一般只在跨时钟域同步时用格雷码,FIFO 内部计数还是用二进制。说白了就是:内部用二进制算,同步用格雷码传。

2.5 二进制 ↔ 格雷码转换

转换公式其实很简单:

// 二进制转格雷码
gray = binary ^ (binary >> 1);

// 格雷码转二进制(Verilog 实现)
assign binary[WIDTH-1] = gray[WIDTH-1];
generate
  for(i = WIDTH-2; i >= 0; i = i-1) begin
    assign binary[i] = gray[i] ^ binary[i+1];
  end
endgenerate

我个人习惯把转换逻辑封装成两个独立的模块:bin2gray 和 gray2bin。这样代码复用性高,也不容易出错。

2.6 本章知识体系

下面这张图总结了指针设计的核心逻辑:

读写指针设计核心逻辑 指针 = 模N计数器 二进制计数原理 回绕(Wrap Around) 格雷码 vs 二进制 加法器成熟,面积小 多位同时翻转问题 额外标志位标记回绕 空/满判断的核心 每次只变1bit,同步安全 内部二进制,同步用格雷 核心原则:内部二进制计算,跨域格雷码同步

2.7 避坑指南

  • 不要直接用二进制指针做跨时钟域同步。 我曾经在一个项目中偷懒,结果空标志偶尔会误判为满,导致读操作被阻塞。查了三天才发现是同步时采样到了错误值。
  • 格雷码转二进制时注意位宽匹配。 我见过有人把 4 位格雷码转成 3 位二进制,结果地址直接少了一半。
  • 回绕标志位一定要参与空满判断。 如果不加最高位,深度 8 的 FIFO 在写入 8 个数据后,指针回到 000,你会误以为 FIFO 是空的——然后继续写入,数据就覆盖了。

嗯,这一章的内容就到这里。指针设计是 FIFO 的基础,理解了二进制计数和回绕机制,后面讲深度扩展、异步 FIFO 同步策略时就会轻松很多。

可可精品资料库