目录
1. CRC介绍
在进行信息传输时,收发双方可制定特定规则,在数据后增加一些冗余位,以增加接收端的检错能力。该冗余位即为本文介绍的循环冗余校验码(Cyclic Redundancy Check, CRC),CRC是一种差错校验码,用来检测信息传输过程中可能出现的错误。其中信息位的长度和校验位的长度可以任意指定。
在发送方,利用生成多项式对数据多项式做模2除生成校验位,在接收方利用同样的多项式对收到的编码多项式做模2除检错。其基本思想是将要传送的数据D(X)表示为一个多项式L,用预先确定的多项式G(X)除以L,得到的余式就是所需的循环冗余校验码。
CRC的生成多项式应符合如下规律:
a. n位的生成多项式中,应包含Xn和X0;
b. 当信息流出现误码时,数据多项式被生成多项式除后余数不为0;
根据5G协议“R15 38.212”,通信中常用的CRC生成多项式包含以下几种:
2. CRC手推过程(以CRC 6为例)
由于手推过程比较繁琐,为方便各位理解,以校验位最短的CRC6为例进行推导。
Step1: 获得除数
CRC16的生成多项式:
还可以写成:
故多项式系数即为需要进行模2除运算的除数(0~6)共7位:1 1 0 0 0 0 1;
Step 2: 数据末位补0
根据生成多项式最高位的阶数,在数据的末位补相应位数的“0”。故本实例应补6个“0”。
数据串范例:1 0 0 1 0 1 0 0 1 0
补0之后范例:1 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0
Step 3: 开始模2除运算,即异或运算
计算时,需将补0后的数据串和除数对齐,按位进行“异或”操作,相同取“0”,相异取“1”。将前7位异或的结果计算结束后,再将未作计算的数据直接下移,形成新的数据串,重复以上操作。每进行一次操作,均需将除数左移至与第一个“1”对齐再继续,表示跳过无需操作的“0”位。进行多次循环异或运算后得到的余数,舍掉无效位0后就是CRC循环校验码的结果“ 1 1 0 1 1 1” 。
Step 4: 增加循环校验位
将计算得到的循环校验位加在数据末位,形成新的数据流。
新数据流:1 0 0 1 0 1 0 0 1 0 1 1 0 1 1 1
Step 5: 接收端反向验证
接收端接收到数据流后,重复Step 3,进行CRC验证,如下,计算后得到结果为全0,表示在传输过程中无误码,完成了一个完整的CRC校验过程。
3. FPGA实现手推结果
根据CRC 的公式可得到如下通用CRC电路,由CRC 6公式和电路图可知:
D0 <= data_in ^ D5;
D1 <= D0; D2< = D1; D3 <= D2;
D5 <= data_in ^ D5^ D4;
FPGA程序、测试TB文件、和最后波形如下,仅供参考。
module crc_serial(
input wire clk ,
input wire rst ,
input wire i_data , //输入的串行数据
input wire i_data_vld , //数据有效标志
output reg [5:0] o_crc_data , //输出的CRC结果
output reg o_crc_vld //CRC 有效标志
);
reg [5:0] crc_reg;
reg [3:0] data_cnt;
//为输出计数,可计0 ~ (输入数据数量-1)个数
always @(posedge clk) begin
if(rst)
data_cnt <= 4'd0;
else if(i_data_vld)
data_cnt <= data_cnt + 4'h1;
else
data_cnt <= 4'd0;
end
always @(posedge clk) begin
if(rst)
crc_reg <= 6'h0;
else if(i_data_vld) begin
crc_reg [5] <= crc_reg[5]^crc_reg[4]^i_data;
crc_reg [4:1] <= crc_reg [3:0];
crc_reg [0] <= crc_reg [5] ^ i_data;
end
else
crc_reg <= crc_reg;
end
//计数输入数据数量个时钟周期,最后一比特计算完成后输出计算结果
always @(posedge clk) begin
if(rst)
o_crc_data <= 6'd0;
else if(data_cnt ==4'd10)
o_crc_data <= crc_reg;
else
o_crc_data <= 6'd0;
end
always @(posedge clk) begin
if(rst)
o_crc_vld <= 1'd0;
else if(data_cnt ==4'd10)
o_crc_vld <= 1'd1;
else
o_crc_vld <= 1'd0;
end
endmodule
module crc_serial_tb();
reg clk ;
reg rst ;
reg i_data ;
reg i_data_vld ;
wire [5:0] o_crc_data ;
wire o_crc_vld ;
parameter CLK_74_25 = 6.734;
initial begin
clk = 1'b0;
forever begin
#CLK_74_25 clk = ~clk;
end
end
//生成数据时钟和传入模块的时钟反相
wire clk_data_gen;
assign clk_data_gen = ~clk;
initial begin
rst = 1'b1;
i_data = 10'b0;
i_data_vld = 1'b0;
#1000
rst = 1'b0;
//根据电路图,高位先进入逻辑,低位后进入逻辑
@(posedge clk_data_gen) begin //9
//i_data = 10'b1001010010;
i_data = 1'b1;
i_data_vld = 1'b1;end
@(posedge clk_data_gen) begin //8
i_data = 1'b0;
i_data_vld = 1'b1; end
@(posedge clk_data_gen) begin //7
i_data = 1'b0;
i_data_vld = 1'b1; end
@(posedge clk_data_gen) begin //6
i_data = 1'b1;
i_data_vld = 1'b1; end
@(posedge clk_data_gen) begin //5
i_data = 1'b0;
i_data_vld = 1'b1; end
@(posedge clk_data_gen) begin //4
i_data = 1'b1;
i_data_vld = 1'b1; end
@(posedge clk_data_gen) begin //3
i_data = 1'b0;
i_data_vld = 1'b1; end
@(posedge clk_data_gen) begin //2
i_data = 1'b0;
i_data_vld = 1'b1; end
@(posedge clk_data_gen) begin //1
i_data = 1'b1;
i_data_vld = 1'b1; end
@(posedge clk_data_gen) begin //0
i_data = 1'b0;
i_data_vld = 1'b1;end
@(posedge clk_data_gen) begin
i_data = 10'b0;
i_data_vld = 1'b0;
end
end
crc_serial crc_serial_test(
.clk (clk) ,
.rst (rst) ,
.i_data (i_data ) ,
.i_data_vld (i_data_vld) ,
.o_crc_data (o_crc_data) ,
.o_crc_vld (o_crc_vld)
);
endmodule
以上均为个人总结,如有错误之处欢迎留言讨论,如果对您有帮助,欢迎点赞收藏,谢谢可爱的小哥哥小姐姐们!