CRC生成算法入门到实战:从原理到Verilog

1.CRC算法的原理

CRC即循环冗余校验码:是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性。
CRC的算法在数学和通信上已经有过很多的学者对其研究,本文引用了参考文章1的方式对CRC的原理进行解读,这篇文章对于算法的解读浅显易懂,但也在性能方面浅尝辄止,不过对于应用来说戳戳有余,方便工程师快速对CRC算法理解并入门。

1.1 奇偶校验

所谓通讯过程的校验是指在通讯数据后加上一些附加信息,通过这些附加信息来判断接收到的数据是否和发送出的数据相同。CRC校验和奇偶校验就是如此。比如说RS232串行通讯可以设置奇偶校验位,所谓奇偶校验就是在发送的每一个字节后都加上一位,使得每个字节中1的个数为奇数个或偶数个。
采用奇校验,则在数据后补上个0,数据变为0001 1010 0,数据中1的个数为奇数个(3个)
采用偶校验,则在数据后补上个1,数据变为0001 1010 1,数据中1的个数为偶数个(4个)
奇偶校验也可以是最简单的一种CRC校验。

1.2 CRC校验

CRC校验算法的基本思想是将传输的数据当做一个位数很长的数。将这个数除以另一个数。得到的余数作为校验数据附加到原数据后面。
假设要传输的数据为:1101011011
我们还需要一个除数,这个除数来自于我们的CRC生成多项式,比如:
x 4 + x + 1 x^4+x+1 x4+x+1
其对应的除数为 1 0 0 1 1,这个除数事实上为生成多项式中x的n次方的系数
这里我们对待传输数据后补充4个0,意味着实际校验位的长度为(生成多项式系数长度-1)位,也是(生成多项式最高次项次数)位
在这里插入图片描述
上图来自参考文章1中,虽然是二进制除法,但是除法使用的是伽罗华域-2的模二除法,单从结果上来说,模二除就是对两位作异或,比如 1 模二除 0 = 1 ; 1 模二除 1 = 0;
最后的结果为 1110
将1110替换掉原来的0000,即可产生含有CRC校验码的数据

2. Matlab测试

既然清楚了生成CRC校验码的原理,尝试用Matlab编写一份函数方便测试,代码源自参考文章2

%CRC循环冗余编码函数
function crc_encode_scr=crc_encode(scr, poly)
%scr为信息多项式系数向量,poly为生成多项式系数向量[],
%例如,若系数为1100,则输入为[1 1 0 0]
[M,N]=size(poly);
scrg=[scr zeros(1,N-1)];%在信息多项式系数后补零
[q,r]=deconv(scrg, poly);
%多项式除法q中为商,r为余数,此为十进制多项式除法
r=abs(r);
for i=1:length(r)
  a=r(i);
  if(mod(a,2)==0)%将余数变为模二结果
    r(i)=0;
  else
    r(i)=1;
  end
end
crc=r(length(scr)+1:end);%获取余项
crc_encode_scr=bitor(scrg,r);%余项加在信息位后面构成编码

这份代码中使用的除法为deconv()函数,用于去卷积,本质上等于scrg/poly=q…r,然后对余数各个项取余得到模二除法结果,实际测试与计算结果完全一样

close all;clear all;clc;
% 测试计算CRC结果
% 生成多项式为 x^4 + x + 1
scr = [1 1 0 1 0 1 1 0 1 1];
poly_1 = [1 0 0 1 1];
crc_result = crc_encode(scr,poly_1);

3.Verilog实现

实际应用于FPGA工程时,我们往往不会使用上述流程所对应的串行算法,这样十分消耗时序,本文介绍一种并行结构的用法。在网上很多人推荐https://www.easics.com/crctool/的 CRC_Genetion_Tool 已经404了,这个网站笔者曾在一年前使用过,所生成的代码无需做任何修改就可以使用,简单方便。
本文介绍来自OutputLogic的CRC生成工具的用法,地址:http://outputlogic.com/?page_id=321,如果你在阅读这篇文章时此网站也不幸失效,可以下载它的离线版本:链接:https://pan.baidu.com/s/1VbulX74sri89c6KBOgigFQ?pwd=ad7m 提取码:ad7m
网页版本使用方法:
在这里插入图片描述
第一行填入数据长度,第二行填入校验码的位数,点击Apply
在这里插入图片描述
第二步只需要选择生成多项式中除了最高次项以外的其他项就可以,因为生成次数的最高次项总是存在的,例如我这里需要生成的生成多项式为 x^4 + x + 1,我选择1 和 x^1 即可。生成的代码如下:

//-----------------------------------------------------------------------------
// Copyright (C) 2009 OutputLogic.com
// 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.
//-----------------------------------------------------------------------------
// CRC module for data[9:0] ,   crc[3:0]=1+x^1+x^4;
//-----------------------------------------------------------------------------
module crc(
  input [9:0] data_in,
  input crc_en,
  output [3:0] crc_out,
  input rst,
  input clk);
  
  reg [3:0] lfsr_q,lfsr_c;
  
  assign crc_out = lfsr_q;
  
  always @(*) begin
    lfsr_c[0] = lfsr_q[0] ^ lfsr_q[2] ^ lfsr_q[3] ^ data_in[0] ^ data_in[3] ^ data_in[4] ^ data_in[6] ^ data_in[8] ^ data_in[9];
    lfsr_c[1] = lfsr_q[0] ^ lfsr_q[1] ^ lfsr_q[2] ^ data_in[0] ^ data_in[1] ^ data_in[3] ^ data_in[5] ^ data_in[6] ^ data_in[7] ^ data_in[8];
    lfsr_c[2] = lfsr_q[0] ^ lfsr_q[1] ^ lfsr_q[2] ^ lfsr_q[3] ^ data_in[1] ^ data_in[2] ^ data_in[4] ^ data_in[6] ^ data_in[7] ^ data_in[8] ^ data_in[9];
    lfsr_c[3] = lfsr_q[1] ^ lfsr_q[2] ^ lfsr_q[3] ^ data_in[2] ^ data_in[3] ^ data_in[5] ^ data_in[7] ^ data_in[8] ^ data_in[9];
    
  end // always
  
  always @(posedge clk, posedge rst) begin
    if(rst) begin
      lfsr_q <= {4{1'b1}};
    end
    else begin
      lfsr_q <= crc_en ? lfsr_c : lfsr_q;
    end
  end // always
endmodule // crc

离线版本使用方法:
在解压后含有crc-gen.exe的位置用命令提示符打开,用法为:
crc-gen language data_width poly_width poly_string
参数分别可选为:
language: verilog or vhdl
data_width:数据长度
poly_width:校验码的位数
poly_string:生成多项式字符形式
最后一个参数以16进制码的形式输入,但和网页版一样,不需要最高次项
例如生成多项式: x^4 + x + 1,其对应除数为:1 0011,应舍去最高位变为 0000 0011,对应16进制为0x03 参数直接输入03即可,
对于本文算例,命令为:crc-gen verilog 10 4 03 返回代码为:

//-----------------------------------------------------------------------------
// Copyright (C) 2009 OutputLogic.com
// 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.
//-----------------------------------------------------------------------------
// CRC module for
//       data[9:0]
//       crc[3:0]=1+x^1+x^4;
//
module crc(
        input [9:0] data_in,
        input        crc_en,
        output [3:0] crc_out,
        input        rst,
        input        clk);

        reg [3:0] lfsr_q,
                   lfsr_c;
        assign crc_out = lfsr_q;
        always @(*) begin
                lfsr_c[0] = lfsr_q[0] ^ lfsr_q[2] ^ lfsr_q[3] ^ data_in[0] ^ data_in[3] ^ data_in[4] ^ data_in[6] ^ data_in[8] ^ data_in[9];
                lfsr_c[1] = lfsr_q[0] ^ lfsr_q[1] ^ lfsr_q[2] ^ data_in[0] ^ data_in[1] ^ data_in[3] ^ data_in[5] ^ data_in[6] ^ data_in[7] ^ data_in[8];
                lfsr_c[2] = lfsr_q[0] ^ lfsr_q[1] ^ lfsr_q[2] ^ lfsr_q[3] ^ data_in[1] ^ data_in[2] ^ data_in[4] ^ data_in[6] ^ data_in[7] ^ data_in[8] ^ data_in[9];
                lfsr_c[3] = lfsr_q[1] ^ lfsr_q[2] ^ lfsr_q[3] ^ data_in[2] ^ data_in[3] ^ data_in[5] ^ data_in[7] ^ data_in[8] ^ data_in[9];


        end // always

        always @(posedge clk, posedge rst) begin
                if(rst) begin
                        lfsr_q  <= {4{1'b1}};
                end
                else begin
                        lfsr_q  <= crc_en ? lfsr_c : lfsr_q;
                end
        end // always
endmodule // crc

与网页端一致。
由于代码和我们常用的CRC生成算法不太一致,所以需要对代码进行修改

  1. 将移位寄存器的初值全置为0

修改后的代码如下:

`timescale 1ns / 1ps
//-----------------------------------------------------------------------------
// Copyright (C) 2009 OutputLogic.com
// 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.
//-----------------------------------------------------------------------------
// CRC module for
//       data[9:0]
//       crc[3:0]=1+x^1+x^4;
//
module crc_10_4(
        input [9:0] data_in,
        input        crc_en,
        output [3:0] crc_out,
        input        rst,
        input        clk);

        reg [3:0] lfsr_q,
                   lfsr_c;
        assign crc_out = lfsr_q;
        always @(*) begin
                lfsr_c[0] = lfsr_q[0] ^ lfsr_q[2] ^ lfsr_q[3] ^ data_in[0] ^ data_in[3] ^ data_in[4] ^ data_in[6] ^ data_in[8] ^ data_in[9];
                lfsr_c[1] = lfsr_q[0] ^ lfsr_q[1] ^ lfsr_q[2] ^ data_in[0] ^ data_in[1] ^ data_in[3] ^ data_in[5] ^ data_in[6] ^ data_in[7] ^ data_in[8];
                lfsr_c[2] = lfsr_q[0] ^ lfsr_q[1] ^ lfsr_q[2] ^ lfsr_q[3] ^ data_in[1] ^ data_in[2] ^ data_in[4] ^ data_in[6] ^ data_in[7] ^ data_in[8] ^ data_in[9];
                lfsr_c[3] = lfsr_q[1] ^ lfsr_q[2] ^ lfsr_q[3] ^ data_in[2] ^ data_in[3] ^ data_in[5] ^ data_in[7] ^ data_in[8] ^ data_in[9];


        end // always

        always @(posedge clk, posedge rst) begin
                if(rst) begin
                        lfsr_q  <= {4{1'b0}}; //此处校验位初始化为全0
                end
                else begin
                        lfsr_q  <= crc_en ? lfsr_c : lfsr_q;
                end
        end // always
endmodule // crc

Testbench如下:

`timescale 1ns / 1ps

// Author: Yuanzhi76
// Create Date: 2022/07/27 16:10:48
// Tool Versions: Vivado 2021.1
// Version: 0.1

module crc_10_4_tb;
    reg                     clk;
    reg                     rst;
    reg     [9:0]           data_in;
    reg                     crc_en;
    
    wire    [3:0]           crc_out;

    always #5 clk = ~clk; //100MHz

    crc_10_4 U1(
        .clk(clk)
        ,.rst(rst)
        ,.data_in(data_in)
        ,.crc_en(crc_en)
        ,.crc_out(crc_out)
    );

    initial begin
        clk = 1;
        rst = 1;
        data_in = 10'b1101011011;//结果应为1110
        crc_en = 0;
        #100;
        rst = 0;
        crc_en = 1;
        #10;
        crc_en = 0;//一个时钟后关闭enable端口,锁定数据
        //开始对第二个数据进行CRC编码时注意复位
        rst = 1;
        data_in = 10'b1110111011;//结果应为1101
        #10;
        rst = 0;
        crc_en = 1;
        #10;
        crc_en = 0;
    end
endmodule

上述仿真结果如图所示:
在这里插入图片描述
注意:

  1. 此模块输入数据后只给到此模块一个时钟周期后锁定,否则此模块会将初始数据变更为非全0的其他数据,并基于此数据运算CRC校验码,显然违反并行结构的前提条件
  2. 每次重新运算需要复位,保证lfsr_q的初值全为0

参考:

  1. 写给嵌入式程序员的循环冗余校验(CRC)算法入门引导
  2. 循环冗余码CRC使用matlab实现
  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值