CRC冗余校验简介

简介

在数据通信中,接收端通常需要检测传输过程中是否发生差错,常用的方法包括: 奇偶校验(Parity Check)、校验和(Check Sum)和CRC(Cyclic Redundancy Check) 等。

相应的检测方式是:①发送端按照某种算法对发送消息进行计算,得到校验码,然后将校验码和消息码一起发生到接收端;②接收端对接收到的消息按照相同算法计算,得到本地校验码;③将本地校验码与接收到校验码进行比较,如果两者一致,则证明接受到的消息是正确的,否则则存在传输错误。

奇偶校验
通过计算传输数据二进制表示中“1”的个数是奇数还是偶数来进行校验的方法,被称为奇偶校验。统计出奇数个“1”的称为奇校验,反正称为偶校验。通常专门设置一个奇偶校验位,用它使这组代码中“1”的个数为奇数或偶数。

采用何种校验是事先规定好的,若用奇校验,则当接收端收到这组数据时,校验“1”的个数是否为奇数个,从而确定传输是否正确。奇偶校验可以检测出1位差错,或者任意奇数个差错。

校验和
校验和的思想比较简单:将传输的消息当成8位(或16/32位)整数序列,将这些整数加起来的结果就是校验码,也称校验和。校验和被广泛应用于IP协议中,按照16位整数运算,而且其MSB的进位被加到结果中。

奇偶校验不能检测出偶数位差错。对于校验和,如果整数序列中有两个整数出错,一个增加了一定的值,另一个减少了相同的值,这种差错就检测不出来,因此复杂的通信系统通常采用CRC校验。

CRC算法的基本原理
CRC从直观上理解就是输入序列对某个表达式求余数,或者就是一系列数据的求异或的过程。
假设码子多项式为:在这里插入图片描述
生成多项式为:在这里插入图片描述
那么CRC的码字是在这里插入图片描述
在这里插入图片描述通用的CRC-32算法程序语句如下:

'define POLY_CONST 32'h04c11db7
crc32_new = {crc32_old[30:0],1'd0} ^ ({32{(crc32_old[31]^bitin)}} & 'POLY_CONST);

就好比上图,先将首位数据与输入进行异或,再进行32位拼接,再与参数进行相与,再与左移后的数据进行异或,0与任何数异或都是该数本身。0与任何数相与都是0,所以不会改变异或的位置。

与乐鑫2020笔试中那个将c代码转化为单拍完成的并行可综合Verilog类似。

上面代码中,crc32_old可以是上次计算的结果,也可以是第一次计算时的初始值,bitin是新参与运算的一位数值。

更进一步修改,任意串行CRC都可按照如下语句描述,只需要更改N和POLY_CONST:

`define N 16
`define POLY_CONT 0x1021
crc_new = {crc_old[N-2:0],1'b0}^({N{(crc_old[N-1]^bitin)}} & `POLY_CONST);

对于一个p位长度的二进制序列进行CRC校验,就是为这个序列添加一个r长度的序列,接收端在收到p+r序列后,能够根据某种规则判断出是否出错。

最朴实的CRC实现算法就是将传输的数据当作一个位数很长的数,将这个数除以约定的r位常数,得到的余数就是CRC校验结果。因此,发送端将校验数据附加到原数据后,接收端将所有数据收到,然后除以r,如果余数为0,则CRC正确。

CRC算法实现
CRC校验核心就是无借位的除法运算,假如生成多项式为1_0011_0001(简记为0x31),具体的计算实现代码框架如下:
(1)将CRC寄存器(8位,比生产多项式少1位)赋初值0
(2)在待传输信息流后面加入8个0
(3)while(数据未处理完)
(4)begin
(5)if(CRC寄存器的首位是1)
(6)reg = reg xor 0x31
(7)CRC寄存器左移一位,读入一个新的数据于CRC寄存器的0位的位置
(8)end

当数据流输入完毕后,CRC寄存器内保留的数据即为CRC结果。

上述算法如果在数据流的开头添加一些0,并不影响最后校验字的结果,这意味着对开头为多个0的CRC无法区分校验结果,因此真正实用的CRC都必须设定余数初始值和结果异或值。

所谓的余数初始值就是在计算CRC值的开始,给CRC寄存器一个初始值。
结果异或值是在其余计算完成后将CRC寄存器的值再与结果异或值进行异或操作以作为最后的校验值。

具体的CRC标准中,针对余数初始值和结果异或值的设定,网上可查。

针对余数初始值、结果异或值的CRC算法改进,其描述内容如下:
(1)设置CRC寄存器,并给其赋值为余数初始值
(2)将数据的第一个8位字符与CRC寄存器进行异或,并把结果存入CRC寄存器。
(3)CRC寄存器向右移1位,MSB补零,移出并检查LSB
(4)如果LSB为0,重复(3),如LSB为1,CRC寄存器与0x31相异或
(5)重复(3)和(4),直至8次移位全部完成,此时一个8位数据处理完毕
(6)重复(2)到(5),直到所有数据全部处理完成
(9)将最终CRC寄存器的内容与结果异或值进行异或操作后即为CRC值。

在线工具地址:http://www.ip33.com/crc.html。
CRC代码生成器的代码为:

////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 1999-2008 Easics NV.
// This source file may be used and distributed without restriction
// provided that this copyright statement is not removed from the file
// and that any derivative work contains the original copyright notice
// and the associated disclaimer.
//
// THIS SOURCE FILE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS
// OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
// WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
//
// Purpose : synthesizable CRC function
//   * polynomial: x^16 + x^12 + x^5 + 1
//   * data width: 16
//
// Info : [email protected]
//        http://www.easics.com
////////////////////////////////////////////////////////////////////////////////
module CRC16_D16;

  // polynomial: x^16 + x^12 + x^5 + 1
  // data width: 16
  // convention: the first serial bit is D[15]
  function [15:0] nextCRC16_D16;

    input [15:0] Data;
    input [15:0] crc;
    reg [15:0] d;
    reg [15:0] c;
    reg [15:0] newcrc;
  begin
    d = Data;
    c = crc;

    newcrc[0] = d[12] ^ d[11] ^ d[8] ^ d[4] ^ d[0] ^ c[0] ^ c[4] ^ c[8] ^ c[11] ^ c[12];
    newcrc[1] = d[13] ^ d[12] ^ d[9] ^ d[5] ^ d[1] ^ c[1] ^ c[5] ^ c[9] ^ c[12] ^ c[13];
    newcrc[2] = d[14] ^ d[13] ^ d[10] ^ d[6] ^ d[2] ^ c[2] ^ c[6] ^ c[10] ^ c[13] ^ c[14];
    newcrc[3] = d[15] ^ d[14] ^ d[11] ^ d[7] ^ d[3] ^ c[3] ^ c[7] ^ c[11] ^ c[14] ^ c[15];
    newcrc[4] = d[15] ^ d[12] ^ d[8] ^ d[4] ^ c[4] ^ c[8] ^ c[12] ^ c[15];
    newcrc[5] = d[13] ^ d[12] ^ d[11] ^ d[9] ^ d[8] ^ d[5] ^ d[4] ^ d[0] ^ c[0] ^ c[4] ^ c[5] ^ c[8] ^ c[9] ^ c[11] ^ c[12] ^ c[13];
    newcrc[6] = d[14] ^ d[13] ^ d[12] ^ d[10] ^ d[9] ^ d[6] ^ d[5] ^ d[1] ^ c[1] ^ c[5] ^ c[6] ^ c[9] ^ c[10] ^ c[12] ^ c[13] ^ c[14];
    newcrc[7] = d[15] ^ d[14] ^ d[13] ^ d[11] ^ d[10] ^ d[7] ^ d[6] ^ d[2] ^ c[2] ^ c[6] ^ c[7] ^ c[10] ^ c[11] ^ c[13] ^ c[14] ^ c[15];
    newcrc[8] = d[15] ^ d[14] ^ d[12] ^ d[11] ^ d[8] ^ d[7] ^ d[3] ^ c[3] ^ c[7] ^ c[8] ^ c[11] ^ c[12] ^ c[14] ^ c[15];
    newcrc[9] = d[15] ^ d[13] ^ d[12] ^ d[9] ^ d[8] ^ d[4] ^ c[4] ^ c[8] ^ c[9] ^ c[12] ^ c[13] ^ c[15];
    newcrc[10] = d[14] ^ d[13] ^ d[10] ^ d[9] ^ d[5] ^ c[5] ^ c[9] ^ c[10] ^ c[13] ^ c[14];
    newcrc[11] = d[15] ^ d[14] ^ d[11] ^ d[10] ^ d[6] ^ c[6] ^ c[10] ^ c[11] ^ c[14] ^ c[15];
    newcrc[12] = d[15] ^ d[8] ^ d[7] ^ d[4] ^ d[0] ^ c[0] ^ c[4] ^ c[7] ^ c[8] ^ c[15];
    newcrc[13] = d[9] ^ d[8] ^ d[5] ^ d[1] ^ c[1] ^ c[5] ^ c[8] ^ c[9];
    newcrc[14] = d[10] ^ d[9] ^ d[6] ^ d[2] ^ c[2] ^ c[6] ^ c[9] ^ c[10];
    newcrc[15] = d[11] ^ d[10] ^ d[7] ^ d[3] ^ c[3] ^ c[7] ^ c[10] ^ c[11];
    nextCRC16_D16 = newcrc;
  end
  endfunction
endmodule

此时需要修改应硬件代码:首先将代码从function转变为时序逻辑电路,其次,修改输入和输出,使CRC的输入输出大小端匹配,即输入是否需要进行位反正,输出是否需要整体位翻转。最后决定CRC是否需要和0xfffffff异或。

`timescale 1ns/1ps
module crc16_test (
    input     wire              i_clk                 , //时钟;
    input     wire              i_rst_n               , //同步复位;
    input     wire              i_din_valid           , //输入数据有效;
    input     wire    [15:0]    i_din                 , //输入数据;
    output    wire              o_dout_valid          , //输出CRC值有效;
    output    wire    [15:0]    o_dout                  //输出CRC;
);
reg [15:0] r_dout;
wire [15:0] d;
wire [15:0] c;
assign d = i_din;
assign c = r_dout;
always @(posedge i_clk) begin
    if (~i_rst_n)
        r_dout <= 16'hffff; //初始值为ffff;
    else if (i_din_valid)
    begin //计算逻辑;
        r_dout[0]  = d[12] ^ d[11] ^ d[8] ^ d[4] ^ d[0] ^ c[0] ^ c[4] ^ c[8] ^ c[11] ^ c[12];
        r_dout[1]  = d[13] ^ d[12] ^ d[9] ^ d[5] ^ d[1] ^ c[1] ^ c[5] ^ c[9] ^ c[12] ^ c[13];
        r_dout[2]  = d[14] ^ d[13] ^ d[10] ^ d[6] ^ d[2] ^ c[2] ^ c[6] ^ c[10] ^ c[13] ^ c[14];
        r_dout[3]  = d[15] ^ d[14] ^ d[11] ^ d[7] ^ d[3] ^ c[3] ^ c[7] ^ c[11] ^ c[14] ^ c[15];
        r_dout[4]  = d[15] ^ d[12] ^ d[8] ^ d[4] ^ c[4] ^ c[8] ^ c[12] ^ c[15];
        r_dout[5]  = d[13] ^ d[12] ^ d[11] ^ d[9] ^ d[8] ^ d[5] ^ d[4] ^ d[0] ^ c[0] ^ c[4] ^ c[5] ^ c[8] ^ c[9] ^ c[11] ^ c[12] ^ c[13];
        r_dout[6]  = d[14] ^ d[13] ^ d[12] ^ d[10] ^ d[9] ^ d[6] ^ d[5] ^ d[1] ^ c[1] ^ c[5] ^ c[6] ^ c[9] ^ c[10] ^ c[12] ^ c[13] ^ c[14];
        r_dout[7]  = d[15] ^ d[14] ^ d[13] ^ d[11] ^ d[10] ^ d[7] ^ d[6] ^ d[2] ^ c[2] ^ c[6] ^ c[7] ^ c[10] ^ c[11] ^ c[13] ^ c[14] ^ c[15];
        r_dout[8]  = d[15] ^ d[14] ^ d[12] ^ d[11] ^ d[8] ^ d[7] ^ d[3] ^ c[3] ^ c[7] ^ c[8] ^ c[11] ^ c[12] ^ c[14] ^ c[15];
        r_dout[9]  = d[15] ^ d[13] ^ d[12] ^ d[9] ^ d[8] ^ d[4] ^ c[4] ^ c[8] ^ c[9] ^ c[12] ^ c[13] ^ c[15];
        r_dout[10] = d[14] ^ d[13] ^ d[10] ^ d[9] ^ d[5] ^ c[5] ^ c[9] ^ c[10] ^ c[13] ^ c[14];
        r_dout[11] = d[15] ^ d[14] ^ d[11] ^ d[10] ^ d[6] ^ c[6] ^ c[10] ^ c[11] ^ c[14] ^ c[15];
        r_dout[12] = d[15] ^ d[8] ^ d[7] ^ d[4] ^ d[0] ^ c[0] ^ c[4] ^ c[7] ^ c[8] ^ c[15];
        r_dout[13] = d[9] ^ d[8] ^ d[5] ^ d[1] ^ c[1] ^ c[5] ^ c[8] ^ c[9];
        r_dout[14] = d[10] ^ d[9] ^ d[6] ^ d[2] ^ c[2] ^ c[6] ^ c[9] ^ c[10];
        r_dout[15] = d[11] ^ d[10] ^ d[7] ^ d[3] ^ c[3] ^ c[7] ^ c[10] ^ c[11];
    end
end
reg r_dout_valid = 0;
always @(posedge i_clk) //输入数据在一个时钟内完成CRC计算,下一个时钟输出;
begin
    r_dout_valid <= i_din_valid;
end

assign o_dout_valid = r_dout_valid;
assign o_dout = r_dout ;

endmodule // end the crc16_test model;

CRC的串行实现为:
LFSR计算CRC,可以用多项式G(x)表示,G(x) = X16+X12+X5+1模型可如下图所示。
在这里插入图片描述

module s_crc(
	input clk,
	input rst_n,
	input load,
	input [7:0] data,
	output reg done,
	output reg [7:0] crc
);

reg [2:0] cnt;
reg [7:0] datatemp;
reg [3:0] c_state,n_state;
parameter IDLE = 4'd0;
parameter CHANGE = 4'd1;
parameter DONE = 4'd2;

always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		c_state <= IDLE;
	end
	else begin
		c_state <= n_state;
	end
end


always @ (*) begin
	case(c_state)
		IDLE : begin
					if(load) begin
						n_state <= CHANGE;
					end
					else begin
						n_state <= IDLE;
					end
			   end
		CHANGE : begin
					if(cnt==3'd7) begin
						n_state <= DONE;
					end
					else begin
						n_state <= CHANGE; 
					end
			   end
		DONE : begin
					n_state <= IDLE;
				end
	endcase
end

always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		crc <= 8'b0;
	end
	else begin
		case(c_state)
			IDLE : begin
						done <= 1'b0;
						cnt <= 3'b0;
						if(load) datatemp <= data;
					end
			CHANGE : begin
						cnt <= cnt + 1'b1;
						crc <= {crc[6:0],1'b0} ^ ({8{(crc[7]^datatemp[cnt])}} & 8'h31);
					end
			DONE : begin
						crc <= crc;
						done <= 1'b1;
					end
		endcase
	end
end


endmodule

测试代码:

`timescale 1ps/1ps
module tb();
reg clk;
reg rst_n;
reg load;
reg [7:0] data;
wire done;
wire [7:0] crc;

s_crc u1(
	.clk(clk),
	.rst_n(rst_n),
	.load(load),
	.data(data),
	.done(done),
	.crc(crc)
);


initial begin
	clk=0;
	rst_n =0;
	#14;
	rst_n =1;
end

always #5 clk = ~clk;


initial begin
	load = 0;
	data = 8'h22;
	#17;
	@(posedge clk);
	load = 1;
	data = 8'h33;
	@(posedge clk);
	load = 0;
	data = 8'h11;
end




endmodule

猜你喜欢

转载自blog.csdn.net/weixin_43727437/article/details/106722142