格雷码为什么相邻只差 1 位?还能用在哪?
更新时间:2026-09-21。问题驱动短答页。异步 FIFO 见 异步 FIFO 为什么用格雷码,CDC 见 跨时钟域处理,低功耗见 低功耗方法。
一句话回答
格雷码是一种对整数的重新编码,其性质是“任意两个相邻数值的码字之间恰好只有 1 个 bit 不同”。普通二进制在加 1 时可能有多位同时翻转(如 0111→1000 有 4 位跳变),由于各位翻转存在微小先后,另一个时钟域若正好在跳变瞬间采样,可能采到既非旧值也非新值的错误中间码;格雷码因为每拍只变 1 位,跨域采样时最坏也只会取到“前一个值”或“后一个值”,两者都合法、不会出现错误指针。转换很简单:G = B XOR (B>>1),反向可逐位恢复。除了异步 FIFO 的读写指针(靠格雷码 + 同步器安全比较满/空),它还用于:低功耗总线编码(每拍平均翻转位更少,降低动态功耗)、旋转/直线机械编码器(多位同时变化时只可能错 1 个刻度)、以及任何需要让计数器/状态在采样边界天然安全的场合。
编码与转换
| 十进制 | 二进制 | 格雷码 |
|---|---|---|
| 0 | 000 | 000 |
| 1 | 001 | 001 |
| 2 | 010 | 011 |
| 3 | 011 | 010 |
| 4 | 100 | 110 |
- 二进 → 格雷:
G[i] = B[i] ^ B[i+1](即 G = B ^ (B>>>1))。 - 格雷 → 二进:从最高位往下
B[i] = B[i+1] ^ G[i]。 - 注意相邻只差 1 位,但它不适合直接做二进制算术,比较/运算前要转回二进制。
异步 FIFO 为什么离不开它
- 读写指针各在自己的时钟域自增;判断空/满要把对侧指针同步过来,见 异步 FIFO 格雷码。
- 指针是连续递增的计数值,用格雷码后多 bit 跨域“天然安全”:同步器哪怕在跳变时采样也只得到相邻合法值,再配合指针跨一圈才比较的设计,保证满/空判断正确。
- 仍需两级同步,格雷码解决“多位不一致”,同步器解决“单 bit 亚稳”,见 两级触发器。
其他用途
- 低功耗编码总线:地址/数据若做格雷映射,平均翻转位数下降,充放电次数减少,见 低功耗方法(代价是编解码逻辑,多用在地址总线等连续变化场景)。
- 机械/光学编码器:码盘按格雷码刻,相邻刻度只变一道,读头对位误差最多错 1 格而不是跳到错误的大值。
- 跨域状态/计数器:连续计数器、FSM 输出需要被异步域读取时,one-hot/格雷都可降低风险。
常见坑
- 以为用了格雷码就不用同步器——仍要打两拍防亚稳。
- 对非连续变化的普通数据总线用格雷编码:只有“数值连续递增”时才保证相邻 1 位。
- FIFO 深度不是 2 的幂时格雷码满/空判断会出错(标准格雷码环需要 2ⁿ 长度)。
- 直接对格雷码做加减比较,结果错误,需先转回二进制。
总结
格雷码让相邻整数只有一位翻转,从而在多位同时跳变有先后时保证跨时钟域采样只会得到旧值或新值这两个合法结果;靠 G=B^(B>>1) 转换、用后再转回二进制运算。它最经典的用途是异步 FIFO 指针的满空比较(配合两级同步,分别解决多位不一致和单 bit 亚稳),此外还能降低连续翻转总线的动态功耗、让机械编码器对位误差只错一格,并适用于一切需要在采样边界天然安全的连续计数/状态信号。