简介
在数据通信中,接收端通常需要检测传输过程中是否发生差错,常用的方法包括: 奇偶校验(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 : tools@easics.be
// 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