在很多以太网、SATA 或其他高速接口项目中,我们经常会接触到 CRC(循环冗余校验)。通常,工程师会通过网站或软件工具生成一整套带异或操作的组合逻辑来实现 CRC 算法,而不去深入理解其原理——只知道可以这样用。事实上,在大多数情况下,确实不需要了解 CRC 的具体实现方法,这种黑盒化的使用方式在低频窄位宽场景下确实可行。 但是,当你的系统时钟非常高,例如 800MHz、1GHz,或数据位宽很大如 256bit、512bit时,工具生成的完整组合逻辑往往会因为时序问题而无法跑通。这时,就需要对 CRC 进行拆分设计,以优化时序和逻辑面积。如果不了解 CRC 的本质和计算方法,就很难进行这样的优化设计。 因此,理解 CRC 的原理和硬件实现方式非常必要。本文将分为两章,系统介绍 CRC 的发展历程与硬件高效实现方法。在本篇,我们将讲解: 掌握这些内容之后,下一步将介绍如何实现高效的硬件 CRC 算法。 CRC前传:错误检测 CRC初识:CRC的关键元素 CRC硬件算法初探:简单CRC算法 错误检测的目的,就是让接收方能判断一条在“嘈杂”(可能引入错误)的信道里传输过来的消息有没有被破坏。 做法是这样的: 举个简单例子: 接收方重新计算: 跟附带的校验和 当然,如果校验和本身在传输中被改了,就有可能把正常的消息误判成错误的,这种情况叫安全型错误(宁可多报错,不漏报)。 更糟的是,还有一种情况:数据和校验和都被改了,但恰好改得一致,这样接收方就完全察觉不到了。这叫危险型错误,而且是完全无法彻底避免的。我们只能通过增加校验和的信息量(比如从 1 字节扩展到 2 字节)来尽量降低这种风险。 除了简单的加和,还有更复杂的错误检测方法,比如对消息做复杂变换,加入更多冗余信息。 在前面那个例子里,我们用“把所有字节加起来再对 256 取余”的方法检测消息是否被破坏。 这种方法能检测出错误没错,但它太简单了,所以也有缺陷。 虽然消息明显变了,但因为 (8+20+5) mod 256 仍然是 33,所以校验和对不上。 为了加强校验,我们可以把寄存器从 8 位扩展到 16 位(也就是对 65536 取余),这样理论上出错没被发现的概率能从 1/256 降到 1/65536。 听起来不错,但这里有个坑: 解决这个问题的方法就是: 所以,一个强的校验和算法至少需要满足两个条件: 顺便一提,“checksum”这个词原来是指简单的加法校验,但现在已经被扩展到更复杂的算法,比如 CRC(循环冗余校验)。CRC 在第二个条件上做得很好,而且可以灵活配置宽度。 在寻找比“加法”更复杂的校验方式时,我们可能会想到各种奇葩的办法: 想法可以天马行空,但其实没必要搞这么夸张。只要从加法往下一个算术步骤——除法,就够了。 加法做校验太弱,而除法在某些条件下就能做出很强的校验算法,尤其是当除数的位宽接近校验和寄存器位宽时。 CRC(循环冗余校验)的核心思想就是: 接收方收到数据后,再用同样的除数除一遍,看余数是不是和附带的校验和一致,如果一致,就说明数据大概率没出错。 举个小例子: 这时候,发送方发出去的内容是: 接收方收到后再做一遍 为什么除法比加法好? 在上一节里我们把 CRC 除法当作普通二进制除法(无进位 XOR), “CRC 算法使用某个多项式” 那是因为在数学上,我们把二进制数看作一个多项式的系数序列(通俗讲就是把二进制数变成多项式的形式) 。 例: 简写为: 这样,消息和除数都能表示成多项式。 如果我们想计算 按普通多项式乘法展开: 在普通算术里,我们知道 CRC 不这样做。 于是: 中, 最后结果: 抛开多项式选择,我们现在关注 CRC 计算的核心算术特点: CRC 加法与普通二进制加法的唯一区别:不进位。 例如: 位运算规则: 减法在 CRC 算术里和加法完全一样: 位运算规则: 所以: CRC 的加法、减法、本质上就是 XOR(异或) 在普通二进制里, 例: 也就是说,同一个数可以通过加或减同样的值变成另一个数,因此无法定义严格的大小顺序。 CRC 乘法就是按位移后的加法(XOR)求和: 例: CRC 除法类似长除法,但判断“能除”是用最高位 1 的位置来比较的: 例: 最终余数: 在 CRC 算术中: 例: 如果 A = 不断把某个固定模式(poly)在不同位置 XOR 进数据里(通俗的讲 (信息+CRC校验) 是可以被poly整除的,不能整除CRC校验就错了) 原始消息: 在末尾追加 W 个 0, 附加 这就是 CRC 校验值(checksum)。 将原消息和 CRC 码拼接后发送: 接收端有两种常见做法: 方法 A 方法 B 选择 CRC 的多项式(poly)其实是一门“玄学”,细节可以参考 Tanenbaum 的书(p.130–132),那里讲得很清楚。 先记住一点:发送出去的消息 T(消息T尾部带了CRC校验) 一定是 poly 的倍数。 如果传输过程中消息被破坏,接收方收到的其实是: 其中 E 是“错误向量”(+ 代表 XOR)。 因为 T mod G = 0,剩下的就全看 E 能不能被 G 整除。 错误形态: 解决方法:选一个 G,至少有两个 1 位。这样它的倍数不可能是只有一个 1 的数,所以单比特错误一定能检出。 错误形态: 解决方法:选一个 G,它的倍数不包含以下模式: 有些 G 满足这个条件,比如 如果想让所有“1 的个数是奇数”的错误都能被发现,可以选一个 1 的个数是偶数 的 G。 错误形态: 即中间连续一段是 1,其它全是 0。 让我们从最朴素的实现开始 ,这个版本可能很慢,但能清晰展现算法本质。理解这个基础后,我们会像搭积木一样,一步步添加优化技巧,最终打造出那个你在实际项目中常见的、高效的表驱动实现。 此方法看似稍显杂乱,但其本质是通过从消息中不断”减去”多项式的不同幂次(即移位操作),直至剩余仅余余数。若对此理解困难,可参考CRC计算的完整步骤中长除法的示例说明。 假设: 算法伪代码: 注意: 可以把它想象成一个小机器: 以 Poly=11011,W=4 为例: 通过上述内容,相信你已经对CRC校验的数学原理和基础实现有了系统性的理解。在后续章节中,我们将深入探讨面向高性能计算场景的CRC硬件优化架构,包括并行化处理等关键技术。 欢迎加入FPGA/IC专属AI知识库,在这里你可以查阅更多知识,无需下载App,微信小程序即可一键使用。 欢迎加入FPGA与数字IC学习交流群! #FPGA #数字IC #EDA #Verilog #CRC #循环冗余校验 #错误检测 #硬件实现 #多项式除法 #模2运算 #校验和 #数据完整性 #通信协议 #高速接口 #以太网 #SATA #并行计算 #时序优化前言
导读
CRC前传:错误检测
错误检测的目的
发送方会根据消息本身计算出一个特定的值(叫做校验和,checksum),然后把它加到消息后面一起发出去。
接收方收到消息后,用同样的计算方法,再算一遍校验和,然后和消息中附带的校验和对比一下。
假设我们的校验和算法很粗暴,就是“把消息里的所有字节加起来,再对 256 取余”。
比如:原始消息: 6 23 4
加上校验和:6 23 4 33 (33 = (6+23+4) mod 256)
传输后收到:6 27 4 33 (中间的 23 被传成了 27)
6 + 27 + 4 = 37
33
不一样 → 发现出错!
但本文只讨论 CRC(循环冗余校验)这种方法,它属于不改动原始数据,只在末尾追加一个校验和的算法,格式大概是:<原始数据> <校验和>
加法错误检测的缺陷
比如:原始消息: 6 23 4
加上校验和: 6 23 4 33 (33 = (6+23+4) mod 256)
传输后收到: 6 27 4 33
如果数据在传输中被随机改动,有 1/256 的概率 恰好不会被发现。比如:原始消息: 6 23 4
加上校验和: 6 23 4 33
传输后收到: 8 20 5 33
如果公式太简单(像这种单纯的加法),每个输入字节只会影响寄存器的一小部分位,不管寄存器多宽,还是可能漏检。
比如上面的例子,即使校验寄存器有一兆字节宽,错误也能逃过去。
换一个更复杂的公式,让每一个输入字节都有可能影响寄存器的任何一位。
除法错误检测的优势
假设消息是两个字节 (6, 23)
,用十六进制就是 0x0617
,二进制写出来是 0000 0110 0001 0111
。
我们用一个 4 位(1 字节宽)校验寄存器,除数是二进制的 1001
(十进制 9)。
做二进制长除法,最后的余数是 0010
(十进制 2)。 ...0000010101101 = 00AD = 173 = QUOTIENT
____-___-___-___-
9= 1001 ) 0000011000010111 = 0617 = 1559 = DIVIDEND
DIVISOR 0000.,,....,.,,,
----.,,....,.,,,
0000,,....,.,,,
0000,,....,.,,,
----,,....,.,,,
0001,....,.,,,
0000,....,.,,,
----,....,.,,,
0011....,.,,,
0000....,.,,,
----....,.,,,
0110...,.,,,
0000...,.,,,
----...,.,,,
1100..,.,,,
1001..,.,,,
====..,.,,,
0110.,.,,,
0000.,.,,,
----.,.,,,
1100,.,,,
1001,.,,,
====,.,,,
0111.,,,
0000.,,,
----.,,,
1110,,,
1001,,,
====,,,
1011,,
1001,,
====,,
0101,
0000,
----
1011
1001
====
0010 = 02 = 2 = REMAINDER
0x0617(原消息) + 0x2(校验和)
0x0617 ÷ 9
,如果余数还是 2,就认为传输没出错。
CRC初识:CRC的关键元素
CRC 算法里的“多项式”是怎么回事?
但在很多书里,你会听到说:
“CRC 运算是多项式算术”
1. 把二进制数变成多项式
普通数 23
(十进制)= 17
(十六进制)= 10111
(二进制)
如果把每一位二进制数当作一个多项式的系数(0 或 1),就得到:1*x^4 + 0*x^3 + 1*x^2 + 1*x^1 + 1*x^0
x^4 + x^2 + x^1 + x^0
2. 多项式乘法(正常规则)
1101 × 1011
,对应多项式是:(x^3 + x^2 + 1) × (x^3 + x^1 + 1)
= (x^6 + x^4 + x^3)
+ (x^5 + x^3 + x^2)
+ (x^3 + x^1 + 1)
= x^6 + x^5 + x^4 + 3*x^3 + x^2 + x^1 + 1
x=2
,可以把 3*x^3
进位成:x^7 + x^3 + x^2 + x^1 + 1
3. CRC 用的多项式算术:模 2 & 无进位
CRC 的多项式运算是在模 2 下进行的:
x^6 + x^5 + x^4 + 3*x^3 + x^2 + x^1 + 1
3*x^3
按模 2 变成:1*x^3 (因为 3 mod 2 = 1)
x^6 + x^5 + x^4 + x^3 + x^2 + x^1 + 1
4. 结论
CRC 算术的基本规则(无进位运算)
所有运算都是二进制的,并且没有进位。
虽然很多人叫它“多项式算术”,但这里我们直接称它 CRC 算术。1. 加法和减法
这意味着每一位的结果只取决于这一位对应的两个输入位,不影响其他位。 10011011
+11001010
---------
01010001
0 + 0 = 0
0 + 1 = 1
1 + 0 = 1
1 + 1 = 0 (无进位)
10011011
-11001010
---------
01010001
0 - 0 = 0
0 - 1 = 1 (借位不需要,因为是 XOR)
1 - 0 = 1
1 - 1 = 0
XOR 也是它自己的逆运算(做一次 XOR 再 XOR 同样的数,就回到原值,A XOR B = C,C XOR B = A)。
2. CRC 算术里的大小关系失效
1010
(10)显然比 10
(2)大。
但在 CRC 算术中,这种“大小”比较没意义。1010 = 1010 + 0011
1010 = 1010 - 0011
3. 乘法
1101
x 1011
----
1101
1101.
0000..
1101...
-------
1111111 Note: The sum uses CRC addition
-------
4. 除法
1100001010
_______________
10011 ) 11010110110000
10011,,.,,....
-----,,.,,....
10011,.,,....
10011,.,,....
-----,.,,....
00001.,,....
00000.,,....
-----.,,....
00010,,....
00000,,....
-----,,....
00101,....
00000,....
-----,....
01011....
00000....
-----....
10110...
10011...
-----...
01010..
00000..
-----..
10100.
10011.
-----.
01110
00000
-----
1110 = Remainder
1110
5. 倍数与可整除的含义
A = 0111010110
B = 11
A = B*2 + B*(2^4) + B*(2^5) + 2*(2^7)
0111010110
= .......11.
+ ....11....
+ ...11.....
.11.......
0111010111
,就不可能用 B 的若干位移 XOR 出来,所以不可整除。
6. CRC算术核心思想
这样既能构造倍数,也能做除法检测。CRC 计算的完整步骤
1. 选择一个多项式(poly)
Poly = 10011 → W = 4
2. 准备消息
1101011011
W
个0后,信息相当于被左移W
位(即乘以 2^W),为后续的模2除法(CRC计算)预留空间。这样可以在信息位后腾出位置,容纳最终的CRC校验码:1101011011 0000
3. 在 CRC 算术(无进位 XOR)下做二进制长除法
1100001010 = Quotient (nobody cares about the quotient)
_______________
10011 ) 11010110110000 = Augmented message (1101011011 + 0000)
=Poly 10011,,.,,....
-----,,.,,....
10011,.,,....
10011,.,,....
-----,.,,....
00001.,,....
00000.,,....
-----.,,....
00010,,....
00000,,....
-----,,....
00101,....
00000,....
-----,....
01011....
00000....
-----....
10110...
10011...
-----...
01010..
00000..
-----..
10100.
10011.
-----.
01110
00000
-----
1110 = Remainder = THE CHECKSUM!!!!
4. 得到 CRC 校验码
Remainder = 1110
5. 发送数据
1101011011 1110
6. 接收端校验方式
0
,则校验通过如何选择CRC多项式(poly)
我这里只是想劝你——千万别自己随便拍脑袋造一个 poly,不然你会踩坑。如果你不关心为什么某个 poly 更好,只想直接搞个靠谱又快的实现,那就直接用附录A列出的“算术上可靠”的标准 poly,别自己发明。1. CRC 校验的数学背景
原因是:
T + E
接收方用 poly G 去除:(T + E) mod G = E mod G
如果 E 是 G 的倍数,那么错误检测不出来,所以选 poly 的目标就是:找一个 G,让它的倍数尽量不像传输线路里常见的噪声模式。
2. 常见的错误类型和 poly 选择原则
单比特错误(Single-Bit Error)
E = 1000...0000
双比特错误(Two-Bit Error)
E = 100...000100...000
11, 101, 1001, 10001, 100001, ...
(15,14,1)
这个多项式就不会整除长度小于 1...1
(32767 个 0)的模式。奇数个比特错误(Odd Number of Bit Errors)
原因:
所以用偶数个 1 的 poly,可以确保奇数个比特错误全被发现。
Tanenbaum 还指出,更严格的条件是:G 要是 11 的倍数。突发错误(Burst Error)
E = 000...000 111...111 000...000
解决方法:
Tanenbaum 说,如果突发错误的长度大于 W(poly 宽度),漏检概率是 (0.5)^W
。
3. 一些常用的可靠多项式
(16,12,5,0)
→ X.25 标准(16,15,2,0)
→ 常见的 “CRC-16”
(32,26,23,22,16,12,11,10,8,7,5,4,2,1,0)
→ Ethernet 用的 CRC-32CRC硬件算法初探:简单CRC算法
核心思路
步骤流程
3 2 1 0 Bits
+---+---+---+---+
Pop! <-- | | | | | <----- Augmented message
+---+---+---+---+
1 1 0 1 1 = The Poly
R = 0 ; W 位寄存器初始化为 0
M = 消息 + W 个 0 ; 扩展消息
for bit in M: ; 从最高位到最低位
out = R[W-1] ; 记录左边弹出的最高位
R = (R << 1) | bit ; 左移一位,把新位送到最低位
if out == 1: ; 如果弹出位是 1
R = R XOR poly_low ; 与多项式的低 W 位异或
poly_low
是 去掉最高位的多项式位串,因为最高位对应的“除法对齐”已经由 out==1 触发了。说白了就是pop出来的1和poly最高位的1抵消掉了,所以不用关注最高位。
动作对照
示例
R = R XOR 1011
是
写在最后
往期回顾
附录A
附录B
附录C
在这里,你可以提出问题、分享你的学习心得,或是参与到有趣的讨论中。附录D