循环冗余校验(Cyclic Redundancy Check,CRC)是通信中常用的差错检测编码方式,其基本工作原理是根据输入的信息位(信息码元),按照给定的生成多项式产生校验位(校验码元),并一起传送到接收端。在接收端,接收电路按照相同的规则对接收数据进行计算并生成本地的校验位,然后与收到的校验位进行对比,如果二者不同,则说明传输过程中发生了错误,否则说明传输是正确的。带有CRC校验结果的数据帧结构如表1-2所示。
CRC检验位生成与检测工作包括以下基本步骤。
图1-6是一个并行CRC-32校验运算电路。图中的d[7:0]是输入的用户数据,它是按照字节的方式输入的。load_ini是在对一个新的数据包开始校验计算之前对电路进行初始化的控制信号,经过初始化后,电路内部32比特寄存器的值改变为全1。calc是电路运算指示信号,在整个数据帧输入和CRC校验结果输出的过程中其都应该保持有效(高电平有效)。d_valid为1时表示当前输入的是需要进行校验运算的有效数据。crc[7:0]是电路输出的CRC校验运算结果,它是按照字节方式,在有效数据输入完成后开始输出的,一共有4个有效字节。crc_reg[31:0]是内部寄存器的值,具体使用时不需要该输出。
并行计算的思想,输入数据S要并行输入到G(x)系数为1的支路中,输入数据从输入端按高到低逐bit输入,就可以实现。
假如被除数是2位的数据S[1:0]=01,多项式是10011,x4 +x+1。在CRC校验里面,习惯省略最高位的1,多项式用0011表示。那么S除以0011的模二运算数字电路结构为:
其中d1~ d4是寄存器输入;q1~q4是寄存器输出。寄存器需要赋初值,一般赋全1或全0。
d1=S[1]^q4;
d2= S[1]^ q1^q4;
d3=q2;
d4=q3。
经过一次移位后:
q1=d1= S[1]^q4;
q2= d2= S[1]^ q1^q4;
q3= d3=q2;
q4= d4=q3。
此时有:
d1=S[0]^q3;
d2= S[0]^ S[1]^ q4^q3;
d3= S[1]^ q1^q4;
d4= q2。
令c[3:0]={q4,q3,q2,q1},d[3:0]={d4,d3,d2,d1},那么d就是最终的运算结果表达式,如下
d[3]=c[1];
d[2]= S[1]^ c[0]^c[3];
d[1]= S[0]^ S[1]^ c[3]^ c[2];
d[0]= S[0]^ c[2]。
令c的初值为0,则01对0011的模二除法的余数为0011。
再比如多项式为x5 +x3 +x+1,简记式为01011,其数字电路结构为:
输入数据S要全部输入完,寄存器得到的结果才是最后的结果。同理可推导出其他多项式和输入数据的情况。
对于循环检验,这里举个例子,如果数据是10bit*100个包,则每次输入10bit得到校验码后,该检验码为下次数据计算时寄存器D的初值,如此反复计算得到最后的检验码添加到整个数据后面即可,而不需要每个数据包后面都添加检验码。
下面是以太网循环冗余校验电路的设计代码:
module crc32_8023(
clk,
reset,
d,
load_init,
calc,
d_valid,
crc_reg,
crc
);
input clk;
input reset;
input [7:0] d;
input load_init;
input calc;
input d_valid;
output reg [31:0] crc_reg;
output reg [7:0] crc;
wire [2:0] ctl;
wire [31:0] next_crc;
wire [31:0] i;
assign i = crc_reg;
assign ctl = {load_init,calc,d_valid};
always @(posedge clk or posedge reset) begin
if(reset)
crc_reg <= 32'hffffffff;
else begin
case (ctl) //{load_init,calc,d_vaild}
3'b000,3'b010: begin crc_reg <= crc_reg; crc <= crc;end
3'b001: begin crc_reg <= {crc_reg[23:0],8'hff};
crc <= ~{crc_reg[16],crc_reg[17],crc_reg[18],crc_reg[19],crc_reg[20],crc_reg[21],crc_reg[22],crc_reg[23]};
//crc <= ~ crc_reg[16:23];
end
3'b011: begin
crc_reg <= next_crc[31:0];
crc <= ~{next_crc[24],next_crc[25],next_crc[26],next_crc[27],next_crc[28],next_crc[29],next_crc[30],next_crc[31]};
//crc <= ~ next_crc[24:31];
end
3'b100,3'b110: begin
crc_reg <= 32'hffffffff;
crc <= crc;
end
3'b101: begin
crc_reg <= 32'hffffffff;
crc <= ~{crc_reg[16],crc_reg[17],crc_reg[18],crc_reg[19],crc_reg[20],crc_reg[21],crc_reg[22],crc_reg[23]};
//crc <= ~ crc_reg[16:23];
end
3'b111: begin
crc_reg <= 32'hffffffff;
crc <= ~{next_crc[24],next_crc[25],next_crc[26],next_crc[27],next_crc[28],next_crc[29],next_crc[30],next_crc[31]};
//crc <= ~ next_crc[24:31];
end
endcase
end
end
assign next_crc[0] = d[7]^i[24]^d[1]^i[30]; //d+i=31
assign next_crc[1] = d[6]^d[0]^d[7]^d[1]^i[24]^i[25]^i[30]^i[31];
assign next_crc[2] = d[5]^d[6]^d[0]^d[7]^d[1]^i[24]^i[25]^i[26]^i[30]^i[31];
assign next_crc[3] = d[4]^d[5]^d[6]^d[0]^i[25]^i[26]^i[27]^i[31];
assign next_crc[4] = d[3]^d[4]^d[5]^d[7]^d[1]^i[24]^i[26]^i[27]^i[28]^i[30];
assign next_crc[5] = d[0]^d[1]^d[2]^d[3]^d[4]^d[6]^d[7]^i[24]^i[25]^i[27]^i[28]^i[29]^i[30]^i[31];
assign next_crc[6] = d[0]^d[1]^d[2]^d[3]^d[5]^d[6]^i[25]^i[26]^i[28]^i[29]^i[30]^i[31];
assign next_crc[7] = d[0]^d[2]^d[4]^d[5]^d[7]^i[24]^i[26]^i[27]^i[29]^i[31];
assign next_crc[8] = d[3]^d[4]^d[6]^d[7]^i[24]^i[25]^i[27]^i[28]^i[0]; //每项多出i[i],i=0、1、2...23
assign next_crc[9] = d[2]^d[3]^d[5]^d[6]^i[1]^i[25]^i[26]^i[28]^i[29];
assign next_crc[10] =d[2]^d[4]^d[5]^d[7]^i[2]^i[24]^i[26]^ i[27]^i[29];
assign next_crc[11] =i[3]^d[3]^i[28]^d[4]^i[27]^d[6]^i[25]^d[7]^i[24];
assign next_crc[12] =d[1]^d[2]^d[3]^d[5]^d[6]^d[7]^i[4]^i[24]^i[25]^i[26]^i[28]^i[29]^i[30];
assign next_crc[13] =d[0]^d[1]^d[2]^d[4]^d[5]^d[6]^i[5]^i[25]^i[26]^i[27]^i[29]^i[30]^i[31];
assign next_crc[14] =d[0]^d[1]^d[3]^d[4]^d[5]^i[6]^i[26]^i[27]^i[28]^i[30]^i[31];
assign next_crc[15] =d[0]^d[2]^d[3]^d[4]^i[7]^i[27]^i[28]^i[29]^i[31];
assign next_crc[16] =d[2]^d[3]^d[7]^i[8]^i[24]^i[28]^i[29];
assign next_crc[17] =d[1]^d[2]^d[6]^i[9]^i[25]^i[29]^i[30];
assign next_crc[18] =d[0]^d[1]^d[5]^i[10]^i[26]^i[30]^i[31];
assign next_crc[19] =d[0]^d[4]^i[11]^i[27]^i[31];
assign next_crc[20] =d[3]^i[12]^i[28];
assign next_crc[21] =d[2]^i[13]^i[29];
assign next_crc[22] =d[7]^i[14]^i[24];
assign next_crc[23] =d[1]^d[6]^d[7]^i[15]^i[24]^i[25]^i[30];
assign next_crc[24] =d[0]^d[5]^d[6]^i[16]^i[25]^i[26]^i[31];
assign next_crc[25] =d[4]^d[5]^i[17]^i[26]^i[27];
assign next_crc[26] =d[1]^d[3]^d[4]^d[7]^i[18]^i[28]^i[27]^i[24]^i[30];
assign next_crc[27] =d[0]^d[2]^d[3]^d[6]^i[19]^i[29]^i[28]^i[25]^i[31];
assign next_crc[28] =d[1]^d[2]^d[5]^i[20]^i[30]^i[29]^i[26];
assign next_crc[29] =d[0]^d[1]^d[4]^i[21]^i[31]^i[30]^i[27];
assign next_crc[30] =d[0]^d[3]^i[22]^i[31]^i[28];
assign next_crc[31] =d[2]^i[23]^i[29];
endmodule
测试代码
`timescale 1ns/1ns
module crc_test();
reg clk, reset;
reg [7:0] d;
reg load_init;
reg calc;
reg data_valid;
wire [31:0] crc_reg;
wire [7:0] crc;
initial
begin
clk=0;
reset=0;
load_init=0;
calc=0;
data_valid=0;
d=0;
end
always begin #10 clk=1; #10 clk=0; end
always begin
crc_reset;
crc_cal;
end
task crc_reset;
begin
reset=1;
repeat(2)@(posedge clk);
#5;
reset=0;
repeat(2)@(posedge clk);
end
endtask
task crc_cal;
begin
repeat(5) @ (posedge clk);
//通过losd_init=1 对CRC计算电路进行初始化
#5; load_init= 1; repeat(1)@ (posedge clk);
//设置1oad_init=0,data_valid= 1,calc=1
//开始对输人数据进行CRC校验运算
#5; load_init= 0; data_valid=1; calc=1; d=8'haa; repeat(1)@ (posedge clk);
#5; data_valid=1; calc=1; d=8'hbb; repeat(1)@ (posedge clk);
#5; data_valid=1; calc=1; d=8'hcc; repeat(1)@ (posedge clk);
#5; data_valid=1; calc=1; d=8'hdd; repeat(1)@ (posedge clk);
//设置load_init=0,data_valid=1,calc=0
//停止对数据进行CRC校验运算,开始输出
//计算结果
#5; data_valid=1; calc=0; d=8'haa; repeat(1)@ (posedge clk);
#5; data_valid=1; calc=0; d=8'hbb; repeat(1)@ (posedge clk);
#5; data_valid=1; calc=0; d=8'hee; repeat(1)@ (posedge clk);
#5; data_valid=1; calc=0; d=8'hdd; repeat(1)@ (posedge clk);
#5; data_valid=0; repeat(10)@ (posedge clk);
end
endtask
crc32_8023 my_crc_test(.clk(clk),.reset(reset),.d(d),.load_init(load_init),.calc(calc),.d_valid(data_valid),.crc_reg(crc_reg),.crc(crc));
endmodule
图1-7是电路的仿真结果。图中①是电路进行CRC校验计算之前对电路进行初始化操作的过程,经过初始化之后,crc_reg内部数值为全1。②是对输入数据aa-> bb-> cc-> dd进行运算操作的过程,此时calc和data_valid均为1。③是输出计算结果的过程,CRC校验运算结果a7、01、b4和55先后被输出。
在接收方向上,可以采用相同的电路进行校验检查,判断是否在传输过程中发生了差错。具体工作时,可以边接收用户数据边进行校验运算,当一个完整的MAC帧接收完成后(此时接收数据帧中的校验结果也参加了校验运算),如果当前校验电路的crc_reg值为0xC704DD7B(对于以太网中使用的CRC-32校验,无论原始数据是什么,正确接收时校验和都是此固定数值),说明没有发生错误,否则说明MAC帧有错。
CRC-32校验值的作用是用于检测数据传输或存储中的错误。发送数据时,会根据数据内容生成简短的校验和,并将其与数据一起发送。接收数据时,将再次生成校验和并将其与发送的校验和进行比较。如果两者相等,则没有数据损坏。如果两者不相等,则说明数据在传输或存储过程中发生了改变,可能是由于噪声、干扰、故障或恶意篡改等原因造成的。
CRC-32校验值可以有效地检测出数据中的随机错误,但是不能保证检测出所有的错误。例如,如果数据中有偶数个比特发生了翻转,那么CRC-32校验值可能不会改变,从而无法发现错误。因此,CRC-32校验值只能作为一种辅助的错误检测手段,不能完全依赖它来保证数据的正确性和完整性。
相关工具
如果不理解推导过程的话,可以由相关工具帮忙计算出结果和得到Verilog代码:
CRC校验Verilog代码生成链接:http://outputlogic.com/?page_id=321
CRC校验计算工具链接:http://www.ip33.com/crc.html,这个工具只能计算16bit为一个数据包的数据,如果数据包为10bit等之类的就不太适用
在线计算器使用举例
报文 : 1011001 (0x59)
生成多项式 : g(x) = x^4 + x^3 + 1
CRC : 1010 ( 0xa)
CRC计算结果截图:
参考文献
Verilog HDL算法与电路设计-乔庐峰
评论 (0)