CRC实战宝典:从原理到代码,全面攻克循环冗余校验
github开源:CRC软硬件协同测试项目
CRC 简介
CRC(循环冗余校验)是一种强大的错误检测技术,广泛应用于数字网络和存储系统。它是确保数据完整性的重要方法,能够检测传输或存储过程中对原始数据的意外更改。
什么是 CRC?
CRC 是一种数学算法,具有以下特点:
- 将数据视为有限域中的多项式表示
- 使用预定义的多项式(称为生成多项式)进行二进制除法
- 生成固定长度的校验和,附加到消息末尾
- 能够高概率地检测出常见的传输错误
CRC 的优势在于其能够检测:
- 所有单比特错误
- 所有双比特错误(在标准 CRC 中)
- 任何奇数个比特错误
- 突发错误(基于多项式长度的一定范围内)
CRC 原理
多项式表示
在 CRC 中,数据和生成多项式都表示为具有二进制系数(0 或 1)的多项式。例如:
- 二进制序列
1101
表示多项式 x³ + x² + 1 - 二进制序列
10011
表示多项式 x⁴ + x + 1
CRC 参数
完整的 CRC 算法由以下参数定义:
- 位宽(Width):CRC 值的比特数
- 多项式(Polynomial):用于除法的生成多项式
- 初始值(Init):CRC 寄存器的初始值
- 输入反转(RefIn):输入字节是否反转(位序反转)
- 输出反转(RefOut):最终 CRC 是否反转
- 输出异或值(XorOut):与最终 CRC 异或的值
这些参数定义了不同的 CRC 标准(CRC-8、CRC-16、CRC-32 等)
CRC 计算过程
CRC 计算的高级过程包括:
发送方:
- 在原始数据后附加 k-1 个零(其中 k 是 CRC 的位宽)
- 使用模 2 除法,用生成多项式除以上述结果
- 余数即为 CRC 值,附加到原始数据后
接收方:
- 用相同的生成多项式除以接收到的数据(包括 CRC)
- 如果余数为零,则认为数据无错误
- 如果余数非零,则检测到错误
模 2 二进制除法
CRC 计算的核心是模 2 二进制除法,其特点是:
- 使用异或(XOR)操作代替减法
- 没有进位或借位操作
- 按位处理消息
- 可以在硬件和软件中高效实现
常见 CRC 标准
CRC 类型 | 位宽 | 多项式 | 初始值 | 输入反转 | 输出反转 | 输出异或值 | 常见用途 |
---|---|---|---|---|---|---|---|
CRC-8 | 8 | 0x07 | 0x00 | 否 | 否 | 0x00 | ATM 头部 |
CRC-8/CDMA2000 | 8 | 0x9B | 0xFF | 否 | 否 | 0x00 | 移动网络 |
CRC-16/CCITT | 16 | 0x1021 | 0xFFFF | 否 | 否 | 0x0000 | HDLC, 蓝牙 |
CRC-16/IBM | 16 | 0x8005 | 0x0000 | 是 | 是 | 0x0000 | USB, SCSI |
CRC-32 | 32 | 0x04C11DB7 | 0xFFFFFFFF | 是 | 是 | 0xFFFFFFFF | 以太网, ZIP |
Python 实现
核心代码
让我们看看 CRC 在 Python 模型中的实现:
def reverse_bits(x, num_bits):
"""反转指定位数的位序"""
reversed_x = 0
for i in range(num_bits):
reversed_x |= ((x >> i) & 1) << (num_bits - 1 - i)
return reversed_x
def crc_process_byte(crc, byte, poly, width, refin):
"""处理单个字节的CRC计算"""
# 1. 如果需要反转输入
if refin:
byte = reverse_bits(byte, 8)
# 2. 直接将字节与CRC高位进行异或(避免一位一位处理)
crc ^= (byte << (width - 8))
# 3. 处理8个位
for _ in range(8):
# 判断最高位,使用位移判断避免额外计算
if crc & (1 << (width - 1)):
# 左移+异或多项式
crc = ((crc << 1) ^ poly) & ((1 << width) - 1)
else:
# 仅左移
crc = (crc << 1) & ((1 << width) - 1)
return crc
def calculate_crc(data_bytes, width, poly, init, refin, refout, xorout):
"""计算字节序列的CRC校验值"""
# 确保poly不包含最高位(如果已经包含)
poly = poly & ((1 << width) - 1)
crc = init
for byte in data_bytes:
crc = crc_process_byte(crc, byte, poly, width, refin)
if refout:
crc = reverse_bits(crc, width)
crc ^= xorout
crc &= (1 << width) - 1 # 确保结果在低width位
return crc
关键函数
- reverse_bits:反转值的位序(用于 RefIn 和 RefOut)
- crc_process_byte:在 CRC 计算过程中处理单个字节:
- 可选择反转输入字节
- 将字节与当前 CRC 值异或
- 使用多项式除法处理每一位
- calculate_crc:计算字节序列 CRC 的主函数:
- 使用 init 值初始化 CRC 寄存器
- 处理输入数据中的每个字节
- 可选择反转最终 CRC
- 将结果与 XorOut 值异或
测试
import crcmod
def reverse_bits(x, num_bits):
"""反转指定位数的位序"""
reversed_x = 0
for i in range(num_bits):
reversed_x |= ((x >> i) & 1) << (num_bits - 1 - i)
return reversed_x
def crc_process_byte(crc, byte, poly, width, refin):
"""处理单个字节的CRC计算"""
# 1. 如果需要反转输入
if refin:
byte = reverse_bits(byte, 8)
# 2. 直接将字节与CRC高位进行异或(避免一位一位处理)
crc ^= (byte << (width - 8))
# 3. 处理8个位
for _ in range(8):
# 判断最高位,使用位移判断避免额外计算
if crc & (1 << (width - 1)):
# 左移+异或多项式
crc = ((crc << 1) ^ poly) & ((1 << width) - 1)
else:
# 仅左移
crc = (crc << 1) & ((1 << width) - 1)
return crc
def calculate_crc(data_bytes, width, poly, init, refin, refout, xorout):
"""计算字节序列的CRC校验值"""
# 确保poly不包含最高位(如果已经包含)
poly = poly & ((1 << width) - 1)
crc = init
for byte in data_bytes:
crc = crc_process_byte(crc, byte, poly, width, refin)
if refout:
crc = reverse_bits(crc, width)
crc ^= xorout
crc &= (1 << width) - 1 # 确保结果在低width位
return crc
# 示例用法
if __name__ == "__main__":
# 示例参数(以CRC-8为例)
width = 16
poly = 0x10c21 # 多项式 x^8 + x^2 + x + 1 (隐式最高位)
init = 0xffff
xorout = 0x0000
# 输入数据(假设输入S018F0转换为字节数组)
input_data = [0x71,0xFA,0x96,0x59,0x91,0x93,0xB2,0xD1,0x35]
crc_result_standard = calculate_crc(input_data, width, poly, init, False, False, xorout)
print(f"CRC结果: 0x{crc_result_standard:02X}")
crc_result_reflected = calculate_crc(input_data, width, poly, init, True, True, xorout)
print(f"CRC结果: 0x{crc_result_reflected:02X}")
crc_result_mixed1=calculate_crc(input_data,width,poly,init,True,False,xorout)
print(f"CRC结果: 0x{crc_result_mixed1:02X}")
crc_result_mixed2=calculate_crc(input_data,width,poly,init,False,True,xorout)
print(f"CRC结果: 0x{crc_result_mixed2:02X}")
crc16_func_standard = crcmod.mkCrcFun(poly, initCrc=0xffff, rev=False, xorOut=0x0000)
crc16_func_reflected = crcmod.mkCrcFun(poly, initCrc=0xffff, rev=True, xorOut=0x0000)
print(f"Expected standard: {crc16_func_standard(bytes(input_data)):04x}")
print(f"Expected reflected: {crc16_func_reflected(bytes(input_data)):04x}")
此外,也可以在CRC在线计算,验证结果
Verilog 实现
硬件实现在 Verilog 中分为两个模块:
主 CRC 模块(crc.v
)
module crc #(
parameter bits =8,
parameter poly =8'h33,
parameter init =8'hff,
parameter [0:0]refin =1'b0,
parameter [0:0]refout =1'b0,
parameter xorout =8'h00
)(
input clk,
input rst_n,
input data_valid,
input start,
input [7:0] data_in,
output reg crc_ready,
output reg [bits-1:0] crc_out
);
// 内部寄存器
reg [bits-1:0] crc_reg;
wire [bits-1:0] crc_next;
reg data_processed;
// 实例化字节处理模块
crc_process_byte #(.bits(bits),.poly(poly)) uut (
.crc_in(crc_reg),
.byte_in(data_in),
.refin_in(refin),
.crc_out(crc_next)
);
// 位翻转函数实现
function [bits-1:0] reflect;
input [bits-1:0] data;
integer i;
begin
reflect = 0;
for (i = 0; i < bits; i = i + 1)
reflect = reflect|(data[i]<<(bits-1-i));
end
endfunction
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
crc_reg <= init;
crc_ready <= 0;
crc_out <= 0;
data_processed <= 0;
end else if(start) begin
// 开始新的计算
crc_reg <= init;
crc_ready <= 0;
data_processed <= 0;
end else if(data_valid) begin
// 处理数据
crc_reg <= crc_next;
crc_ready <= 0;
data_processed <= 1;
end else if (data_processed && !data_valid && !crc_ready) begin
// 数据处理完成
if(refout) begin
crc_out <= (reflect(crc_reg) ^ xorout) & ((1<<bits)-1);
end else begin
crc_out <= (crc_reg ^ xorout) & ((1<<bits)-1);
end
crc_ready <= 1;
end
end
endmodule
字节处理模块(crc_process_byte.v
)
module crc_process_byte #(
parameter bits = 8,
parameter poly = 8'h33
)
(
input refin_in,
input [8-1:0] byte_in,
input [bits-1:0] crc_in,
output reg [bits-1:0] crc_out
);
reg [7:0] byte_reg;
reg [bits-1:0] crc_reg;
integer i;
always@(*)begin
byte_reg = refin_in? reflect(byte_in): byte_in;
crc_reg = crc_in^(byte_reg<<(bits-8));
crc_out = 0;
for(i=0;i<8;i=i+1)begin
if(crc_reg[bits-1])begin
crc_reg=((crc_reg<<1)^poly)&((1<<bits)-1);
end
else begin
crc_reg=(crc_reg<<1)&((1<<bits)-1);
end
end
crc_out = crc_reg;
end
// 位翻转函数实现
function [7:0] reflect;
input [7:0] data;
integer i;
begin
reflect = 0;
for (i = 0; i < 8; i = i + 1)
reflect = reflect|(data[i]<<(7-i));
end
endfunction
endmodule
测试示例(crc_tb.v
)
`timescale 1ns/1ns
module crc_tb;
// 参数定义
parameter CRC_WIDTH = 8;
parameter CRC_POLY = 9'h107;
parameter CRC_INIT = 8'hff;
parameter CRC_REFIN = 0;
parameter CRC_REFOUT = 0;
parameter CRC_XOROUT = 0;
// 信号声明
reg clk;
reg rst_n;
reg data_valid;
reg start;
reg [7:0] data_in;
wire crc_ready;
wire [CRC_WIDTH-1:0] crc_out;
// 直接实例化CRC模块,不再使用crc_top作为中间层
crc #(
.bits(CRC_WIDTH),
.poly(CRC_POLY),
.init(CRC_INIT),
.refin(CRC_REFIN),
.refout(CRC_REFOUT),
.xorout(CRC_XOROUT)
) crc_inst (
.clk(clk),
.rst_n(rst_n),
.data_valid(data_valid),
.start(start),
.data_in(data_in),
.crc_ready(crc_ready),
.crc_out(crc_out)
);
// 测试流程
initial begin
// 复位
rst_n = 0;
start = 0;
data_valid = 0;
data_in = 0;
#10 rst_n = 1;
// 开始新的CRC计算
#10 start = 1;
#10 start = 0;
// 输入数据字节
data_in = 8'h01; data_valid = 1; #10 data_valid = 0; #10;
data_in = 8'h02; data_valid = 1; #10 data_valid = 0; #10;
data_in = 8'h03; data_valid = 1; #10 data_valid = 0; #10;
data_in = 8'h04; data_valid = 1; #10 data_valid = 0; #10;
data_in = 8'h05; data_valid = 1; #10 data_valid = 0; #20;
// 等待结果
wait(crc_ready);
$display("CRC-8结果: 0x%h", crc_out);
end
// 时钟生成
initial begin
clk = 0;
forever #5 clk = ~clk; // 10ns周期时钟
end
endmodule
输出结果为0x85, 我们用网站进行测试
输出结果一致