一、CRC 校验基本原理
循环冗余校验(CRC,Cyclic Redundancy Check)是一种用于检测数据传输和存储时错误的校验方法,通过多项式除法生成校验码。
值得注意的是,CRC只能用于检测错误,但是不能纠错。同样的还有SHA-256。
如果因为异常工作环境导致单个数据的某些bit出错,可以通过加入纠错码(例如汉明码、BCH、LDPC等)来纠错。
接下来简单说一下CRC校验的基本原理,可以简述为以下步骤:
数据表示与生成多项式:将待校验的数据视为一个二进制多项式(即,数据中的每个比特对应多项式的一个系数)。选取一个预先定义的生成多项式,通常记为G(x)
,用于进行校验。
附加校验位:在数据的尾部附加若干个零比特(通常附加的零的数量等于生成多项式的阶数),形成待校验的数据帧。
二进制除法:使用生成多项式对数据帧进行二进制除法(模2除法),得到一个余数。这个余数即为CRC校验码。
附加校验码:将计算得到的CRC校验码附加到原始数据后,形成传输的数据帧。
校验过程:在接收端,使用相同的生成多项式对接收到的数据帧(包含CRC校验码)进行二进制除法。如果余数为零,说明数据在传输过程中没有发生错误;如果余数不为零,说明数据存在错误。
二、二进制除法介绍
因为在实际实现的过程中涉及到了二进制除法(模2除法)所以这里再简单介绍一下。
除法的运算规则:在二进制除法中,所有计算均使用模2运算,也就是逐位的“异或”运算。这里就不介绍异或的运算规则了。
二进制除法的基本步骤如下:
-
初始化:将被除数(即需要进行CRC校验的数据帧)和除数(即生成多项式)对齐,准备进行运算。通常被除数会在其末尾添加若干个零比特,以便进行完整的校验计算。
-
移位比对:从被除数的高位开始,与生成多项式的高位对齐。如果当前被除数的最高位为1,则执行异或操作。
-
异或运算:当对齐后,判断当前位:
如果当前被除数的最高位是1,就对生成多项式和被除数进行异或操作。
如果当前位是0,则相当于对0进行异或运算,结果保持不变。(不懂的可以自己计算一下和0异或的结果,位数和生成多项式一致)
-
移位并继续除法:执行完一次异或运算后,将被除数的剩余部分向左移位,(直到出现1为止,因为如果高位是0的话那就是要和0异或,结果不变)并继续与生成多项式对齐,重复上述步骤直到所有比特位都处理完。
-
得到余数:最终剩下的数就是余数,这个余数就是我们要附加到原始数据上的CRC校验码。
具体的例子我们放在CRC实现的时候一起讲吧。
三、CRC校验实现具体例子
-
假设我们要发送的数据是1101
-
生成多项式是 x 3 + x + 1 x^3+x+1 x3+x+1,那么与多项式对应的二进制数是1011(因为多项式中的三次方项和一次项和常数项有系数,所以是第0、1、3位为1)
-
用1101对多项式对应的二进制数1011进行模2除法得到的余数就是CRC校验码(在1101后面补上3个0,因为多项式中最高次项为3次)
CRC校验码的位数和生成多项式中的最高次项对应(这里多项式的最高次项为3,那么CRC校验码就应该是3位)
- 计算过程如下:
1101000
1011
——————
0110000
01011
——————
0011100
0010110
——————
0001010
0001011
—————
0000001
-
这里得到的余数是001,那么CRC校验码就是001
-
将校验码加到要传输的数据后面,得到1101001,这就是传输时发送的数据
-
接收方对接收的数据再对多项式进行模2除法,如果此时最终得到的余数为0,那么接收的数据就是正确的。反之,就是错误的。计算步骤如下:
1101001
1011
——————
0110001
01011
——————
0011101
001011
——————
0001011
0001011
——————
0000000
所以得到最终的余数为0,说明接收的数据是正确的
四、Verilog 代码示例
方法一:
module crc_calculator(input clk,input rst,input [7:0] data_in,output reg [3:0] crc_out,output reg crc_valid
);parameter POLYNOMIAL = 5'b11001; // 生成多项式 x^4 + x + 1
reg [11:0] temp_data;
reg [2:0] state;always @(posedge clk or posedge rst) beginif (rst) begincrc_out <= 4'b0;crc_valid <= 0;state <= 0;end else begincase (state)3'b000: begintemp_data <= {data_in, 4'b0}; // 数据扩展state <= 3'b001;end3'b001: beginif (temp_data[11]) temp_data[11:7] <= temp_data[11:7] ^ POLYNOMIAL; // 高位为1,进行异或temp_data <= temp_data << 1; // 左移if (temp_data[7:4] == 4'b0) state <= 3'b010; // 完成CRC计算end3'b010: begincrc_out <= temp_data[3:0]; // 输出CRC校验码crc_valid <= 1;state <= 3'b000; // 重新开始endendcaseend
endendmodule
方法二:
module crc_test(input clk,input rst,input [7:0] data_in,output reg[3:0] crc_out,output reg crc_vld
);parameter polynomial = 5'b11001;
localparam IDLE = 3'b001,CRC = 3'b010,DONE = 3'b100;reg [11:0] temp = 0;
reg [2:0] state;
wire [11:0] signal_temp;assign signal_temp = {data_in,4'b0};always @ (posedge clk or posedge rst)beginif(rst)beginstate <= IDLE;crc_out <= 4'b0;crc_vld <= 1'b0;endelse case(state)IDLE:begincrc_out<= 4'b0;crc_vld<= 1'b0;temp <= signal_temp;state <= CRC;endCRC:beginstate <= CRC;crc_vld <= 1'b0;if(temp[11]) temp[11:7] <= temp[11:7] ^ polynomial;else if(temp[10])temp[10:6] <= temp[10:6] ^ polynomial; else if(temp[9])temp[9:5] <= temp[9:5] ^ polynomial; else if(temp[8])temp[8:4] <= temp[8:4] ^ polynomial;else if(temp[7])temp[7:3] <= temp[7:3] ^ polynomial; else if(temp[6])temp[6:2] <= temp[6:2] ^ polynomial;else if(temp[5])temp[5:1] <= temp[5:1] ^ polynomial; else if(temp[4])temp[4:0] <= temp[4:0] ^ polynomial;else state<=DONE;endDONE:begincrc_out <= temp[3:0];crc_vld <= 1'b1;state <= IDLE;enddefault : begincrc_out <= 4'b0;crc_vld <= 1'b1;state <= IDLE;endendcase
end
endmodule