CRC是什么?
CRC定义 CRC(Cyclic Redundancy Check),循环冗余校验,其特征是信息字段和校验字段的长度可以任意选定,CRC编码格式是在k位有效数据之后添加r位校验码,形成总长度为n(K+R)位的CRC码。
生成多项式
生成多项式 即使是CRC8也有多种,但总是有这样一个结果,最低位和最高为均为1,所以可以表达为(mathtype公式插不上来,截图了):
其中,Hn为相关性,为0或1。CRC-X,X为几,校验位就是Xbit,即生成多项式的最高次是几,校验结果的位宽就是几bit。以G(x)=x^5+x^3+1为例,其对应的二进制为5’b101001,其中1代表多项式的系数。
帧校验序列FCS
帧检验序列FCS 帧检验序列简称为FCS(Frame Check Sequence),是为了进行差错检验而添加的冗余码。
生成多项式 在进行CRC校验时,发送方和接收方需要事先约定一个除数,即生成多项式,一般记为G(x),生成多项式的最高位和最低位必须为1。
帧校验码的计算
设信息字段为K位,校验字段为R位,码字长度N=K+R,设双方事先约定了一个R次多项式G(x),则CRC校验码为:V(x)=A(x)G(x)=xRm(x)+r(x) m(x)为K次信息多项式,r(x)为R-1次检验多项式(看晕了的话直接看实例更便于理解,毕竟公式太抽象)。
这里r(x)对应的代码即为冗余码,加在原信息字段后即形成CRC码。r(x)的计算方法为:在K位信息字段的后面添加R个0,再除以g(x)对应的代码序列,得到的余数即为r(x)对应的代码(应为R-1位;若不足,而在高位补0)。
首先是CRC-8,CRC-8的余数是一个8bit数据,这一位是发送设备处理需要发送k为信息码外,还需要发送8bit的校验位,假设信息为16bit,[0110_0010_0100_1100]2,即为设备需要发送的数据,再加上8bit的校验位,则必须发送16+8=24bit的数据。
计算方法有:手算、移位寄存器和并行处理。(并行运算此处不做展开说明,其实并行运算是最复杂的,因为需要进行公式推导,但是好在多项式基本都有自己固定的运算公式,非常固定,用就好了)
手算CRC
以信息码为[10_1011_1011]2 ,使用下面的生成多项式为例:
G(x)=X^4+x+1
a、首先,把信息位左移4bit,结果为[10_1011_1011_0000]2 ,然后做异或除法运算,关于为什么右移4bit,是因为CRC本质是一个取余运算,余数的位宽是最大位宽-1。
b、循环异或除法运算:
[10_1011_1011_0000]2
[10_011]2
——————————————————————————
00_1101_1011_0000
[1001_1]2
——————————————————————————
0100_0011_0000
[100_11]2
——————————————————————————
00_1111_0000
[1001_1]2
——————————————————————————
0110_1000
[100_11]2
——————————————————————————
010_0100
[10_011]2
——————————————————————————
10
c、最后进行填补,校验位为0010,最终发送到接收端的数据为[10_1011_1011_0010]2 ,接收端再根据多项式对接收数据进行校验,结果为0表示接收正确。对结果4'b0010进行验证:
移位寄存器的实现
原理:
移位寄存器接近于硬件设计,在输入为单bit时很有效,以 ,其对应的二进制形式为:[1_0000_0111]2,哪一位为1就代表哪一位要进行异或操作,最高位的1表示与输入进行异或(参考上面的手动异或运算,输入数据的最高位先进行异或操作),0111就是代表 C0、C1、C2前面都有一次异或操作,用框图表示就是下图:
寄存器逻辑图:
以G(x)=x^4+x+1为例,其二进制形式为5'b1_0011,其框图为:
设C0 C1 C2 C3初始值皆为0,信息码为10_1011_1011,将信息码从高位到低位逐次移入逻辑电路,计算CRC检验结果。
C1的输入是上一个时钟的C0与本拍的输入进行异或操作,最后将操作结果的左边作为最低位,右边作为最高位,即为CRC校验值:
需要注意的是寄存器的初始值大多是确定的(就目前这个例子来说),因为我试了试1111,根本不对~~~~~~~
example
我对乐鑫一道题的题干保持怀疑态度,因为自己见识比较少,未曾见过这种形式的CRC校验,所以对于该题我仅给出个人理解,如果有实际应用中单bit输出使用过的朋友欢迎一起讨论!
改题目从公众号《数字IC自修室》中看到,我认为串行输出的结果应该为8bit,所以以下代码以8bit的校验结果输出编写:
主程序:
module CRC8(
input clk,
input rst_n,
input data, //输入数据
input data_valid, //data valid指示,该信号为1表示输入数据有效
input crc_start, //CRC校验开始控制
output reg [7:0]crc_out, //串行CRC输出
output reg [5:0] num, //该信号用于观察校验进程,无实际作用
output reg crc_valid //串行CRC输出有效信号
);
reg [3:0]state; //状态机
reg [7:0]crc; //中间寄存器
//reg [5:0]num; //计数器
wire [7:0]crc_new;
always@(posedge clk or negedge rst_n)
if(~rst_n)
crc<=8'h00;
else if(crc_start)
crc<=8'h00;
else if(data_valid)
begin
/*
比较之下此处还是使用时序语句+data_valid控制比较好,
因为假设串行数据是不连续的,使用组合逻辑就容易发生错误
*/
crc[0]<=crc[7]^data;
crc[1]<=crc[0]^crc[7]^data;
crc[2]<=crc[1]^crc[7]^data;
crc[3]<=crc[2];
crc[4]<=crc[3];
crc[5]<=crc[4];
crc[6]<=crc[5];
crc[7]<=crc[6];
end
always@(posedge clk or negedge rst_n)
if(~rst_n )
num<=6'd0;
else if(crc_start ) //假设被中途打断,将计数器清零
num<=6'd0;
else if(num==6'd32) //32个数据校验完毕,将计数器清零
num<=6'd0;
else if(data_valid) //每次数据有效时,将计数器加一
num<=num+1'b1;
always@(posedge clk or negedge rst_n)
if(~rst_n)
begin
crc_valid<=1'b0;
crc_out<=1'b0;
end
else if(num==32)
begin
crc_valid<=1'b1;
crc_out<=crc;
end
endmodule
仿真文件:
`timescale 1ns/1ns
`define clk_period 20
module c_tb();
reg clk, rst_n, data, data_valid, crc_start; //CRC校验开始控制
wire [7:0]crc_out; //串行CRC输出
wire crc_valid; //串行CRC输出有效信号
wire [5:0] num;
CRC8 c0(
.clk(clk),
.rst_n(rst_n),
.data(data), //输入数据
.data_valid(data_valid), //data valid指示,该信号为1表示输入数据有效
.crc_start(crc_start), //CRC校验开始控制
.crc_out(crc_out), //串行CRC输出
.num(num),
.crc_valid(crc_valid) //串行CRC输出有效信号
);
initial begin
clk=1'b0;
end
always #(`clk_period/2) clk=~clk;
initial begin
rst_n=1'b0;
data =1'b1;
data_valid=1'b0;
crc_start=1'b0;
#100;
rst_n=1'b1;
crc_start=1'b1;
#20;
crc_start=1'b0;
#50;
data_valid=1'b1;
#(`clk_period*16);
data=1'b0;
#(`clk_period*16);
data_valid=1'b0;
#50;
$stop;
end
endmodule
仿真结果:
运行结果为:8'hFA
对比:
为了验证代码正确性,打开在线CRC校验工具,输入32'hffff_0000,查看运算结果,与仿真一致,说明代码正确。