**
一、CRC检错原理
**
CRC(cyclic redundancy check/code):循环冗余检错技术/循环冗余校验码;
《计算机网络 第五版》—谢希仁所著的教材通过例子对循环冗余检验原理的原理说明如下:
在发送端,先把数据划分为组,假定每组k个比特。现假定待传送的数据M=101001(k=6)。CRC运算就是在数据M的后面添加供差错检测用的n位冗余码,然后构成一个帧发送出去,一共发送(k+n)位。在所要发送的数据后面增加n位的冗余码,虽然增大了数据传输的开销,但却可以进行差错检测。当传输可能出现错误时,付出这种代价往往是很值得的。
这n位冗余码可用以下方法得出。用二进制的模2运算进行乘M的运算,这相当于在M后面添加n个0。得到的(k+n)位的数除以收发双方事先商定的长度为(n+1)位除数P,得出的商是Q而余数是R(n位,比P少一位)。关于除数P下面还要介绍。在图1所示的例子中,M=101001(即k=6)。假定除数P=1103(即n=3)。经模2除法运算后的结果是:商Q=110101(这个商并没有什么用处),而余数R=001。这个余数R就作为冗余码拼接在数据M的后面发送出去。这种为了进行检错而添加的冗余码常称为帧检验序列FCS(Frame Check Sequence)。因此加上FCS后发送的帧是101001001(即),共有(k+n)位。
顺便说一下,循环冗余检验CRC和帧检验序列FCS并不是同一概念。CRC是一种检错方法,而FCS是添加在数据后面的冗余码,在检错方法上可以选用CRC,但也可以不选用CRC。
在接受端把接收到的数据以帧为单位进行CRC检验:把收到的每一个帧都除以同样的除数P(模2运算),然后检查得到的余数R。
如果在传输过程中无差错,那么经过CRC检验后得出的余数R肯定是0。但如果出现误码,那么余数R仍等于零的概率是非常非常小的(可以通过不太复杂的概率计算得出)。
总之,在接收端对收到的每一帧经过CRC检验后,有以下两种情况:
1)若得出的余数R=0,则判定这个帧没有差错,即接受(accept);
2)若余数R!=0,则判定这个帧有差错,即丢弃(discard)。
一种较方便的方法时用多项式来表示循环冗余检验过程。在上面的例子中,用多项式 表示上面的除数P=1101(最高位对应于,最低位对应于)。多项式P(X)称为生成多项式。现在广泛使用的生成多项式P(X)有以下几种:
小结:
小结一下上述教材内容所提到的内容:
1)什么叫"模2运算"?
通过一个例子的话我想能够在你脑子里形成清晰的概念。假如一个数k被任意的数除,那么它产生的余数必定是在[0,n)区间中。即x mod n ∈ [0, n)。模2运算也是符合这种理论的,即模2运算就是指结果只能是在[0,2)区间中取整数值的运算,显然"模2运算"就是指结果只能为0、1的特殊二进制运算。
2)为什么"模2运算"属于特殊二进制运算,必须突出"特殊"二字?
第一点,"模2运算"和"二进制运算"名字本身就不同,因此类比的时候使用"特殊"二字;第二点,两者的运算其实是有区别,"模2运算"既不进位也不退位,而"二进制运算"和"十进制运算"均需要进、退位。
3)循环冗余检验只是数学检错技术中的一种,还有奇偶校检等数学手段。而且循环冗余检验并不能保证100%正确,只能说接近100%。
**
二、CRC检错实战
**
首先分析生成多项式,显然n=3,因此循环冗余码应为3位二进制数。生成多项式代表的二进制除数P=1001,发送的数据为101110,则真实的被除数为101110000。进行减模2运算(异或运算)如下:
于是得到FCS为011,因此发送数据实际为101110011(除以1001余数为0,读者可自行尝试)。注意到,在运算的时候,每次都会往后退一位,即第一位每次都是被消掉的。
注意:计算机网络通信中,我们对生成多项式(除数)作了统一的标准,即针对不同的帧,发送端网卡添加不同的FCS,而接受端网卡知道相应帧该用CRC-16还是CRC-32来检错。
上代码:
`timescale 1ns/1ps
module CRC(
input wire clk,
input wire rstn,
input wire req,
input wire [5:0] i_data,
input wire [3:0] i_crc,
output reg [8:0] o_data_fcs,
output reg o_vld
);
reg [5:0] i_data_r;//缓存
reg [3:0] i_crc_r;//缓存
reg start;
reg [9-4-1:0] data_middle_shift;
reg [3:0] fcs_r;
reg [5:0] count;
always @(posedge clk or negedge rstn ) begin
if(~rstn)begin
i_data_r<='b0;
i_crc_r<='b0;
start<='b0;
end
else if(req & ~start)begin
i_data_r<=i_data;
i_crc_r<=i_crc;
start<='b1;
end
else if(start && o_vld)begin
start<='b0;
end
end
//利用移位寄存器实现模2运算输入
always @(posedge clk or negedge rstn ) begin
if(~rstn)begin
data_middle_shift<='b0;
end
else if(req)begin
data_middle_shift<={i_data[1:0],3'b000};
end
else if(start && count!='b0)begin
data_middle_shift<={data_middle_shift[3:0],1'b0};
end
end
//控制异或运算次数
always @(posedge clk or negedge rstn ) begin
if(~rstn)begin
count<='b0;
end
else if(req)begin
count<='d5;
end
else if(start&&count=='d0)begin
count<='d5;
end
else if(start)begin
count<=count-'b1;
end
end
//模2运算
always @(posedge clk or negedge rstn ) begin
if(~rstn)begin
fcs_r<='b0;
end
else if(req)begin
if(i_data[5])
fcs_r<=i_data[5:2]^i_crc;
else begin
fcs_r<=i_data[5:2];
end
end
else if(start)begin
if(fcs_r[2])
fcs_r<={fcs_r[2:0],data_middle_shift[4]}^i_crc_r;
else begin
fcs_r<={fcs_r[2:0],data_middle_shift[4]};
end
end
end
always @(posedge clk or negedge rstn ) begin
if(~rstn)begin
o_vld<='b0;
end
else if(count=='b1 && start)begin
o_vld<='b1;
end
else
o_vld<='b0;
end
always @(posedge clk or negedge rstn ) begin
if(~rstn)begin
o_data_fcs<='b0;
end
else if(o_vld)begin
o_data_fcs<={i_data_r,fcs_r[2:0]};
end
else
o_data_fcs<=o_data_fcs;
end
endmodule
tb:
`timescale 1ns/1ps
module tb_crc();
reg clk;
reg rstn;
reg req;
reg [5:0] i_data;
reg [3:0] i_crc;
wire [8:0] o_data_fcs;
wire o_vld;
initial begin
clk<=1;
rstn=1;
#(20)
rstn=0;
#100;
rstn<=1;
end
always #10 clk=~clk;
initial begin
req<='b0;
i_data<=6'b101110;
i_crc<='b1001;
#(120) req<='b1;
#(20) req<='b0;
end
CRC cr_inst(
.clk(clk),
.rstn(rstn),
.req(req),
.i_data(i_data),
.i_crc(i_crc),
.o_data_fcs(o_data_fcs),
.o_vld(o_vld)
);
endmodule
测试结果:计数到0输出crc校验码:101110011