CRC實戰寶典:從原理到代碼,全面攻克循環冗余校驗
github開源:CRC軟硬件協同測試項目
CRC 簡介
CRC(循環冗余校驗)是一種強大的錯誤檢測技術,廣泛應用于數字網絡和存儲系統。它是確保數據完整性的重要方法,能夠檢測傳輸或存儲過程中對原始數據的意外更改。
什么是 CRC?
CRC 是一種數學算法,具有以下特點:
- 將數據視為有限域中的多項式表示
- 使用預定義的多項式(稱為生成多項式)進行二進制除法
- 生成固定長度的校驗和,附加到消息末尾
- 能夠高概率地檢測出常見的傳輸錯誤
CRC 的優勢在于其能夠檢測:
- 所有單比特錯誤
- 所有雙比特錯誤(在標準 CRC 中)
- 任何奇數個比特錯誤
- 突發錯誤(基于多項式長度的一定范圍內)
CRC 原理
多項式表示
在 CRC 中,數據和生成多項式都表示為具有二進制系數(0 或 1)的多項式。例如:
- 二進制序列
1101
表示多項式 x3 + x2 + 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 = 0for i in range(num_bits):reversed_x |= ((x >> i) & 1) << (num_bits - 1 - i)return reversed_xdef 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 crcdef calculate_crc(data_bytes, width, poly, init, refin, refout, xorout):"""計算字節序列的CRC校驗值"""# 確保poly不包含最高位(如果已經包含)poly = poly & ((1 << width) - 1)crc = initfor byte in data_bytes:crc = crc_process_byte(crc, byte, poly, width, refin)if refout:crc = reverse_bits(crc, width)crc ^= xoroutcrc &= (1 << width) - 1 # 確保結果在低width位return crc
關鍵函數
- reverse_bits:反轉值的位序(用于 RefIn 和 RefOut)
- crc_process_byte:在 CRC 計算過程中處理單個字節:
- 可選擇反轉輸入字節
- 將字節與當前 CRC 值異或
- 使用多項式除法處理每一位
- calculate_crc:計算字節序列 CRC 的主函數:
- 使用 init 值初始化 CRC 寄存器
- 處理輸入數據中的每個字節
- 可選擇反轉最終 CRC
- 將結果與 XorOut 值異或
測試
import crcmoddef reverse_bits(x, num_bits):"""反轉指定位數的位序"""reversed_x = 0for i in range(num_bits):reversed_x |= ((x >> i) & 1) << (num_bits - 1 - i)return reversed_xdef 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 crcdef calculate_crc(data_bytes, width, poly, init, refin, refout, xorout):"""計算字節序列的CRC校驗值"""# 確保poly不包含最高位(如果已經包含)poly = poly & ((1 << width) - 1)crc = initfor byte in data_bytes:crc = crc_process_byte(crc, byte, poly, width, refin)if refout:crc = reverse_bits(crc, width)crc ^= xoroutcrc &= (1 << width) - 1 # 確保結果在低width位return crc# 示例用法
if __name__ == "__main__":# 示例參數(以CRC-8為例)width = 16poly = 0x10c21 # 多項式 x^8 + x^2 + x + 1 (隱式最高位)init = 0xffffxorout = 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;beginreflect = 0;for (i = 0; i < bits; i = i + 1)reflect = reflect|(data[i]<<(bits-1-i));end
endfunctionalways @(posedge clk or negedge rst_n) beginif(!rst_n) begincrc_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) begincrc_out <= (reflect(crc_reg) ^ xorout) & ((1<<bits)-1);end else begincrc_out <= (crc_reg ^ xorout) & ((1<<bits)-1);endcrc_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@(*)beginbyte_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)beginif(crc_reg[bits-1])begincrc_reg=((crc_reg<<1)^poly)&((1<<bits)-1);endelse begincrc_reg=(crc_reg<<1)&((1<<bits)-1);endendcrc_out = crc_reg;
end// 位翻轉函數實現
function [7:0] reflect;input [7:0] data;integer i;beginreflect = 0;for (i = 0; i < 8; i = i + 1)reflect = reflect|(data[i]<<(7-i));end
endfunction
endmodule
測試示例(crc_tb.v
)
`timescale 1ns/1nsmodule 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 beginclk = 0;forever #5 clk = ~clk; // 10ns周期時鐘endendmodule
輸出結果為0x85, 我們用網站進行測試
輸出結果一致