FPGA基础:一文吃透CRC算法(下)——CRC硬件加速原理深度解析-Anlogic-安路社区-FPGA CPLD-ChipDebug

FPGA基础:一文吃透CRC算法(下)——CRC硬件加速原理深度解析

前言

在很多以太网、SATA 或其他高速接口项目中,我们经常会接触到 CRC(循环冗余校验)。通常,工程师会通过网站或软件工具生成一整套带异或操作的组合逻辑来实现 CRC 算法,而不去深入理解其原理——只知道可以这样用。事实上,在大多数情况下,确实不需要了解 CRC 的具体实现方法,这种黑盒化的使用方式在低频窄位宽场景下确实可行。

但是,当你的系统时钟非常高,例如 800MHz、1GHz,且数据位宽很大如 256bit、512bit时,工具生成的完整组合逻辑往往会因为时序问题而无法跑通。这时,就需要对 CRC 进行拆分设计,以优化时序和逻辑面积。如果不了解 CRC 的本质和计算方法,就很难进行这样的优化设计。因此,理解 CRC 的原理和硬件实现方式非常必要。

如果你曾经困惑过:为什么要做输入/输出反转?CRC 的异或参数到底有什么意义?又或者想知道 CRC 在硬件里是如何高效实现的——那么这篇文章正好适合你。这里我会讲解一种适合硬件实现的高效查表算法,并分享如何进一步优化。在此基础上,还会带你理解常见 CRC 参数的来源与作用。读完之后,相信你对 CRC 的认识会比大多数人更深

CRC硬件算法初探

表驱动 (Table-Driven)实现方法

前面介绍的 简单 算法很适合作为入门,因为它和 CRC 的理论对应得很直接,而且名字就叫“简单”嘛。但它有两个大问题:

  1. 1. 它是按比特来处理的,写起来很麻烦(即使是 C 语言)
  2. 2. 跑得慢,因为它得每一位都循环一次

为了加速,我们得想办法一次处理更多的位,比如:

  • • nibbles(4 位)
  • • bytes(8 位)
  • • words(16 位)
  • • longwords(32 位)
  • • doublewords(64 位)甚至更大。

4 位(半字节)最好别用,因为它不和字节对齐,不方便。至少我们得做到按字节来处理,大多数表驱动的算法也都是一次处理一个字节的。


从位到字节的思路转换

为了说明方便,我们把 CRC 的多项式从 4 位换成 32 位 的。寄存器的样子不变,只是每个格子现在代表的是1 个字节而不是 1 位。
多项式(Poly)是 33 位的:最高位有一个隐含的 1,剩下 32 位是有效位。

   3    2    1    0   (字节编号)
+----+----+----+----+
|    |    |    |    |  <- 接收消息(补充后的消息)
+----+----+----+----+

SIMPLE 算法还是能用的,但我们来看它在 32 位寄存器里的运作方式:
假设最高字节(第 3 个字节)的位是:

t7 t6 t5 t4 t3 t2 t1 t0

poly是:

g7 g6 g5 g4 g3 g2 g1 g0

下一步,t7 决定要不要把多项式(Poly)XOR 进整个寄存器。

如果 t7 = 1,就 XOR;如果是 0,就不动。那么可以表示为:

        t6 t5 t4 t3 t2 t1 t0 ??
+ t7 * (g7 g6 g5 g4 g3 g2 g1 g0)    [Reminder: + is XOR]

那么新的最高位就是:

t6 + t7*g7

重点是:

  • • 新的最高位只跟当前最高字节的前两位有关系
  • • 再下一个最高位只跟当前最高字节的前三位有关系
  • • 总之,第 k 次循环的最高位,只取决于当前最高字节的前 k 位

为了有一个更直观的感受,我们可以举一个W=2的例子,可以看到:t1_1和t1_2完全是由t1,t0,g1,g0所产生,也就是说第2次循环的最高位只取决于当前最高位的前2位(t1,t0)

图片[1]-FPGA基础:一文吃透CRC算法(下)——CRC硬件加速原理深度解析-Anlogic-安路社区-FPGA CPLD-ChipDebug

既然这样,我们完全可以一次拿原来最高字节的全部 8 位,把接下来 8 轮的最高位变化一次算好。
这 8 个“提前算好”的控制位(control byte)可以放进一个字节里,每次取出一位来控制是否 XOR。

这样有几个好处:

  1. 1. 当前最高字节的具体值已经不重要了,反正 8 步之后它都会被移出寄存器。
  2. 2. 其他字节往左移,新消息字节从右边进来。
  3. 3. 在这个过程中,寄存器会按控制字节的每一位来做 XOR 操作。

XOR 可以提前合并

多次在不同偏移量上 XOR 某个常数,其实等效于一开始就 XOR 一个“合并好的常数”。
举个例子:

0100010   (寄存器)
...0110   (XOR 常数 #1)
..0110.   (XOR 常数 #2)
0110...   (XOR 常数 #3)
结果是:
0011000   (一次 XOR 的等效结果)

意思是:你不必一位位做多次 XOR,把它们的效果一次算出来也行。


组合成表驱动算法

所以,最终算法可以这样做:

  1. 1. 看寄存器最高字节(Top)
  2. 2. 从这个字节算出对应的控制字节(其实就是查表)
  3. 3. 把该控制字节对应的“合并好的 XOR 常数”一次 XOR 进寄存器
  4. 4. 寄存器左移一个字节,新字节从右边进来
  5. 5. 重复直到数据处理完

但因为控制字节和 XOR 结果是可以提前算好的,我们可以直接用一个查表法实现:

                   3    2    1    0   Bytes
                +----+----+----+----+
         +-----<|    |    |    |    | <----- Augmented message
         |      +----+----+----+----+
         |                ^
         |                |
         |               XOR
         |                |
         |     0+----+----+----+----+       Algorithm
         v      +----+----+----+----+       ---------
         |      +----+----+----+----+       1. Shift the register left by
         |      +----+----+----+----+          one byte, reading in a new
         |      +----+----+----+----+          message byte.
         |      +----+----+----+----+       2. Use the top byte just rotated
         |      +----+----+----+----+          out of the register to index
         +----->+----+----+----+----+          the table of 25632-bit values.
                +----+----+----+----+       3. XOR the table value into the
                +----+----+----+----+          register.
                +----+----+----+----+       4. Goto 1 iff more augmented
                +----+----+----+----+          message bytes.
             255+----+----+----+----+

为什么表大小是 256

  • • 最高字节是 8 位,所以可能有 256 种取值。
  • • 对于每个取值,我们可以离线计算它在多项式下移 8 次的效果。
  • • 查表的时候,直接把这个预计算的效果 XOR 上去就行。
While (还有数据没处理)
   Top = 寄存器最高字节
   寄存器 = (寄存器左移 8 位) | 下一个消息字节
   寄存器 = 寄存器 XOR 表[Top]
End

这样每个循环只要:

  • • 一个移位
  • • 一个 OR
  • • 一个 XOR
  • • 一次查表

速度快到飞起。


C 代码长这样:

其中,len扩展消息(augmented message,即填充了冗余位的消息)的字节长度,p指向扩展消息的起始地址,r是CRC寄存器(存储中间状态),t临时变量table预计算表(存储256个32位CRC值的数组)。

r = 0;
while (len--) {
    byte t = (r >> 24) & 0xFF;
    r = (r << 8) | *p++;
    r ^= table[t];
}

或者压缩成一行:

r = 0while (len--) r = ((r << 8) | *p++) ^ table[(r >> 24) & 0xFF];

这就是 TABLE 算法,简洁、高效,但不懂 CRC 的人可能一眼看不出来它在干嘛。

CRC硬件算法优化

直接表(Direct Table)算法

原始表的痛点

r = 0;  
while (len--)  
    r = ((r << 8) | *p++) ^ t[(r >> 24) & 0xFF];

在之前的表算法中,为了确保真实数据的所有位都经过寄存器的最高位(控制是否异或多项式),必须在消息末尾添加W/8个零字节(如32位CRC添加4字节零),称为扩展消息(Augmented Message)。其作用是:

  • • 推动真实数据通过寄存器:零字节本身不改变寄存器(异或零无效),但能让真实数据的最后几位被“推”过寄存器的最高位,完成CRC计算。

但扩展消息的问题在于:

  • • 若消息由其他代码提供,无法修改(如只读内存中的数据),添加零字节会很麻烦
  • • 额外的内存开销(需复制消息并添加零字节)

如何让算法不需要补零

回顾一下 CRC 的处理过程:

1. TAIL: 处理末尾填充的零字节(结束处理)

  • • 做了什么? 在真实消息的末尾额外添加了 W/4 个零字节(例如,对于 32 位寄存器就是 4 个 0x00 字节)。
  • • 为什么这些零字节看起来无效?
    • • 与零异或不变: 异或操作 (XOR) 的特性是 任何值 X ^ 0 = X。因此,当寄存器内部发生异或操作时,这些零值不会改变目标字节的值。
    • • 永远达不到影响点: 这些零字节从最右边进入寄存器。在它们有机会完全移位到寄存器最左端(即可能影响后续输出或异或操作的关键位置)之前,计算就已经结束了。它们会被丢弃,根本达不到那个能发挥“零值影响力”的位置。
  • • 为什么它们至关重要(唯一作用)? 这些填充零字节强制算法在最后一个真实数据字节进入之后,额外执行了 W/4 个字节的移位/计算周期。它的唯一目的是确保真实数据的最后几位能够完全传播(propagate)穿过整个寄存器的长度。如果没有这些额外的周期,最终结果就不能正确地反映消息末尾那几个字节的影响。

简单说: 末尾加零不是为了改变寄存器内容,是为了让计算多做几步,把最后一点真实数据“推”到寄存器最深处,确保它们被完全计算进去。

2. HEAD: 处理开头几个字节(初始化)

  • • 情况 1:寄存器初始值为零:
    • • 最开始的 W/4 个字节(例如 4 个字节)被从右侧依次移入寄存器。
    • • 没有额外效果: 描述中提到“前 32 个控制位都是零”,这意味着在算法规则下,前几个计算周期内没有触发任何需要异或到寄存器内部的操作
    • • 结果: 经过这 4 个周期后,寄存器里简单存放着消息的前 4 个字节,没有发生任何改变。
  • • 情况 2:寄存器初始值非零:
    • • 最开始的 W/4 个字节仍然被从右侧移入寄存器。
    • • 有效果: 在这些字节移入的过程中,算法会执行异或操作(基于非零的初始状态及其规则)。具体异或进去的值取决于寄存器的初始值(是初始值的一个函数)。
    • • 结果: 经过这 4 个周期后,寄存器里包含的是消息的前 4 个字节由初始状态衍生出来的某个值进行异或组合的结果。但最主要的行为仍然是加载并整合前 4 个字节。

简单说: 开头几个字节主要是为了把初始数据装进寄存器。如果寄存器本来为零,就是简单的移入;如果寄存器非零,初始值会在装入过程中“混合”(异或)一下这些字节,但核心动作还是装入。

我们可以发现原算法本质上就是完全的异或操作:

r = ((r << 8) | *p++) ^ t[(r >> 24) & 0xFF];
可以转换为:
r = (r << 8) ^ (*p++) ^ t[(r >> 24) & 0xFF]; //本质上就是全异或操作

因为异或运算有结合律:

我们可以直接把数据字节异或到寄存器的高字节,然后查表,这样就不必让它真的在寄存器里流动 W/4 次。当然这里面的表虽然逻辑上和原算法是等价的,但是里面存的内容已经完全不一样了

原算法 
                  3    2    1    0   Bytes
                +----+----+----+----+
         +-----<|    |    |    |    | <----- Augmented message
         |      +----+----+----+----+
         |                ^
         |                |
         |               XOR
         |                |
         |     0+----+----+----+----+       Algorithm
         v      +----+----+----+----+       ---------
         |      +----+----+----+----+       1. Shift the register left by
         |      +----+----+----+----+          one byte, reading in a new
         |      +----+----+----+----+          message byte.
         |      +----+----+----+----+       2. Use the top byte just rotated
         |      +----+----+----+----+          out of the register to index
         +----->+----+----+----+----+          the table of 25632-bit values.
                +----+----+----+----+       3. XOR the table value into the
                +----+----+----+----+          register.
                +----+----+----+----+       4. Goto 1 iff more augmented
                +----+----+----+----+          message bytes.
             255+----+----+----+----+
优化后算法 
        +-----<Message (non augmented)
         |
         v         3    2    1    0   Bytes
         |      +----+----+----+----+
        XOR----<|    |    |    |    |
         |      +----+----+----+----+
         |                ^
         |                |
         |               XOR
         |                |
         |     0+----+----+----+----+       Algorithm
         v      +----+----+----+----+       ---------
         |      +----+----+----+----+       1. Shift the register left by
         |      +----+----+----+----+          one byte, reading in a new
         |      +----+----+----+----+          message byte.
         |      +----+----+----+----+       2. XOR the top byte just rotated
         |      +----+----+----+----+          out of the register with the
         +----->+----+----+----+----+          next message byte to yield an
                +----+----+----+----+          index into the table ([0,255]).
                +----+----+----+----+       3. XOR the table value into the
                +----+----+----+----+          register.
                +----+----+----+----+       4. Goto 1 iff more augmented
             255+----+----+----+----+          message bytes.

r=0while (len--) r = (r<<8) ^ t[(r >> 24) ^ *p++];

原算法的table[i] 表示:

当寄存器的最高字节是 i 时,经过一次左移并按 CRC 多项式除法更新后的 32 位补偿值。

也就是说,这个表是一步的转移函数,不能直接把“多步延迟”压缩到一步里。

而这里的 table[i] 表示的是:

当寄存器最高字节和当前输入字节 XOR 得到 i 时,把它直接当作“最终会到达高位查表的值”,经过一次 CRC 变换后的结果。

但这里有个坑:如果直接用原算法的 table,这个 (r >> 24) ^ *p++ 会对应原算法中不同步的状态,导致结果不对。
为了让提前 XOR 的效果等价,改进算法的表实际上是基于原算法的表预处理了 4 次,相当于把“字节进入寄存器低位后移 3 步才到高位的计算”预先算进表里。

示例

原算法:
    b 进入低位 → (移位+XOR多项式) × 3 次 → 到高位 → 查 T1 → XOR 回寄存器

提前 XOR 算法:
    (高位 ^ b) 直接查 T4 → XOR 回寄存器
        其中 T4 = T1 连续跑 4 次的结果

为了让大家有一个直观的感受,可以看下面的示例:

假设

  • • CRC 宽度 W = 32 bit(4 字节寄存器)
  • • 寄存器初始值 r = 0
  • • 数据字节顺序:B1, B2, B3, B4, B5(5 个字节)
  • • 为了让原算法工作,需要补 4 个零字节:0, 0, 0, 0

原算法(补零版)

循环: r = ((r << 8) | byte) ^ t[(r >> 24) & 0xFF]

时刻     高位字节 ───── 低位字节
        +------+------+------+------+
Step 1: |  0   |  0   |  0   | B1   | → 查表索引 H=0
Step 2: |  0   |  0   | B1   | B2   | → 查表索引 H=0
Step 3: |  0   | B1   | B2   | B3   | → 查表索引 H=0
Step 4: | B1   | B2   | B3   | B4   | → 查表索引 H=B1
Step 5: | B2   | B3   | B4   | B5   | → 查表索引 H=B2
Step 6: | B3   | B4   | B5   | 0    | → 查表索引 H=B3
Step 7: | B4   | B5   | 0    | 0    | → 查表索引 H=B4
Step 8: | B5   | 0    | 0    | 0    | → 查表索引 H=B5

可以看到:

  • • B1 在 Step 4 才进入高位参与查表
  • • B2 在 Step 5 才参与查表
  • • …
  • • 最后 4 个 0 只是用来“推进”寄存器,让最后的 B5 也能查一次表。

改进算法(提前 XOR 版)

循环: r = (r << 8) ^ t[(r >> 24) ^ byte]

Step 1: 查表索引 = (高位=0) ^ B1  → 直接用 B1
Step 2: 查表索引 = (高位=0) ^ B2  → 直接用 B2
Step 3: 查表索引 = (高位=0) ^ B3  → 直接用 B3
Step 4: 查表索引 = (高位=0) ^ B4  → 直接用 B4
Step 5: 查表索引 = (高位=?) ^ B5 → 直接用 B5

这里:

  • • 不需要补 0,因为数据字节的作用是立即生效
  • • 原算法 Step 4的查表过程,在step1就完成了

一开始你可能会觉得奇怪:原来是从寄存器里取出高字节查表,现在怎么是先拿数据字节去异或寄存器的高字节再查表?
这就是因为我们把数据“提前”混到寄存器高字节里了,它本质上和原来的方法是一模一样的,只是走的路径不同——这只是数学等价变换

CRC的硬件特性

输入输出反射是怎么来的?

什么是“反射”?

反射 (reflection) = 把比特顺序左右翻转

例子:

  • • 1010 的 4 位反射是 0101。
  • • 1100 的 4 位反射是 0011。
  • • 如果是一个字节:10010011 → 11001001

为什么会有“反射”的 CRC?

因为 硬件 UART 串口或者网口 在传输数据时,是 低位在前 (LSB first),高位在后 (MSB last) 。
但 CRC 多项式除法的数学定义,默认是 高位先来 (MSB first) 。

这就导致硬件工程师在设计 CRC 电路时,干脆直接按 反射后的位流 来做 CRC。
于是:

  • • 每个字节在进入 CRC 计算之前都会被反射;
  • • 最终得到的 CRC 也是 反射过的 CRC 值
             Message (non augmented) >-----+
                                           |
           Bytes   0    1    2    3        v
                +----+----+----+----+      |
                |    |    |    |    |>----XOR
                +----+----+----+----+      |
                          ^                |
                          |                |
                         XOR               |
                          |                |
                +----+----+----+----+0     |
                +----+----+----+----+      v
                +----+----+----+----+      |
                +----+----+----+----+      |
                +----+----+----+----+      |
                +----+----+----+----+      |
                +----+----+----+----+      |
                +----+----+----+----+<-----+
                +----+----+----+----+
                +----+----+----+----+
                +----+----+----+----+
                +----+----+----+----+
                +----+----+----+----+255

软件怎么配合硬件?

本来,最“直观”的方法是:软件里先把每个字节做 bit 反射,再用正常的表驱动 CRC 算法计算。
但有人没这样干,而是 —— 反射了整个算法世界(寄存器、表、初始值、结果),唯独 不反射输入字节

这样做的效果是:

  • • CRC 表格里的每个表项被反射过;
  • • 初始寄存器值被反射过;
  • • 最终得到的 CRC 结果也是反射过的;
  • • 输入数据字节保持原样,不需要额外处理。

这就是所谓的 Reflected Table Algorithm

那么问题来了,为什么不直接反射字节,而是要在算法里用查表法等“间接实现”?

原因主要有这几点:

  1. 1. 性能考虑
    • • 单独做 bit reflection,每次要对 8 位做移位/查表,效率低。
    • • 软件里一般用 查表法 (lookup table) ,提前把反射过的结果放进表里。这样就不需要在运行时对每个字节逐位翻转。
  2. 2. 兼容不同参数的 CRC 标准
    • • 有些 CRC 标准要求 RefIn/RefOut,有些不要求。
    • • 如果在字节输入时就直接反射,那么算法就只能支持 RefIn=True 的情况,不够灵活。
    • • 所以很多实现是:
      • • 如果 RefIn=True:在查表或循环逻辑里做“按位反射”的效果。
      • • 如果 RefIn=False:保持正常 MSB-first 处理。
  3. 3. 硬件/软件一致性
    • • 硬件 CRC 通常是 LSB-first(比如 USB CRC、以太网 CRC32),软件实现要跟硬件保持一致。
    • • 软件通过“反射”或者“查表的不同方向”来模拟硬件行为,而不是在输入阶段真的把字节反过来。

反射算法与普通CRC算法

  • • 普通 CRC 算法:数据按正常顺序,CRC 寄存器和结果也正常。
  • • 反射 CRC 算法:数据字节不反射,但 CRC 表、寄存器、结果全部反射。

最后得到的 CRC 是 普通算法结果的 bit 反射版本

为什么这让人困惑?

因为现在世界上有一半的 CRC 实现用反射算法,另一半不用。
所以你在看别人代码时,如果没弄清楚 算法到底是 MSB-first 还是 LSB-first是否反射寄存器和结果,你几乎没法对上。

这就是为什么很多 CRC 库里会有四个参数:

  • • ReflectIn(输入是否反射)
  • • ReflectOut(输出是否反射)
  • • Init(初始寄存器)
  • • XorOut(结果异或)

其中 ReflectIn/ReflectOut 就是解决这个历史遗留问题的。


反射 CRC 其实就是为了配合硬件“低位先发”的习惯,把整个 CRC 算法对折了一次。


多项式反转

前面讲了“反射的算法”已经够绕的了,现在还有一种让人更晕的东西:反转的多项式(polynomial) 。

CRC 的核心是一个生成多项式(poly)。奇怪的是,一个好的多项式的反射版本通常也是个好多项式
比如:

  • • 如果 G = 11101 是一个“好”的多项式,那么它的反射版本 10111 也一样好。

结果就是,每当一个组织(比如 CCITT)把某个多项式定为标准,多半在实际应用中,人们又忍不住去用它的反射版。
于是,世界上很多 CRC 标准多项式都出现了“两份”:标准版和反射版。我们把这种反射版叫做 “reversed polys” 。

举几个例子:

X25   标准多项式:   1-0001-0000-0010-0001
X25   反转多项式:   1-0000-1000-0001-0001

CRC16 标准多项式:   1-1000-0000-0000-0101
CRC16 反转多项式:   1-0100-0000-0000-0011

反转多项式和反射算法是不是一回事

答案是:不是一回事!

  • • 在“反射算法”里,多项式本身没变,只是字节的处理方式变了(把数据按位反过来输入)。
  • • 但在这里,整个多项式本身被反射了。注意这里包括了那个最高位(最高次项的隐含 1),不是只反射底下的 16 位或 32 位。

所以千万别搞混:

  • • 反射算法:数据流被按位反了,多项式不变。
  • • 反转多项式:直接把整个多项式写成它的镜像形式。

为什么要设置初始值与终值?

除了前面那些反射、多项式的坑,CRC 算法还有两个常见的“变量”:

  1. 1. 寄存器的初始值(Initial Value)
  2. 2. 最终结果要不要再 XOR 一个固定值(Final XOR Value)

举个例子:

  • • CRC32 的标准算法要求:
    • • 初始值设为 FFFFFFFF
    • • 算完之后再和 FFFFFFFF 做一次 XOR,才得到最终结果。

大多数 CRC 算法其实都是把初始值设成 0
但也有一些故意设成非零值。

为什么要搞这些骚操作?

从数学角度讲:

  • • 初始值设什么都不影响 CRC 算法的“强度”(理论上)。
  • • 它只是一个起点,寄存器从这里开始变化。

但从工程角度看:

  • • 有些消息内容更常见,比如一大段开头全是 0 的数据块。
  • • 如果寄存器初始值也是 0,那开头这些全 0 的字节就不会引起寄存器变化(就是所谓的 “盲区 blind spot” )。
  • • 这样 CRC 就“数不到”这些 0,检测能力就被削弱了。

而开头一长串 0 在真实世界的数据(例如压缩包、图像、协议头)里很常见,所以:
很多 CRC 实现会把初始值设成一个非零的数,避免这个盲点。


总结:
初始值和最终 XOR 值是 CRC 算法里的“调味料”。

  • • 初始值不是随便写的 —— 设成非零更稳妥,避免开头全 0 的数据被“忽略”。
  • • 最终 XOR 值则是历史遗留的工程约定,不影响数学原理,但决定了你算出的结果能不能和“标准”对得上。

CRC的参数定义

前面我们看到,CRC 算法会因为各种原因变得千奇百怪:

  • • 多项式的位宽不一样(8/16/32…)。
  • • 多项式的值不一样。
  • • 初始值不同。
  • • 输入字节是否反射(比特顺序倒过来)。
  • • 算法内部的实现细节不同(直接 XOR 进寄存器,还是通过查表)。
  • • 最终结果要不要反射。
  • • 最终结果要不要再 XOR 一个固定值。

如果光靠“名字”,比如叫“CRC16”,根本没法确定到底是哪一种实现,因为“CRC16”就可能有 N 种版本。所以必须要有一种方式 用一组参数把算法说死,这样才能不混淆。因此必须有一个参数模型把这些东西说清楚

这就是Rocksoft™模型,这个模型给了几个参数,每个参数都清楚定义算法的某个方面:

  1. 1. NAME
    算法的名字,比如 "CRC-16"
  2. 2. WIDTH
    CRC 的位宽,比如 16 位,32 位。
    注意:这里是 多项式的位数 – 1
  3. 3. POLY
    多项式,用十六进制写。
    注意:最顶上的那个 1 不写(因为它是固定存在的)。
    例如多项式 10110,写成 06
  4. 4. INIT
    初始值(寄存器一开始的值)。
  5. 5. REFIN
    输入反射(Boolean)。
    • • TRUE:每个字节的比特要反射(bit0 ↔ bit7)。
    • • FALSE:按正常顺序处理。
  6. 6. REFOUT
    输出反射(Boolean)。
    • • TRUE:最后的 CRC 值要反射。
    • • FALSE:保持原样。
  7. 7. XOROUT
    最终值异或常数(十六进制)。
    算完后和这个数异或,得到最终结果。
  8. 8. CHECK
    校验值:规定用 "123456789" 这个字符串跑算法,得到的结果是多少。
    (常用来验证实现对不对。)

举例:CRC-16

按照这个模型,CRC-16 可以被这样定义:

Name   : "CRC-16"
Width  : 16
Poly   : 8005
Init   : 0000
RefIn  : True
RefOut : True
XorOut : 0000
Check  : BB3D

总的来说:Rocksoft™ 模型就是一个 参数清单,用来精确描述 CRC 算法,不管实现方式有多混乱,只要你把这 7 个参数(加上 check 值)写清楚,就能唯一确定是哪一个 CRC。


写在最后

通过这两篇文章,相信你已经对 CRC 有了比较深入的了解。不过,你可能依然会好奇:CRC 的查表究竟是如何生成的?CRC 的算法又是如何推导出来的?考虑到篇幅限制,这里我会在附录 C 中贴出 CRC 表以及其生成算法的 C 语言实现,供大家参考。后续如果大家感兴趣,我也会单独写一篇文章,专门来详细解析这一问题。

往期回顾

每天学一点可综合SystemVerilog语法(合集)

其他技术专栏

FPGA基础:一文吃透CRC算法(上)

附录A

欢迎加入FPGA/IC专属AI知识库,在这里你可以查阅更多知识,无需下载App,微信小程序即可一键使用

点击链接一键加入

附录B

欢迎加入FPGA与数字IC学习交流群!
在这里,你可以提出问题、分享你的学习心得,或是参与到有趣的讨论中。

链接:FPGA芯研社

附录C

//////////////////////////////////////////////////An Implementation of the Model Algorithm
--------------------------------------------
Here is an implementation of the model algorithm in the C programming
language. The implementation consists of a header file(.h) and an
implementation file(.c). If you're reading this document in a
sequential scroller, you can skip this code by searching for the
string "Roll Your Own".

To ensure that the following code is working, configure it for the
CRC-16 and CRC-32 algorithms given above and ensure that they produce
the specified "check" checksum when fed the test string "123456789"
(see earlier).

/******************************************************************************/
/*                             Start of crcmodel.h                            */
/******************************************************************************/
/*                                                                            */
/* Author : Ross Williams (ross@guest.adelaide.edu.au.).                      */
/* Date   : 3 June 1993.                                                      */
/* Status : Public domain.                                                    */
/*                                                                            */
/* Description : This is the header (.h) file for the reference               */
/* implementation of the Rocksoft^tm Model CRC Algorithm. For more            */
/* information on the Rocksoft^tm Model CRC Algorithm, see the document       */
/* titled "A Painless Guide to CRC Error Detection Algorithms" by Ross        */
/* Williams (ross@guest.adelaide.edu.au.). This document is likely to be in   */
/* "ftp.adelaide.edu.au/pub/rocksoft".                                        */
/*                                                                            */
/* Note: Rocksoft is a trademark of Rocksoft Pty Ltd, Adelaide, Australia.    */
/*                                                                            */
/******************************************************************************/
/*                                                                            */
/* How to Use This Package                                                    */
/* -----------------------                                                    */
/* Step 1: Declare a variable of type cm_t. Declare another variable          */
/*         (p_cm say) of type p_cm_t and initialize it to point to the first  */
/*         variable (e.g. p_cm_t p_cm = &cm_t).                               */
/*                                                                            */
/* Step 2: Assign values to the parameter fields of the structure.            */
/*         If you don't know what to assign, see the document cited earlier.  */
/*         For example:                                                       */
/*            p_cm->cm_width = 16;                                            */
/*            p_cm->cm_poly  = 0x8005L;                                       */
/*            p_cm->cm_init  = 0L;                                            */
/*            p_cm->cm_refin = TRUE;                                          */
/*            p_cm->cm_refot = TRUE;                                          */
/*            p_cm->cm_xorot = 0L;                                            */
/*         Note: Poly is specified without its top bit (18005 becomes 8005).  */
/*         Note: Width is one bit less than the raw poly width.               */
/*                                                                            */
/* Step 3: Initialize the instance with a call cm_ini(p_cm);                  */
/*                                                                            */
/* Step 4: Process zero or more message bytes by placing zero or more         */
/*         successive calls to cm_nxt. Example: cm_nxt(p_cm,ch);              */
/*                                                                            */
/* Step 5: Extract the CRC value at any time by calling crc = cm_crc(p_cm);   */
/*         If the CRC is a 16-bit value, it will be in the bottom 16 bits.    */
/*                                                                            */
/******************************************************************************/
/*                                                                            */
/* Design Notes                                                               */
/* ------------                                                               */
/* PORTABILITY: This package has been coded very conservatively so that       */
/* it will run on as many machines as possible. For example, all external     */
/* identifiers have been restricted to 6 characters and all internal ones to  */
/* 8 characters. The prefix cm (for Crc Model) is used as an attempt to avoid */
/* namespace collisions. This package is endian independent.                  */
/*                                                                            */
/* EFFICIENCY: This package (and its interface) is not designed for           */
/* speed. The purpose of this package is to act as a well-defined reference   */
/* model for the specification of CRC algorithms. If you want speed, cook up  */
/* a specific table-driven implementation as described in the document cited  */
/* above. This package is designed for validation only; if you have found or  */
/* implemented a CRC algorithm and wish to describe it as a set of parameters */
/* to the Rocksoft^tm Model CRC Algorithm, your CRC algorithm implementation  */
/* should behave identically to this package under those parameters.          */
/*                                                                            */
/******************************************************************************/

/* The following #ifndef encloses this entire */
/* header file, rendering it indempotent.     */
#ifndef CM_DONE
#define CM_DONE

/******************************************************************************/

/* The following definitions are extracted from my style header file which    */
/* would be cumbersome to distribute with this package. The DONE_STYLE is the */
/* idempotence symbol used in my style header file.                           */

#ifndef DONE_STYLE

typedefunsignedlong   ulong;
typedefunsigned        bool;
typedefunsignedchar * p_ubyte_;

#ifndef TRUE
#define FALSE 0
#define TRUE  1
#endif

/* Change to the second definition if you don't have prototypes. */
#define P_(A) A
/* #define P_(A) () */

/* Uncomment this definition if you don't have void. */
/* typedef int void; */

#endif

/******************************************************************************/

/* CRC Model Abstract Type */
/* ----------------------- */
/* The following type stores the context of an executing instance of the  */
/* model algorithm. Most of the fields are model parameters which must be */
/* set before the first initializing call to cm_ini.                      */
typedefstruct
  {
   int   cm_width;   /* Parameter: Width in bits [8,32].       */
   ulong cm_poly;    /* Parameter: The algorithm's polynomial. */
   ulong cm_init;    /* Parameter: Initial register value.     */
   bool  cm_refin;   /* Parameter: Reflect input bytes?        */
   bool  cm_refot;   /* Parameter: Reflect output CRC?         */
   ulong cm_xorot;   /* Parameter: XOR this to output CRC.     */

   ulong cm_reg;     /* Context: Context during execution.     */
  } cm_t;
typedefcm_t *p_cm_t;

/******************************************************************************/

/* Functions That Implement The Model */
/* ---------------------------------- */
/* The following functions animate the cm_t abstraction. */

void cm_ini P_((p_cm_t p_cm));
/* Initializes the argument CRC model instance.          */
/* All parameter fields must be set before calling this. */

void cm_nxt P_((p_cm_t p_cm,int ch));
/* Processes a single message byte [0,255]. */

void cm_blk P_((p_cm_t p_cm,p_ubyte_ blk_adr,ulong blk_len));
/* Processes a block of message bytes. */

ulong cm_crc P_((p_cm_t p_cm));
/* Returns the CRC value for the message bytes processed so far. */

/******************************************************************************/

/* Functions For Table Calculation */
/* ------------------------------- */
/* The following function can be used to calculate a CRC lookup table.        */
/* It can also be used at run-time to create or check static tables.          */

ulong cm_tab P_((p_cm_t p_cm,int index));
/* Returns the i'th entry for the lookup table for the specified algorithm.   */
/* The function examines the fields cm_width, cm_poly, cm_refin, and the      */
/* argument table index in the range [0,255] and returns the table entry in   */
/* the bottom cm_width bytes of the return value.                             */

/******************************************************************************/

/* End of the header file idempotence #ifndef */
#endif

/******************************************************************************/
/*                             End of crcmodel.h                              */
/******************************************************************************/


/******************************************************************************/
/*                             Start of crcmodel.c                            */
/******************************************************************************/
/*                                                                            */
/* Author : Ross Williams (ross@guest.adelaide.edu.au.).                      */
/* Date   : 3 June 1993.                                                      */
/* Status : Public domain.                                                    */
/*                                                                            */
/* Description : This is the implementation (.c) file for the reference       */
/* implementation of the Rocksoft^tm Model CRC Algorithm. For more            */
/* information on the Rocksoft^tm Model CRC Algorithm, see the document       */
/* titled "A Painless Guide to CRC Error Detection Algorithms" by Ross        */
/* Williams (ross@guest.adelaide.edu.au.). This document is likely to be in   */
/* "ftp.adelaide.edu.au/pub/rocksoft".                                        */
/*                                                                            */
/* Note: Rocksoft is a trademark of Rocksoft Pty Ltd, Adelaide, Australia.    */
/*                                                                            */
/******************************************************************************/
/*                                                                            */
/* Implementation Notes                                                       */
/* --------------------                                                       */
/* To avoid inconsistencies, the specification of each function is not echoed */
/* here. See the header file for a description of these functions.            */
/* This package is light on checking because I want to keep it short and      */
/* simple and portable (i.e. it would be too messy to distribute my entire    */
/* C culture (e.g. assertions package) with this package.                     */
/*                                                                            */
/******************************************************************************/

#include "crcmodel.h"

/******************************************************************************/

/* The following definitions make the code more readable. */

#define BITMASK(X) (1L << (X))
#define MASK32 0xFFFFFFFFL
#define LOCAL static

/******************************************************************************/

LOCAL ulong reflect P_((ulong v,int b));
LOCAL ulong reflect(v,b)
/* Returns the value v with the bottom b [0,32] bits reflected. */
/* Example: reflect(0x3e23L,3) == 0x3e26                        */
ulong v;
int   b;
{
int   i;
 ulong t = v;
for (i=0; i<b; i++)
   {
    if (t & 1L)
       v|=  BITMASK((b-1)-i);
    else
       v&= ~BITMASK((b-1)-i);
    t>>=1;
   }
return v;
}

/******************************************************************************/

LOCAL ulong widmask P_((p_cm_t));
LOCAL ulong widmask(p_cm)
/* Returns a longword whose value is (2^p_cm->cm_width)-1.     */
/* The trick is to do this portably (e.g. without doing <<32). */
p_cm_t p_cm;
{
return (((1L<<(p_cm->cm_width-1))-1L)<<1)|1L;
}

/******************************************************************************/

voidcm_ini(p_cm)
p_cm_t p_cm;
{
 p_cm->cm_reg = p_cm->cm_init;
}

/******************************************************************************/

voidcm_nxt(p_cm,ch)
p_cm_t p_cm;
int    ch;
{
int   i;
 ulong uch  = (ulong) ch;
 ulong topbit = BITMASK(p_cm->cm_width-1);

if (p_cm->cm_refin) uch = reflect(uch,8);
 p_cm->cm_reg ^= (uch << (p_cm->cm_width-8));
for (i=0; i<8; i++)
   {
    if (p_cm->cm_reg & topbit)
       p_cm->cm_reg = (p_cm->cm_reg << 1) ^ p_cm->cm_poly;
    else
       p_cm->cm_reg <<= 1;
    p_cm->cm_reg &= widmask(p_cm);
   }
}

/******************************************************************************/

voidcm_blk(p_cm,blk_adr,blk_len)
p_cm_t   p_cm;
p_ubyte_ blk_adr;
ulong    blk_len;
{
while (blk_len--) cm_nxt(p_cm,*blk_adr++);
}

/******************************************************************************/

ulong cm_crc(p_cm)
p_cm_t p_cm;
{
if (p_cm->cm_refot)
    return p_cm->cm_xorot ^ reflect(p_cm->cm_reg,p_cm->cm_width);
else
    return p_cm->cm_xorot ^ p_cm->cm_reg;
}

/******************************************************************************/

ulong cm_tab(p_cm,index)
p_cm_t p_cm;
int    index;
{
int   i;
 ulong r;
 ulong topbit = BITMASK(p_cm->cm_width-1);
 ulong inbyte = (ulong) index;

if (p_cm->cm_refin) inbyte = reflect(inbyte,8);
 r = inbyte << (p_cm->cm_width-8);
for (i=0; i<8; i++)
    if (r & topbit)
       r = (r << 1) ^ p_cm->cm_poly;
    else
       r<<=1;
if (p_cm->cm_refin) r = reflect(r,p_cm->cm_width);
return r & widmask(p_cm);
}

/******************************************************************************/
/*                             End of crcmodel.c                              */
/******************************************************************************/


//////////////////////////////////////////////////Roll Your Own Table-Driven Implementation
---------------------------------------------
Despite all the fuss I've made about understanding and defining CRC
algorithms, the mechanics of their high-speed implementation remains
trivial. There are really only two forms: normal and reflected. Normal
shifts to the left and covers the case of algorithms with Refin=FALSE
and Refot=FALSE. Reflected shifts to the right and covers algorithms
with both those parameters true. (If you want one parameter true and
the other false, you'll have to figure it out for yourself!) The
polynomial is embedded in the lookup table(to be discussed). The
other parameters, Init and XorOt can be coded as macros. Here is the
32-bit normal form(the 16-bit form is similar).

   unsignedlongcrc_normal();
   unsignedlongcrc_normal(blk_adr,blk_len)
   unsignedchar *blk_adr;
   unsignedlong  blk_len;
   {
    unsignedlong crc = INIT;
    while (blk_len--)
       crc = crctable[((crc>>24) ^ *blk_adr++) & 0xFFL] ^ (crc << 8);
    return crc ^ XOROT;
   }

Here is the reflected form:

   unsignedlongcrc_reflected();
   unsignedlongcrc_reflected(blk_adr,blk_len)
   unsignedchar *blk_adr;
   unsignedlong  blk_len;
   {
    unsignedlong crc = INIT_REFLECTED;
    while (blk_len--)
       crc = crctable[(crc ^ *blk_adr++) & 0xFFL] ^ (crc >> 8));
    return crc ^ XOROT;
   }

Note: I have carefully checked the above two code fragments, but I
haven't actually compiled or tested them. This shouldn't matter to
you, as, no matter WHAT you code, you will always be able to tell if
you have got it right by running whatever you have created against the
reference model given earlier. The code fragments above are really
just a rough guide. The reference model is the definitive guide.

Note: If you don't care much about speed, just use the reference model
code!


//////////////////////////////////////////////////Generating A Lookup Table
-----------------------------
The only component missing from the normal and reversed code fragments
in the previous section is the lookup table. The lookup table can be
computed at run time using the cm_tab function of the model package
given earlier, or can be pre-computed and inserted into the C program.
In either case, it should be noted that the lookup table depends only
on the POLY and RefIn (and RefOt) parameters. Basically, the
polynomial determines the table, but you can generate a reflected
table too if you want to use the reflected form above.

The following program generates any desired 16-bit or 32-bit lookup
table. Skip to the word "Summary" if you want to skip over this code.



/******************************************************************************/
/*                             Start of crctable.c                            */
/******************************************************************************/
/*                                                                            */
/* Author  : Ross Williams (ross@guest.adelaide.edu.au.).                     */
/* Date    : 3 June 1993.                                                     */
/* Version : 1.0.                                                             */
/* Status  : Public domain.                                                   */
/*                                                                            */
/* Description : This program writes a CRC lookup table (suitable for         */
/* inclusion in a C program) to a designated output file. The program can be  */
/* statically configured to produce any table covered by the Rocksoft^tm      */
/* Model CRC Algorithm. For more information on the Rocksoft^tm Model CRC     */
/* Algorithm, see the document titled "A Painless Guide to CRC Error          */
/* Detection Algorithms" by Ross Williams (ross@guest.adelaide.edu.au.). This */
/* document is likely to be in "ftp.adelaide.edu.au/pub/rocksoft".            */
/*                                                                            */
/* Note: Rocksoft is a trademark of Rocksoft Pty Ltd, Adelaide, Australia.    */
/*                                                                            */
/******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include "crcmodel.h"

/******************************************************************************/

/* TABLE PARAMETERS                                                           */
/* ================                                                           */
/* The following parameters entirely determine the table to be generated. You */
/* should need to modify only the definitions in this section before running  */
/* this program.                                                              */
/*                                                                            */
/*    TB_FILE  is the name of the output file.                                */
/*    TB_WIDTH is the table width in bytes (either 2 or 4).                   */
/*    TB_POLY  is the "polynomial", which must be TB_WIDTH bytes wide.        */
/*    TB_REVER indicates whether the table is to be reversed (reflected).     */
/*                                                                            */
/* Example:                                                                   */
/*                                                                            */
/*    #define TB_FILE   "crctable.out"                                        */
/*    #define TB_WIDTH  2                                                     */
/*    #define TB_POLY   0x8005L                                               */
/*    #define TB_REVER  TRUE                                                  */

#define TB_FILE   "crctable.out"
#define TB_WIDTH  4
#define TB_POLY   0x04C11DB7L
#define TB_REVER  TRUE

/******************************************************************************/

/* Miscellaneous definitions. */

#define LOCAL static
FILE *outfile;
#define WR(X) fprintf(outfile,(X))
#define WP(X,Y) fprintf(outfile,(X),(Y))

/******************************************************************************/

LOCAL void chk_err P_((char *));
LOCAL void chk_err (mess)
/* If mess is non-empty, write it out and abort. Otherwise, check the error   */
/* status of outfile and abort if an error has occurred.                      */
char *mess;
{
 if (mess[0] != 0   ) {printf("%sn",mess); exit(EXIT_FAILURE);}
 if (ferror(outfile)) {perror("chk_err");   exit(EXIT_FAILURE);}
}

/******************************************************************************/

LOCAL void chkparam P_((void));
LOCAL void chkparam ()
{
 if ((TB_WIDTH != 2) && (TB_WIDTH != 4))
    chk_err("chkparam: Width parameter is illegal.");
 if ((TB_WIDTH == 2) && (TB_POLY & 0xFFFF0000L))
    chk_err("chkparam: Poly parameter is too wide.");
 if ((TB_REVER != FALSE) && (TB_REVER != TRUE))
    chk_err("chkparam: Reverse parameter is not boolean.");
}

/******************************************************************************/

LOCAL void gentable P_((void));
LOCAL void gentable ()
{
 WR("/*****************************************************************/n");
 WR("/*                                                               */n");
 WR("/* CRC LOOKUP TABLE                                              */n");
 WR("/* ================                                              */n");
 WR("/* The following CRC lookup table was generated automagically    */n");
 WR("/* by the Rocksoft^tm Model CRC Algorithm Table Generation       */n");
 WR("/* Program V1.0 using the following model parameters:            */n");
 WR("/*                                                               */n");
 WP("/*    Width   : %1lu bytes.                                         */n",
    (ulong) TB_WIDTH);
 if (TB_WIDTH == 2)
 WP("/*    Poly    : 0x%04lX                                           */n",
    (ulong) TB_POLY);
 else
 WP("/*    Poly    : 0x%08lXL                                      */n",
    (ulong) TB_POLY);
 if (TB_REVER)
 WR("/*    Reverse : TRUE.                                            */n");
 else
 WR("/*    Reverse : FALSE.                                           */n");
 WR("/*                                                               */n");
 WR("/* For more information on the Rocksoft^tm Model CRC Algorithm,  */n");
 WR("/* see the document titled "A Painless Guide to CRC Error        */n");
 WR("/* Detection Algorithms" by Ross Williams                        */n");
 WR("/* (ross@guest.adelaide.edu.au.). This document is likely to be  */n");
 WR("/* in the FTP archive "ftp.adelaide.edu.au/pub/rocksoft".        */n");
 WR("/*                                                               */n");
 WR("/*****************************************************************/n");
 WR("n");
 switch (TB_WIDTH)
   {
    case 2: WR("unsigned short crctable[256] =n{n"); break;
    case 4: WR("unsigned long  crctable[256] =n{n"); break;
    default: chk_err("gentable: TB_WIDTH is invalid.");
   }
 chk_err("");

 {
  int i;
  cm_t cm;
  char *form    = (TB_WIDTH==2) ? "0x%04lX" : "0x%08lXL";
  int   perline = (TB_WIDTH==2) ? 8 : 4;

  cm.cm_width = TB_WIDTH*8;
  cm.cm_poly  = TB_POLY;
  cm.cm_refin = TB_REVER;

  for (i=0; i<256; i++)
    {
     WR(" ");
     WP(form,(ulong) cm_tab(&cm,i));
     if (i != 255) WR(",");
     if (((i+1) % perline) == 0) WR("n");
     chk_err("");
    }

 WR("};n");
 WR("n");
 WR("/*****************************************************************/n");
 WR("/*                   End of CRC Lookup Table                     */n");
 WR("/*****************************************************************/n");
 WR("");
 chk_err("");
}
}

/******************************************************************************/

main ()
{
 printf("n");
 printf("Rocksoft^tm Model CRC Algorithm Table Generation Program V1.0n");
 printf("-------------------------------------------------------------n");
 printf("Output file is "%s".n",TB_FILE);
 chkparam();
 outfile = fopen(TB_FILE,"w"); chk_err("");
 gentable();
 if (fclose(outfile) != 0)
    chk_err("main: Couldn't close output file.");
 printf("nSUCCESS: The table has been successfully written.n");
}

/******************************************************************************/
/*                             End of crctable.c                              */
/******************************************************************************/

#FPGA #数字IC #CRC硬件实现 #表驱动算法 #直接表优化 #输入输出反射 #多项式反转 #Rocksoft参数模型 #初始值盲区 #高低频场景优化 #时序优化 #预计算表 #异或特性 #CRC反射算法 #XorOut参数