**要点速览:**在反射二进制格雷码中,相邻数值只有一个比特不同。对于结构正确的位置边界,这一性质可以限制读数歧义,也适合用于异步 FIFO 指针。但它无法消除亚稳态、传感器故障、组合逻辑毛刺,也不能取代 CDC 时序约束。
标准 4 位二进制计数器从 前进到 时,四个比特会全部翻转。在理想化时序图中,它们同时变化;在真实芯片里,由于门延迟不一致、边沿转换需要时间且电源轨存在噪声,它们会以无法预先确定的顺序改变,导致总线上短暂闪现 或 等中间值。对于 CPU 寄存器,这通常无害:所有信号稳定后,时钟边沿才对数值采样。但对于由异步观察者读取的传感器,这些毛刺会成为真实读数。
格雷码也称为反射二进制码,它改变了边界条件:相邻码字只有一个比特不同,因此对准正确的观察者无需同时判定多个预期发生的位转换。正在变化的那一个比特仍可能出现歧义、噪声或亚稳态,所以格雷码只是减少某一类错误,并不会让接口绝对可靠。
单比特相邻为什么重要?
以旋转编码器为例——它是一块光学或磁性圆盘,用来告诉电机控制器转轴当前的角度。圆盘上分布着同心轨道,每条轨道贡献位置字的一个比特,固定角度上的传感器负责读取它们。圆盘旋转时,传感器会看到一系列二进制值。
如果轨道使用普通二进制编码,从位置 ()到位置 ()的边界要求三条轨道在名义上相同的角度改变状态。传感器安装位置、轨道几何形状与阈值差异都会使这些转换无法被同时观察到。控制器可能短暂解码出一个与两个相邻位置都相距甚远的中间码字。
用格雷码对同一块圆盘编码后,每个角度边界都只包含一次单比特转换:
| 位置 | 二进制 | 格雷码 |
|---|---|---|
| 0 | 000 | 000 |
| 1 | 001 | 001 |
| 2 | 010 | 011 |
| 3 | 011 | 010 |
| 4 | 100 | 110 |
| 5 | 101 | 111 |
| 6 | 110 | 101 |
| 7 | 111 | 100 |
观察格雷码一列。任何两行相邻值之间都恰好有一个比特翻转;第 7 行与第 0 行之间也恰好有一个比特翻转,因此该序列既是循环的,也是单位距离的。在预期的一次只跨过一个边界的模型下,阈值不确定性会被限制在旧码或相邻新码之内。但跨越多个边界的失准、污染、轨道损坏、电气噪声,以及对亚稳态样本进行解码,仍然可能产生错误。
4 位格雷码表
这一规律可以自然扩展:
| 十进制 | 二进制 | 格雷码 |
|---|---|---|
| 0 | 0000 | 0000 |
| 1 | 0001 | 0001 |
| 2 | 0010 | 0011 |
| 3 | 0011 | 0010 |
| 4 | 0100 | 0110 |
| 5 | 0101 | 0111 |
| 6 | 0110 | 0101 |
| 7 | 0111 | 0100 |
| 8 | 1000 | 1100 |
| 9 | 1001 | 1101 |
| 10 | 1010 | 1111 |
| 11 | 1011 | 1110 |
| 12 | 1100 | 1010 |
| 13 | 1101 | 1011 |
| 14 | 1110 | 1001 |
| 15 | 1111 | 1000 |
“反射”这个名称来自它的构造规则:要生成 位码,先取 位码,将其上下镜像,然后在原序列前添加 ,在镜像序列前添加 。镜像操作保证两个半区交界处只有一次单比特转换,也就是新加入的最高有效位发生变化。
二进制转格雷码
从二进制 转换为格雷码 的公式,是数字逻辑中最简洁的公式之一:
对于最高有效位,规定。写成单一表达式就是:
在硬件中,每个格雷码输出都是相邻两个二进制位的一次异或;不存在进位链,理想逻辑深度只有一级异或门。但当二进制计数器的多个比特正在发生物理变化时,这并不保证组合输出无毛刺。把转换后的格雷码值寄存起来,再将它作为 CDC 信号使用。要理解为什么 XOR 是合适的运算符,可以回顾异或门作为差异检测器的作用。
**计算示例。**把二进制 转换为格雷码。
结果为格雷码 ;上表也证实它对应十进制 11。
格雷码转二进制
反向转换采用前缀异或:
等价地,从最高有效位向下遍历:,随后令 。直接串行实现具有线性的异或深度;使用并行前缀异或树,则可以用更多布线与门电路换取更浅的深度。不要认为朴素的串接链是唯一实现方式。
继续上面的例子,解码格雷码 :
恢复出的二进制为 ,也就是十进制 11。往返转换完全一致。
编码器之外:时钟域跨越
旋转编码器是教科书中的典型动机,但格雷码在现代系统中影响最大的用途位于 FPGA 与 SoC 内部:用于时钟域跨越(CDC)的异步 FIFO 指针。
双时钟 FIFO 的两侧都需要估计另一侧的指针,才能推导出满或空状态。把普通多位二进制计数器直接跨域传输并不安全,因为一次递增就可能改变多个比特。
稳健的格雷码指针设计通常会:
- 在本地域内保留二进制工作指针,以便进行算术运算;
- 把下一指针转换为格雷码;
- 在源时钟域寄存格雷码值;
- 将格雷码的每一个比特同步到目标时钟域;以及
- 按照该 FIFO 文档规定的满/空方程比较同步后的指针。
格雷编码意味着每次递增只会有一个逻辑指针位变化,却不能独自保证 CDC 正确。变化中的比特仍可能落入目标触发器的采样孔径并进入亚稳态。如果实现约束没有限制延迟或总线偏斜,布线偏斜还可能让不同源转换中的格雷码位在目标端重叠。AMD 与 Intel 都为格雷编码 FIFO 指针记录了此类约束。
同步后的指针还会滞后若干个目标时钟周期。满/空逻辑会围绕这一延迟采取保守设计;它并不是持续精确的跨域计数。正确性至关重要时,应使用经过验证的异步 FIFO 设计或厂商 IP。
硬件中的格雷码计数器
有两种实用方案:
- **二进制计数器加寄存转换。**计算
gray_next = binary_next XOR (binary_next >> 1),并在同一个源时钟上捕获两个下一状态值。寄存后的格雷码输出会按编码序列变化;不要把由存在波纹或偏斜的二进制计数驱动的未寄存转换器输出,当成无毛刺信号对外暴露。 - **原生格雷码状态机。**直接推导沿所选格雷码序列前进的下一状态逻辑。这样可以省去二进制状态寄存器,但递增、递减及 FIFO 地址算术会变得不够方便。
要试验第一种方案,可以打开4 位二进制计数器模板,加入异或网络,并寄存其四个输出。比较每次时钟之后的稳定状态。如果仿真器能够模拟门延迟,还要观察转换期间未寄存转换器的输出;它能直观展示源寄存器为何不可缺少。
缺点:算术运算并不方便
格雷码的超能力——单比特转换——也有代价:直接使用格雷码做算术非常麻烦。格雷编码数值不存在可直接使用的进位传播公式。两个格雷码数相加时,必须先把两者转换为二进制,完成加法,再转换回来。乘法与移位同样不适合直接进行。
因此,普通算术数据通路通常会先把格雷编码状态转换为二进制,而不是直接以格雷码执行完整算术。该编码适合特定边界,并不是二进制的通用替代品。卡诺图采用格雷码次序则有另一个原因:相邻单元必须只在一个输入变量上不同,这样几何分组才能对应蕴含项。
历史说明
该编码以贝尔实验室研究员 Frank Gray 的姓氏命名。他的“脉冲编码通信”专利申请于 1947 年提交,并在 1953 年以美国专利 2,632,058 授权;专利将这种方案称为反射二进制码,并强调相邻的被表示幅值只在一个脉冲位置上不同。相关单位距离序列早于该专利,因此更准确的说法是:Gray 推动了这种重要应用及其命名传承,而不是发明了所有此类序列。
常见误区
- **以为可以直接做算术。**对格雷编码值逐位相加只会得到毫无意义的结果。务必先转换为二进制。
- **忽略最高有效位上方补零的约定。**二进制转格雷码公式假定最高有效位上方存在一个虚拟零。忘记它会使高位符号发生错误翻转。
- **把格雷码与移位寄存器环路混为一谈。**普通移位寄存器(例如SISO/SIPO 设计)会横向移动比特,无法在端到端范围内保持单位距离性质。应在边界处完成转换。
- **混淆格雷码与独热码。**独热编码(任何时刻只有一个比特置位)从另一种意义上也具有抗毛刺能力,但其宽度为 ,而不是 。格雷码是更紧凑的选择。
下一步
已发布的 SISO、SIPO、PISO 与 PIPO 指南比较了四种常见移位寄存器数据路径。不要用普通移位寄存器替代异步 FIFO:相关联的多位数据需要一种能在跨越时钟边界时保持一致性的协议。
要实际观察格雷码,可以载入 4 位二进制计数器模板,搭建转换器,加入输出寄存器,然后验证连续两个寄存值之间的汉明距离。接着,在能够感知延迟的模型中故意移除寄存器,再观察转换期间的行为。
