CRC校验原理与FPGA实现(含推导过程)

CRC校验原理与FPGA实现(含推导过程)

  • 写在前面
  • 一、CRC校验原理
    • 1.1 CRC校验基本概念
    • 1.2 CRC校验计算
      • 1.2.1 发送端CRC校验码计算
        • 1.2.1.1 CRC校验码计算方法
        • 1.2.1.2 CRC校验码计算例子
      • 1.2.2 接收端CRC校验
        • 1.2.2.1 校验通过
        • 1.2.2.2 数据段出错
        • 1.2.2.3 CRC校验码段出错
  • 二、CRC校验电路设计
    • 2.1 串行CRC校验电路推导
      • 2.1.1 长除法电路推导
      • 2.1.2 线性移位法电路推导
      • 2.1.3 串行CRC校验小结
    • 2.2 并行CRC校验电路推导(单个时钟出结果)
  • 三、RTL级代码
    • 3.1 长除法串行CRC校验RTL级代码
    • 3.2 线性移位寄存器法串行CRC校验RTL级代码
    • 3.3 并行CRC校验RTL级代码
  • 四、仿真结果
    • 4.1 长除法串行CRC校验仿真结果
      • 4.1.1 TestBench
      • 4.1.2仿真结果
    • 4.2 长除法串行CRC校验仿真结果
      • 4.2.1 TestBench
      • 4.1.2仿真结果
    • 4.3 并行CRC校验仿真结果
      • 4.3.1 TestBench
      • 4.3.2 仿真结果
  • 写在最后

写在前面

  CRC校验全称为循环冗余校验(Cyclic Redundancy Check),常用于数据传输中的错误检测。

一、CRC校验原理

1.1 CRC校验基本概念

  在学习CRC校验前,需要了解CRC校验中的几个基本概念:

  • NAME:参数模型名称,比如CRC-8、CRC-16、CRC-32等
  • WIDTH:CRC校验位宽度
  • POLY:多项式的简写,用十六进制表示。例如:CRC-8的多项式为x8+x2+x+1,对应的二进制表示为1_0000_0111,省略掉最高位“1”后,在转为十六进制,得到CRC-8的多项式简写0x07
  • INIT:开始进行CRC校验之前寄存器的初始化预置值,以十六进制表示,在不指定的情况下默认为0x00
  • REFIN:待进行CRC校验的数据是否进行高低位反转标志位,True或者False
  • REFOUT:输出的CRC校验结果(与XOROUT异或之后的结果)是否进行高低位反转标志位,True或者False
  • XOROUT:CRC校验计算结果与XOROUT进行异或后得到最终的CRC校验结果

  下面这张图是常用的一些参数模型。
CRC校验原理与FPGA实现(含推导过程)_第1张图片

1.2 CRC校验计算

1.2.1 发送端CRC校验码计算

1.2.1.1 CRC校验码计算方法

  发送端进行CRC校验码的计算可以归结为以下几个步骤:

  • Step1:将原始信息与INIT进行异或操作。若INIT=0x00,则原始信息与INIT进行异或操作后还是不变 1 ⊕ 0 = 1 1\oplus 0=1 10=1 0 ⊕ 0 = 0 0\oplus 0=0 00=0);若INIT=0xFF,则原始信息与INIT进行异或操作后相当于按位取反 1 ⊕ 1 = 0 1\oplus 1=0 11=0 1 ⊕ 0 = 1 1\oplus 0=1 10=1)。比如原始信息为10101010,当INIT为0x00时,进行异或操作得到的结果为10101010,当INIT为0xFF时,进行异操作的结果为01010101。
  • Step2:若REFIN为True,则Step1中的结果进行高低位反转,否则,不进行任何操作。比如REFIN为True时,1001101→1011001。
  • Step3:在Step2的计算结果后加入WIDTH位的0,WIDTH等于CRC模型的多项式最高次幂,比如CRC-4的多项式为x4+x+1,则在原始数据后加入4个0;
  • Step4:将Step3的计算结果作为被除数,根据多项式得到除数,进行模二除法,求得余数,取余数的低WIDTH位。比如,CRC-4的多项式为x4+x+1,则除数为10011,若被除数为10110110,则进行模二除法的结果为1000。
  • Step5:将Step4计算的结果与XOROUT进行异或。比如XOROUT为0xFF, 1000 ⊕ 0000 = 0111 1000\oplus 0000=0111 10000000=0111
  • Step6:若REFOUT为True,则对Step5的计算结果进行高低位反转,否则,不进行任何操作。该步骤计算得到的结果就是发送端的WIDTH比特的CRC校验码。比如REFIN为True时,1001101→1011001。
  • Step7:将Step6计算得到的WIDTH比特的CRC校验码与原始信息进行位拼接,原始信息在前,CRC校验码在后,得到发送端需要发送的数据+CRC校验码。比如,原始信息为10101010,CRC校验码为0111,则发送端需要发送的数据加CRC校验码为10101010_0111(0xAA7)。
1.2.1.2 CRC校验码计算例子

  假设在发送端需要校验的原始数据为0x35B0(11010110110000),采用上图中CRC-4的校验的参数模型(输入输出不反转,即REFIN与REFOUT为False)。其具体参数如下:

  • NAME: CRC-4
  • WIDTH : 4
  • POLY : 0x03(0000_0011)
  • INIT : 0x00
  • XOROUT = 0x00
  • REFIN:False
  • REFOUT:False

  那么,CRC-4的多项式x4+x+1对应的二进制表示为10011(即除数),原始数据为1101011011(10比特数据),在原始数据后加上WIDTH位的0得到被除数,根据长除法计算得到余数1110(4比特),即CRC校验码,将4比特校验码拼接在原始数据后,就得到了发送端需要发送的数据+CRC:11010110111110

CRC校验原理与FPGA实现(含推导过程)_第2张图片

1.2.2 接收端CRC校验

  在接收端,根据接收到的数据+CRC校验码判断接收到的数据数据是否正确,判断的方法有两种,但是其本质都是使用长除法。

  • 方法一:先从接收端接收到的数据+CRC校验码(14比特)中分离出数据段(10比特),再根据10比特数据使用长除法计算得到4比特CRC校验位(补0使用长除法,与接收端计算CRC校验码的方式一致),并与接收到的4比特CRC校验码进行对比,如果一致,则表示CRC校验通过,数据接收正确;否则,表示CRC校验不通过,接收端接收错误。
  • 方法二:将接收端接收到的数据+CRC校验(14比特),直接使用长除法进行计算,如果得到的CRC校验结果为0,则表示CRC校验通过,接收端接收正确;否则,则表示CRC校验不通过,接收端接收错误。

  在接收端进行CRC校验时,有一点值得注意:接收端CRC校验错误,有可能是由于CRC校验码接收错误,也有可能是由于数据接收错误。如果是接收到的数据是正确的,但是CRC校验码接收错误,进行CRC校验必然导致校验无法通过,那么在实际应用过程中如何解决这一问题?丢包重发?在后续的学习过程会对该问题进行解决,本文不讨论该问题的解决方法。

1.2.2.1 校验通过

  假设在接收端接收到的数据为11010100111110,这里接收到的数据段为1101010011,接收到的CRC校验码为1110。
  如果采用方法一进行CRC校验,对数据段1101010011补4个0,使用长除法进行计算,得到校验码为1110,与接收端接收到的CRC校验码对比,一致,校验通过,如下图所示。
CRC校验原理与FPGA实现(含推导过程)_第3张图片
  如果采用方法而进行CRC校验,直接将接收端接收到的数据+CRC校验码作为被除数,进行长除法,得到结果为0,校验通过,数据接收正确,数据接收正确,如下图所示。
CRC校验原理与FPGA实现(含推导过程)_第4张图片

1.2.2.2 数据段出错

  假设在接收端接收到的数据为11010100111110,这里在数据段中的第7比特出现错误,通过计算,得到计算的4比特CRC校验码为0101,与接收端接收到4比特CRC校验码1110进行对比,不相等,所以接收端接收到的数据+CRC中出现错误,如下图所示(这里使用方法一进行计算,方法二参照前面的例子进行计算,这里不展示)。
CRC校验原理与FPGA实现(含推导过程)_第5张图片

1.2.2.3 CRC校验码段出错

  再比如在接收端接收到的数据为11010110111111,这里在CRC校验码段中的第4比特出现错误(实际上应该是0),通过计算,得到计算的4比特CRC校验码为1110,与接收端接收到4比特CRC校验码1111进行对比,不相等,所以接收端接收到的数据+CRC中出现错误,如下图所示(这里使用方法一进行计算,方法二参照前面的例子进行计算,这里不展示)。
CRC校验原理与FPGA实现(含推导过程)_第6张图片

二、CRC校验电路设计

  根据CRC校验计算的原理,可以使用FPGA进行实现。CRC校验的硬件电路可以分为串行CRC校验(多个时钟出数据)和并行CRC校验(单个时钟出数据)。网上很多介绍CRC校验电路的文章,但是其中并没有给出为什么电路是这样的,根据原理怎么设计出该电路。比如:CRC-

2.1 串行CRC校验电路推导

2.1.1 长除法电路推导

  前面计算CRC校验码的过程中,当被除数的前几位连续为0时,直接进行连续左移操作,直到被除数的第1位不为0,与除数进行模二减法运算,重复该计算直到被除数的位数小于除数,得到的结果即CRC校验码。下面给出完整的计算过程。
CRC校验原理与FPGA实现(含推导过程)_第7张图片
  上面这种形式可能有些人看起来有点乱,那么把余数补全,就会更容易理解了,如下图所示。
CRC校验原理与FPGA实现(含推导过程)_第8张图片

  从上面完整的长除法计算过程中,可以看到,实际上长除法计算中,主要涉及的就是两大操作:模二减法移位操作。可以总结为以下几个步骤:

  • Step1:将除数10011最高位MSB与当前层级的被除数(位数与除数一致,5位)最高位MSB进行对齐;
  • Step2:若当前层级被除数的最高位MSB为1,则执行模二减法;若当前被除数的最高位MSB为0,则不进行任何操作;
  • Step3:对Step2的结果左移一位,得到下一层级的被除数;
  • Step4:重复Step1~Step3,直到被除数的位宽等于WIDTH比特,得到的被除数即长除法得到的结果,也就是CRC校验码;

  那么,假设当前层级的被除数(高WIDTH+1位,在上面这个例子中WIDTH=4)用a4a3a2a1a0表示,而下一层级的被除数用a4’a3’a2’a1’a0’表示,除数为g4g3g2g1g0=10011(对应CRC-4多项式),可以得到a4a3a2a1a0与a4’a3’a2’a1’a0’的关系如下:

i f   a 4 = 0 { a 4 ′ = a 3 a 3 ′ = a 2 a 2 ′ = a 1 a 1 ′ = a 0 a 1 ′ = d a t a _ i n (当 a 4 等于 0 ,做移位操作) if \ {a_4} = 0 \left\{ \begin{array}{c} {a_4}^\prime = {a_3} \\ {a_3}^\prime = {a_2} \\ {a_2}^\prime = {a_1} \\ {a_1}^\prime = {a_0} \\ {a_1}^\prime = {data\_in} \end{array}\right.(当a_4等于0,做移位操作) if a4=0 a4=a3a3=a2a2=a1a1=a0a1=data_in(当a4等于0,做移位操作)

i f   a 4 = 1 { a 4 ′ = a 3 ⊕ g 3 a 3 ′ = a 2 ⊕ g 2 a 2 ′ = a 1 ⊕ g 1 a 1 ′ = a 0 ⊕ g 0 a 0 ′ = d a t a _ i n (当 a 4 等于 1 ,做异或 + 移位操作) if \ {a_4} = 1 \left\{ \begin{array}{c} {a_4}^\prime = {a_3} \oplus {g_3} \\ {a_3}^\prime = {a_2} \oplus {g_2} \\ {a_2}^\prime = {a_1} \oplus {g_1} \\ {a_1}^\prime = {a_0} \oplus {g_0} \\ {a_0}^\prime = {data\_in} \end{array}\right.(当a_4等于1,做异或+移位操作) if a4=1 a4=a3g3a3=a2g2a2=a1g1a1=a0g0a0=data_in(当a4等于1,做异或+移位操作)
  那么,进一步的,可以对上面两个公式进行合并,得到如下公式(其中 a 4 ˜ \~{a_4} a4˜表示 a 4 {a_4} a4的取反):
{ a 4 ′ = a 4 ˜ a 3 + a 4 ( a 3 ⊕ g 3 ) a 3 ′ = a 4 ˜ a 2 + a 4 ( a 2 ⊕ g 2 ) a 2 ′ = a 4 ˜ a 1 + a 4 ( a 1 ⊕ g 1 ) a 1 ′ = a 4 ˜ a 0 + a 4 ( a 0 ⊕ g 0 ) a 0 ′ = d a t a _ i n \left\{ \begin{array}{c} {a_4}^\prime = \~{a_4}{a_3} + {a_4}( {a_3} \oplus {g_3}) \\ {a_3}^\prime = \~{a_4}{a_2} + {a_4}({a_2} \oplus {g_2}) \\ {a_2}^\prime = \~{a_4}{a_1} + {a_4}({a_1} \oplus {g_1}) \\ {a_1}^\prime = \~{a_4}{a_0} + {a_4}({a_0} \oplus {g_0}) \\ {a_0}^\prime = {data\_in} \end{array}\right. a4=a4˜a3+a4(a3g3)a3=a4˜a2+a4(a2g2)a2=a4˜a1+a4(a1g1)a1=a4˜a0+a4(a0g0)a0=data_in

  将该公式中的异或展开为与或的形式,可以得到如下公式:

{ a 4 ′ = a 4 ˜ a 3 + a 4 ( a 3 ˜ g 3 + a 3 g 3 ˜ ) a 3 ′ = a 4 ˜ a 2 + a 4 ( a 2 ˜ g 2 + a 2 g 2 ˜ ) a 2 ′ = a 4 ˜ a 1 + a 4 ( a 1 ˜ g 1 + a 1 g 1 ˜ ) a 1 ′ = a 4 ˜ a 0 + a 4 ( a 0 ˜ g 0 + a 0 g 0 ˜ ) a 0 ′ = d a t a _ i n \left\{ \begin{array}{c} {a_4}^\prime = \~{a_4}{a_3} + {a_4}( \~{a_3} {g_3}+ {a_3} { \~{g_3}}) \\ {a_3}^\prime = \~{a_4}{a_2} + {a_4}( \~{a_2} {g_2}+ {a_2} { \~{g_2}}) \\ {a_2}^\prime = \~{a_4}{a_1} + {a_4}( \~{a_1} {g_1}+ {a_1} { \~{g_1}}) \\ {a_1}^\prime = \~{a_4}{a_0} + {a_4}( \~{a_0} {g_0}+ {a_0} { \~{g_0}}) \\ {a_0}^\prime = {data\_in} \end{array}\right. a4=a4˜a3+a4(a3˜g3+a3g3˜)a3=a4˜a2+a4(a2˜g2+a2g2˜)a2=a4˜a1+a4(a1˜g1+a1g1˜)a1=a4˜a0+a4(a0˜g0+a0g0˜)a0=data_in

  由前面CRC模型的多项式可知, g 4 {g_4} g4=1, g 3 {g_3} g3=0, g 2 {g_2} g2=0, g 1 {g_1} g1=1, g 0 {g_0} g0=1,那么,可以得到 g 4 ˜ \~{g_4} g4˜=0, g 3 ˜ \~{g_3} g3˜=1, g 2 ˜ \~{g_2} g2˜=1, g 1 ˜ \~{g_1} g1˜=0, g 0 ˜ \~{g_0} g0˜=0,代入上述公式得到如下公式:
{ a 4 ′ = a 4 ˜ a 3 + a 4 a 3 a 3 ′ = a 4 ˜ a 2 + a 4 a 2 a 2 ′ = a 4 ˜ a 1 + a 4 a 1 ˜ a 1 ′ = a 4 ˜ a 0 + a 4 a 0 ˜ a 0 ′ = d a t a _ i n \left\{ \begin{array}{c} {a_4}^\prime = \~{a_4}{a_3} + {a_4}{a_3} \\ {a_3}^\prime = \~{a_4}{a_2} + {a_4}{a_2} \\ {a_2}^\prime = \~{a_4}{a_1} + {a_4}\~{a_1} \\ {a_1}^\prime = \~{a_4}{a_0} + {a_4}\~{a_0} \\ {a_0}^\prime = {data\_in} \end{array}\right. a4=a4˜a3+a4a3a3=a4˜a2+a4a2a2=a4˜a1+a4a1˜a1=a4˜a0+a4a0˜a0=data_in

  再将上述公式合并成异或形式,得到最终的公式:

{ a 4 ′ = a 3 a 3 ′ = a 2 a 2 ′ = a 4 ⊕ a 1 a 1 ′ = a 4 ⊕ a 0 a 0 ′ = d a t a _ i n \left\{ \begin{array}{c} {a_4}^\prime = {a_3} \\ {a_3}^\prime = {a_2} \\ {a_2}^\prime = {a_4} \oplus {a_1} \\ {a_1}^\prime = {a_4} \oplus {a_0} \\ {a_0}^\prime = {data\_in} \end{array}\right. a4=a3a3=a2a2=a4a1a1=a4a0a0=data_in

  于是,我们就可以根据上述公式画出相应的电路图,如下图所示。

在这里插入图片描述
  那么,上述对应的是长除法的推导与电路,那么,网上的大多数教程给的电路如下图所示,这又是怎么实现的?
CRC校验原理与FPGA实现(含推导过程)_第9张图片

  对比这两个电路,刚开始,我以为这里只是简单的把输入data_in接的寄存器去掉,直接把data_in接到异或电路上,这是大错特错,在后续的仿真部分可以看到错的有多离谱。那么,这是怎么实现的,在去掉一个寄存器的情况下,还能保证计算正确的?事实上,这是用线性反馈移位寄存器法设计的CRC校验电路,在下一节我们将进行介绍。

2.1.2 线性移位法电路推导

  假设当前需要校验的信息为b4b3b2b1b0,采用CRC-4校验模型,其多项式对应的二进制表示为g4g3g2g1g0=10011,假设其计算产生的校验码为C3C2C1C0,即:
b 4 b 3 b 2 b 1 b 0 0000 模二除 g 4 g 3 g 2 g 1 g 0 的余数为 C 3 C 2 C 1 C 0 b_4b_3b_2b_1b_00000模二除g_4g_3g_2g_1g_0的余数为C_3C_2C_1C_0 b4b3b2b1b00000模二除g4g3g2g1g0的余数为C3C2C1C0
  这里我们假设需要校验的信息是5比特的,那么,如果在该基础上加1比特新数据m,即此时需要校验的信息为 b 4 b 3 b 2 b 1 b 0 m b_4b_3b_2b_1b_0m b4b3b2b1b0m,那么假设其计算产生的校验码为 C 3 ′ C 2 ′ C 1 ′ C 0 ′ C_3'C_2'C_1'C_0' C3C2C1C0,即:
b 4 b 3 b 2 b 1 b 0 m 0000 模二除 g 4 g 3 g 2 g 1 g 0 的余数为 C 3 ′ C 2 ′ C 1 ′ C 0 ′ b_4b_3b_2b_1b_0m0000模二除g_4g_3g_2g_1g_0的余数为C_3'C_2'C_1'C_0' b4b3b2b1b0m0000模二除g4g3g2g1g0的余数为C3C2C1C0 C 3 ′ C 2 ′ C 1 ′ C 0 ′ = b 4 b 3 b 2 b 1 b 0 m 0000 模二除 g 4 g 3 g 2 g 1 g 0 的余数 C_3'C_2'C_1'C_0'=b_4b_3b_2b_1b_0m0000模二除g_4g_3g_2g_1g_0的余数 C3C2C1C0=b4b3b2b1b0m0000模二除g4g3g2g1g0的余数

  在这里, C 3 ′ C 2 ′ C 1 ′ C 0 ′ C_3'C_2'C_1'C_0' C3C2C1C0 C 3 C 2 C 1 C 0 C_3C_2C_1C_0 C3C2C1C0的关系是什么(这是线性移位寄存器法电路设计的核心所在)?实际上, b 4 b 3 b 2 b 1 b 0 m 0000 = b 4 b 3 b 2 b 1 b 0 00000 ⊕ m 0000 b_4b_3b_2b_1b_0m0000=b_4b_3b_2b_1b_000000 \oplus m0000 b4b3b2b1b0m0000=b4b3b2b1b000000m0000,所以 b 4 b 3 b 2 b 1 b 0 m 0000 模二除 g 4 g 3 g 2 g 1 g 0 b_4b_3b_2b_1b_0m0000模二除g_4g_3g_2g_1g_0 b4b3b2b1b0m0000模二除g4g3g2g1g0可以根据分配律进行拆分,即:
C 3 ′ C 2 ′ C 1 ′ C 0 ′ = b 4 b 3 b 2 b 1 b 0 m 0000 模二除 g 4 g 3 g 2 g 1 g 0 的余数 = ( b 4 b 3 b 2 b 1 b 0 00000 模二除 g 4 g 3 g 2 g 1 g 0 的余数 ) ⊕ ( m 0000 模二除 g 4 g 3 g 2 g 1 g 0 的余数 ) C_3'C_2'C_1'C_0'=b_4b_3b_2b_1b_0m0000模二除g_4g_3g_2g_1g_0的余数 \\ = (b_4b_3b_2b_1b_000000模二除g_4g_3g_2g_1g_0的余数) \oplus (m0000模二除g_4g_3g_2g_1g_0的余数) C3C2C1C0=b4b3b2b1b0m0000模二除g4g3g2g1g0的余数=(b4b3b2b1b000000模二除g4g3g2g1g0的余数)(m0000模二除g4g3g2g1g0的余数)
  拆分之后,我们需要计算的包括两块:

  • b 4 b 3 b 2 b 1 b 0 00000 模二除 g 4 g 3 g 2 g 1 g 0 的余数 b_4b_3b_2b_1b_000000模二除g_4g_3g_2g_1g_0的余数 b4b3b2b1b000000模二除g4g3g2g1g0的余数
  • m 0000 模二除 g 4 g 3 g 2 g 1 g 0 的余数 m0000模二除g_4g_3g_2g_1g_0的余数 m0000模二除g4g3g2g1g0的余数

  其中, b 4 b 3 b 2 b 1 b 0 00000 模二除 g 4 g 3 g 2 g 1 g 0 的余数 = C 3 C 2 C 1 C 0 0 模二除 g 4 g 3 g 2 g 1 g 0 的余数 b_4b_3b_2b_1b_000000模二除g_4g_3g_2g_1g_0的余数=C_3C_2C_1C_00模二除g_4g_3g_2g_1g_0的余数 b4b3b2b1b000000模二除g4g3g2g1g0的余数=C3C2C1C00模二除g4g3g2g1g0的余数,所以进一步的,可以得到:
C 3 ′ C 2 ′ C 1 ′ C 0 ′ = b 4 b 3 b 2 b 1 b 0 m 0000 模二除 g 4 g 3 g 2 g 1 g 0 的余数 = ( b 4 b 3 b 2 b 1 b 0 00000 模二除 g 4 g 3 g 2 g 1 g 0 的余数 ) ⊕ ( m 0000 模二除 g 4 g 3 g 2 g 1 g 0 的余数 ) = ( C 3 C 2 C 1 C 0 0 模二除 g 4 g 3 g 2 g 1 g 0 的余数 ) ⊕ ( m 0000 模二除 g 4 g 3 g 2 g 1 g 0 的余数 ) = ( C 3 C 2 C 1 C 0 0 ⊕ m 0000 ) 模二除 g 4 g 3 g 2 g 1 g 0 的余数 = t C 2 C 1 C 0 0 模二除 g 4 g 3 g 2 g 1 g 0 的余数 C_3'C_2'C_1'C_0'=b_4b_3b_2b_1b_0m0000模二除g_4g_3g_2g_1g_0的余数 \\ = (b_4b_3b_2b_1b_000000模二除g_4g_3g_2g_1g_0的余数) \oplus (m0000模二除g_4g_3g_2g_1g_0的余数) \\ = (C_3C_2C_1C_00模二除g_4g_3g_2g_1g_0的余数) \oplus (m0000模二除g_4g_3g_2g_1g_0的余数) \\ = (C_3C_2C_1C_00 \oplus m0000)模二除g_4g_3g_2g_1g_0的余数 \\ = tC_2C_1C_00 模二除g_4g_3g_2g_1g_0的余数 C3C2C1C0=b4b3b2b1b0m0000模二除g4g3g2g1g0的余数=(b4b3b2b1b000000模二除g4g3g2g1g0的余数)(m0000模二除g4g3g2g1g0的余数)=(C3C2C1C00模二除g4g3g2g1g0的余数)(m0000模二除g4g3g2g1g0的余数)=(C3C2C1C00m0000)模二除g4g3g2g1g0的余数=tC2C1C00模二除g4g3g2g1g0的余数

  其中, t = ( C 3 ⊕ m ) t=(C_3\oplus m) t=(C3m) g 4 g 3 g 2 g 1 g 0 = 10011 g_4g_3g_2g_1g_0=10011 g4g3g2g1g0=10011所以,可以得到以下关系式:

t = 1 t=1 t=1时, C 3 ′ C 2 ′ C 1 ′ C 0 ′ = 1 C 2 C 1 C 0 0 − 10011 = C 2 C 1 C ˜ 0 1 C_3'C_2'C_1'C_0'=1C_2C_1C_00-10011=C_2C_1\~C_01 C3C2C1C0=1C2C1C0010011=C2C1C˜01
t = 0 t=0 t=0时, C 3 ′ C 2 ′ C 1 ′ C 0 ′ = 0 C 2 C 1 C 0 0 − 00000 = C 2 C 1 C 0 0 C_3'C_2'C_1'C_0'=0C_2C_1C_00-00000=C_2C_1C_00 C3C2C1C0=0C2C1C0000000=C2C1C00

  所以可以得到 C 3 ′ C 2 ′ C 1 ′ C 0 ′ C_3'C_2'C_1'C_0' C3C2C1C0 C 3 C 2 C 1 C 0 C_3C_2C_1C_0 C3C2C1C0的关系如下:
C 3 ′ C 2 ′ C 1 ′ C 0 ′ = C 2 C 1 ( C 0 ⊕ t ) t C_3'C_2'C_1'C_0'=C_2C_1(C_0 \oplus t)t C3C2C1C0=C2C1(C0t)t
{ C 3 ′ = C 2 C 2 ′ = C 1 C 1 ′ = C 0 ⊕ t = C 0 ⊕ C 3 ⊕ m C 0 ′ = C 3 ⊕ m \left\{ \begin{array}{c} {C_3}^\prime = {C_2} \\ {C_2}^\prime = {C_1} \\ {C_1}^\prime = {C_0} \oplus t = {C_0} \oplus {C_3} \oplus m \\ {C_0}^\prime = {C_3} \oplus m \end{array}\right. C3=C2C2=C1C1=C0t=C0C3mC0=C3m
  根据上述得到的 C 3 ′ C 2 ′ C 1 ′ C 0 ′ C_3'C_2'C_1'C_0' C3C2C1C0 C 3 C 2 C 1 C 0 C_3C_2C_1C_0 C3C2C1C0的关系可以画出相应的电路图,如下图所示。这里, C 3 C 2 C 1 C 0 C_3C_2C_1C_0 C3C2C1C0表示输入一组序列进行CRC校验产生的余数, C 3 ′ C 2 ′ C 1 ′ C 0 ′ C_3'C_2'C_1'C_0' C3C2C1C0表示在该序列的基础上,加一比特后再进行CRC校验产生的余数。

CRC校验原理与FPGA实现(含推导过程)_第10张图片

2.1.3 串行CRC校验小结

  在前面,我们推导了串行CRC校验电路的两种设计方法对应的电路,那么,这两种电路的区别在哪里?根据前面的推导过程,不难看出,长除法电路的设计是站在被除数的角度进行设计的,即下一层级的被除数a4’a3’a2’a1’a0’与当前层级被除数a4a3a2a1a0的关系是什么样的,而线性移位寄存器法电路的设计是站在序列模二除多项式的余数角度进行设计的,即添加1比特数据后整个序列的模二除多项式的余数与未添加1比特数据整个序列的模二除多项式的余数的关系是什么样。

2.2 并行CRC校验电路推导(单个时钟出结果)

  在前面,我们所设计的电路都是串行的,但是,对于很多场景,我们需要高并行度的CRC校验,网上也给出了很多并行CRC校验的Verilog代码生成,比如Easics或者Generator for CRC HDL code,可以进行配置,然后生成CRC校验的Verilog代码。那么串行CRC校验的公式又是如何得到的?实际上,并行CRC校验电路就是根据线性移位寄存器法设计的串行CRC校验电路推导得到的。
  再讲解并行CRC校验公式的推导前,我们要先知道CRC校验服从分配律,即:
a 4 a 3 a 2 a 1 a 0 = ( b 4 b 3 b 2 b 1 b 0 ) ⊕ ( c 4 c 3 c 2 c 1 c 0 ) a_4a_3a_2a_1a_0=(b_4b_3b_2b_1b_0)\oplus(c_4c_3c_2c_1c_0) a4a3a2a1a0=(b4b3b2b1b0)(c4c3c2c1c0)
  则
a 4 a 3 a 2 a 1 a 0 的校验码 = ( b 4 b 3 b 2 b 1 b 0 的校验码 ) ⊕ ( c 4 c 3 c 2 c 1 c 0 的校验码 ) a_4a_3a_2a_1a_0的校验码=(b_4b_3b_2b_1b_0的校验码)\oplus(c_4c_3c_2c_1c_0的校验码) a4a3a2a1a0的校验码=(b4b3b2b1b0的校验码)(c4c3c2c1c0的校验码)
  那么,任何一个数据 a 4 a 3 a 2 a 1 a 0 a_4a_3a_2a_1a_0 a4a3a2a1a0(假设CRC校验数据为5比特)都可以由00001、00010、00100、01000、10000异或得到,所以我们只需要提前计算好00001、00010、00100、01000、10000在寄存器初始值为0000的情况下的CRC校验码,然后,根据CRC校验的分配律可以得出,任何一个数据 a 4 a 3 a 2 a 1 a 0 a_4a_3a_2a_1a_0 a4a3a2a1a0的CRC校验码都可以由00001、00010、00100、01000、10000在寄存器初始值为0000情况下的CRC校验码异或得到。在这里,我们对照着前面得到的线性反馈移位寄存器法设计的CRC校验电路一步一步计算得到输入数据为00001、00010、00100、01000、10000在采用CRC-4(x4+x+1)且寄存器初始值为0对应的CRC校验码(也可以使用长除法手算或者通过在线CRC校验计算ip33.com得出),如下图所示:

CRC校验原理与FPGA实现(含推导过程)_第11张图片

  根据上图,我们就可以分别得到输入数据为00001、00010、00100、01000、10000在采用CRC-4(x4+x+1)且寄存器初始值为0对应的CRC校验码,如下表:

输入数据 CRC校验码
00001 0011
00010 0110
00100 1100
01000 1011
10000 0101

  于是,根据 a 4 a 3 a 2 a 1 a 0 a_4a_3a_2a_1a_0 a4a3a2a1a0与00001、00010、00100、01000、10000的关系:
a 4 a 3 a 2 a 1 a 0 = ( a 0 ( 00001 ) ) ⊕ ( a 1 ( 00010 ) ) ⊕ ( a 2 ( 00100 ) ) ⊕ ( a 3 ( 01000 ) ) ⊕ ( a 4 ( 10000 ) ) a_4a_3a_2a_1a_0=(a_0(00001))\oplus(a_1(00010))\oplus(a_2(00100))\oplus(a_3(01000))\oplus(a_4(10000)) a4a3a2a1a0=(a0(00001))(a1(00010))(a2(00100))(a3(01000))(a4(10000))

  可以得到
a 4 a 3 a 2 a 1 a 0 的校验码 = ( a 0 ( 00001 ) 的校验码 ) ⊕ ( a 1 ( 00010 ) 的校验码 ) ⊕ ( a 2 ( 00100 ) 的校验码 ) ⊕ ( a 3 ( 01000 ) 的校验码 ) ⊕ ( a 4 ( 10000 ) 的校验码 ) = ( a 0 ( 0011 ) ) ⊕ ( a 1 ( 0110 ) ) ⊕ ( a 2 ( 1100 ) ) ⊕ ( a 3 ( 1011 ) ) ⊕ ( a 4 ( 0101 ) ) a_4a_3a_2a_1a_0的校验码=(a_0(00001)的校验码)\oplus(a_1(00010)的校验码)\oplus(a_2(00100)的校验码)\oplus(a_3(01000)的校验码)\oplus(a_4(10000)的校验码)\\ =(a_0(0011))\oplus(a_1(0110))\oplus(a_2(1100))\oplus(a_3(1011))\oplus(a_4(0101)) a4a3a2a1a0的校验码=(a0(00001)的校验码)(a1(00010)的校验码)(a2(00100)的校验码)(a3(01000)的校验码)(a4(10000)的校验码)=(a0(0011))(a1(0110))(a2(1100))(a3(1011))(a4(0101))
  那么,设寄存器初始值为0,输入数据为 a 4 a 3 a 2 a 1 a 0 a_4a_3a_2a_1a_0 a4a3a2a1a0,计算得到的校验码为 C 3 C 2 C 1 C 0 C_3C_2C_1C_0 C3C2C1C0,则根据上式可以得到以下并行CRC校验的公式:
{ C 3 = ( a 0 . 0 ) ⊕ ( a 1 . 0 ) ⊕ ( a 2 . 1 ) ⊕ ( a 3 . 1 ) ⊕ ( a 4 . 0 ) = a 2 ⊕ a 3 C 2 = ( a 0 . 0 ) ⊕ ( a 1 . 1 ) ⊕ ( a 2 . 1 ) ⊕ ( a 3 . 0 ) ⊕ ( a 4 . 1 ) = a 1 ⊕ a 2 ⊕ a 4 C 1 = ( a 0 . 1 ) ⊕ ( a 1 . 1 ) ⊕ ( a 2 . 0 ) ⊕ ( a 3 . 1 ) ⊕ ( a 4 . 0 ) = a 0 ⊕ a 1 ⊕ a 3 C 0 = ( a 0 . 1 ) ⊕ ( a 1 . 0 ) ⊕ ( a 2 . 0 ) ⊕ ( a 3 . 1 ) ⊕ ( a 4 . 1 ) = a 0 ⊕ a 3 . ⊕ a 4 \left\{ \begin{array}{c} {C_3} = (a_0.0) \oplus (a_1.0) \oplus (a_2.1) \oplus (a_3.1) \oplus (a_4.0)= a_2\oplus a_3\\ {C_2} = (a_0.0) \oplus (a_1.1) \oplus (a_2.1) \oplus (a_3.0) \oplus (a_4.1)=a_1\oplus a_2 \oplus a_4\\ {C_1} = (a_0.1) \oplus (a_1.1) \oplus (a_2.0) \oplus (a_3.1) \oplus (a_4.0)=a_0 \oplus a_1 \oplus a_3 \\ {C_0} = (a_0.1) \oplus (a_1.0) \oplus (a_2.0) \oplus (a_3.1) \oplus (a_4.1)=a_0 \oplus a_3. \oplus a_4 \end{array}\right. C3=(a0.0)(a1.0)(a2.1)(a3.1)(a4.0)=a2a3C2=(a0.0)(a1.1)(a2.1)(a3.0)(a4.1)=a1a2a4C1=(a0.1)(a1.1)(a2.0)(a3.1)(a4.0)=a0a1a3C0=(a0.1)(a1.0)(a2.0)(a3.1)(a4.1)=a0a3.a4
  在前面,我们假设的寄存器的初始值为0000,如果寄存器的初始值不为0000呢?假设寄存器的初始值为 m 3 m 2 m 1 m 0 m_3m_2m_1m_0 m3m2m1m0,输入待校验数据为00000,实际上寄存器的初始值也服从分配律,即
m 3 m 2 m 1 m 0 = ( n 3 n 2 n 1 n 0 ) ⊕ ( k 3 k 2 k 1 k 0 ) m_3m_2m_1m_0=(n_3n_2n_1n_0)\oplus(k_3k_2k_1k_0) m3m2m1m0=(n3n2n1n0)(k3k2k1k0)

初始值为 m 3 m 2 m 1 m 0 ,输入为 00000 的校验码 = ( 初始值为 n 3 n 2 n 1 n 0 ,输入为 00000 的校验码 ) ⊕ ( 初始值为 k 3 k 2 k 1 k 0 ,输入为 00000 的校验码 ) 初始值为m_3m_2m_1m_0,输入为00000的校验码=(初始值为n_3n_2n_1n_0,输入为00000的校验码)\oplus(初始值为k_3k_2k_1k_0,输入为00000的校验码) 初始值为m3m2m1m0,输入为00000的校验码=(初始值为n3n2n1n0,输入为00000的校验码)(初始值为k3k2k1k0,输入为00000的校验码)
  所以,任意一个初始值 m 3 m 2 m 1 m 0 m_3m_2m_1m_0 m3m2m1m0都可以由0001、0010、0100、1000异或得到,可以先计算出寄存器的初始值分别为0001、0010、0100、1000,输入待验证数据为00000的CRC校验码。然后,任意一个初始值为 m 3 m 2 m 1 m 0 m_3m_2m_1m_0 m3m2m1m0,输入数据为00000的校验码都可以由初始值为0001、0010、0100、1000,输入数据为00000的校验码异或得到。在这里,我们对照着前面线性移位寄存器法设计的CRC校验电路,一步一步计算得到寄存器初始值分别为0001、0010、0100、1000在采用CRC-4(x4+x+1)且输入数据为00000对应的CRC校验码,如下图所示:
CRC校验原理与FPGA实现(含推导过程)_第12张图片
  根据上图,我们就可以分别得到寄存器初始值分别为0001、0010、0100、1000在采用CRC-4(x4+x+1)且输入数据为00000对应的CRC校验码,如下表:

寄存器初始值 CRC校验码
0001 0110
0010 1100
0100 1011
1000 0101

  于是,根据 m 3 m 2 m 1 m 0 m_3m_2m_1m_0 m3m2m1m0与0001、0010、0100、1000的关系:
m 3 m 2 m 1 m 0 = ( m 0 ( 0001 ) ) ⊕ ( m 1 ( 0010 ) ) ⊕ ( m 2 ( 0100 ) ) ⊕ ( m 3 ( 1000 ) ) m_3m_2m_1m_0=(m_0(0001))\oplus(m_1(0010))\oplus(m_2(0100))\oplus(m_3(1000)) m3m2m1m0=(m0(0001))(m1(0010))(m2(0100))(m3(1000))

  可以得到
初始值为 m 3 m 2 m 1 m 0 ,输入为 00000 的校验码 = ( 初始值为 m 0 ( 0001 ) ,输入为 00000 的校验码 ) ⊕ ( 初始值为 a 1 ( 00010 ) ,输入为 00000 的校验码 ) ⊕ ( 初始值为 a 2 ( 00100 ) ,输入为 00000 的校验码 ) ⊕ ( 初始值为 a 3 ( 01000 ) ,输入为 00000 的校验码 ) ⊕ ( 初始值为 a 4 ( 10000 ) ,输入为 00000 的校验码 ) = ( m 0 ( 0110 ) ) ⊕ ( m 1 ( 1100 ) ) ⊕ ( m 2 ( 1011 ) ) ⊕ ( m 3 ( 0101 ) ) 初始值为m_3m_2m_1m_0,输入为00000的校验码=(初始值为m_0(0001),输入为00000的校验码)\oplus(初始值为a_1(00010),输入为00000的校验码)\oplus(初始值为a_2(00100),输入为00000的校验码)\oplus(初始值为a_3(01000),输入为00000的校验码)\oplus(初始值为a_4(10000),输入为00000的校验码)\\ =(m_0(0110))\oplus(m_1(1100))\oplus(m_2(1011))\oplus(m_3(0101)) 初始值为m3m2m1m0,输入为00000的校验码=(初始值为m0(0001),输入为00000的校验码)(初始值为a1(00010),输入为00000的校验码)(初始值为a2(00100),输入为00000的校验码)(初始值为a3(01000),输入为00000的校验码)(初始值为a4(10000),输入为00000的校验码)=(m0(0110))(m1(1100))(m2(1011))(m3(0101))
  那么,设寄存器初始值为 m 3 m 2 m 1 m 0 m_3m_2m_1m_0 m3m2m1m0,输入数据为00000,计算得到的CRC校验码为 C 3 C 2 C 1 C 0 C_3C_2C_1C_0 C3C2C1C0,则根据上式可以得到以下并行CRC校验的公式:
{ C 3 = ( m 0 . 0 ) ⊕ ( m 1 . 1 ) ⊕ ( m 2 . 1 ) ⊕ ( m 3 . 0 ) = m 1 ⊕ m 2 C 2 = ( m 0 . 1 ) ⊕ ( m 1 . 1 ) ⊕ ( m 2 . 0 ) ⊕ ( m 3 . 1 ) = m 0 ⊕ m 1 ⊕ m 3 C 1 = ( m 0 . 1 ) ⊕ ( m 1 . 0 ) ⊕ ( m 2 . 1 ) ⊕ ( m 3 . 0 ) = m 0 ⊕ m 2 C 0 = ( m 0 . 0 ) ⊕ ( m 1 . 0 ) ⊕ ( m 2 . 1 ) ⊕ ( m 3 . 1 ) = m 2 ⊕ m 3 \left\{ \begin{array}{c} {C_3} = (m_0.0) \oplus (m_1.1) \oplus (m_2.1) \oplus (m_3.0) = m_1\oplus m_2\\ {C_2} = (m_0.1) \oplus (m_1.1) \oplus (m_2.0) \oplus (m_3.1) = m_0\oplus m_1 \oplus m_3\\ {C_1} = (m_0.1) \oplus (m_1.0) \oplus (m_2.1) \oplus (m_3.0) = m_0 \oplus m_2\\ {C_0} = (m_0.0) \oplus (m_1.0) \oplus (m_2.1) \oplus (m_3.1) = m_2 \oplus m_3 \end{array}\right. C3=(m0.0)(m1.1)(m2.1)(m3.0)=m1m2C2=(m0.1)(m1.1)(m2.0)(m3.1)=m0m1m3C1=(m0.1)(m1.0)(m2.1)(m3.0)=m0m2C0=(m0.0)(m1.0)(m2.1)(m3.1)=m2m3
  于是,根据前面两种情况:(1)寄存器初始值为0,输入数据为 a 4 a 3 a 2 a 1 a 0 a_4a_3a_2a_1a_0 a4a3a2a1a0的CRC校验码; (2)寄存器初始值为 m 3 m 2 m 1 m 0 m_3m_2m_1m_0 m3m2m1m0,输入数据为0000的CRC校验码;我们可以得到寄存器初始值为 m 3 m 2 m 1 m 0 m_3m_2m_1m_0 m3m2m1m0,输入数据为 a 4 a 3 a 2 a 1 a 0 a_4a_3a_2a_1a_0 a4a3a2a1a0的CRC校验码,公式如下:
{ C 3 = ( m 0 . 0 ) ⊕ ( m 1 . 1 ) ⊕ ( m 2 . 1 ) ⊕ ( m 3 . 0 ) = m 1 ⊕ m 2 ⊕ a 2 ⊕ a 3 C 2 = ( m 0 . 1 ) ⊕ ( m 1 . 1 ) ⊕ ( m 2 . 0 ) ⊕ ( m 3 . 1 ) = m 0 ⊕ m 1 ⊕ m 3 ⊕ a 1 ⊕ a 2 ⊕ a 4 C 1 = ( m 0 . 1 ) ⊕ ( m 1 . 0 ) ⊕ ( m 2 . 1 ) ⊕ ( m 3 . 0 ) = m 0 ⊕ m 2 ⊕ a 0 ⊕ a 1 ⊕ a 3 C 0 = ( m 0 . 0 ) ⊕ ( m 1 . 0 ) ⊕ ( m 2 . 1 ) ⊕ ( m 3 . 1 ) = m 2 ⊕ m 3 ⊕ a 0 ⊕ a 3 . ⊕ a 4 \left\{ \begin{array}{c} {C_3} = (m_0.0) \oplus (m_1.1) \oplus (m_2.1) \oplus (m_3.0) = m_1\oplus m_2 \oplus a_2\oplus a_3\\ {C_2} = (m_0.1) \oplus (m_1.1) \oplus (m_2.0) \oplus (m_3.1) = m_0\oplus m_1 \oplus m_3\oplus a_1\oplus a_2 \oplus a_4\\ {C_1} = (m_0.1) \oplus (m_1.0) \oplus (m_2.1) \oplus (m_3.0) = m_0 \oplus m_2\oplus a_0 \oplus a_1 \oplus a_3 \\ {C_0} = (m_0.0) \oplus (m_1.0) \oplus (m_2.1) \oplus (m_3.1) = m_2 \oplus m_3\oplus a_0 \oplus a_3. \oplus a_4 \end{array}\right. C3=(m0.0)(m1.1)(m2.1)(m3.0)=m1m2a2a3C2=(m0.1)(m1.1)(m2.0)(m3.1)=m0m1m3a1a2a4C1=(m0.1)(m1.0)(m2.1)(m3.0)=m0m2a0a1a3C0=(m0.0)(m1.0)(m2.1)(m3.1)=m2m3a0a3.a4
  需要注意的时,在这里我们推导了待校验CRC数据为5比特时的并行CRC校验公式,对于不同校验长度的并行CRC校验公式,是不同的,这里我们仅提供一种方法,对于其他校验长度的并行CRC校验公式,可以按照上述步骤自行推导。如果仅仅只是想要使用并行CRC校验的Vrilog代码,可以通过Easics或者Generate for CRC HDL code直接生成。

三、RTL级代码

  对应着上面推导得到的三种不同CRC校验电路设计方法的公式,我们编写RTL级代码,如下(注意我们这里CRC校验不对输出结果进行异或操作,需要的可以在输出端自行处理)。

3.1 长除法串行CRC校验RTL级代码

`timescale 1ns/1ns

module crc4_d10_serial_1
#(
    parameter   CRC_WIDTH  = 4, //CRC校验码宽度
    parameter   DATA_WIDTH = 5  //CRC校验数据长度
)
(
    input   wire                        clk           , //时钟信号
    input   wire                        rst_n         , //复位信号
    input   wire                        crc_en        , //CRC校验使能信号
    input   wire                        data_in_serial, //输入校验数据(串行)
    output  wire    [CRC_WIDTH-1:0]     data_out      , //输出CRC校验码(并行输出)
    output  wire                        dout_vld        //输出CRC校验码有效信号
);

reg                              r_data_in_serial; //输出校验数据打一拍
reg   [CRC_WIDTH:0]              r_data_out; //移位寄存器数据
reg   [$clog2(DATA_WIDTH)-1:0]   cnt       ; //计数器(用于计数产生校验结束)
reg                              r_crc_en  ; //CRC校验工作信号(在该信号为高电平期间进行CRC校验)
reg                              r_crc_done; //CRC校验结束信号打一拍

wire                             crc_done  ; //CRC校验结束信号

//移位寄存器
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        r_data_out <= 4'd0;
    else if(r_crc_en) begin
        r_data_out[0] <= r_data_in_serial;
        r_data_out[1] <= r_data_out[4] ^ r_data_out[0];
        r_data_out[2] <= r_data_out[4] ^ r_data_out[1];
        r_data_out[3] <= r_data_out[2];
        r_data_out[4] <= r_data_out[3];
    end
    else
        r_data_out <= 4'd0;
end

//输入数据打拍
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        r_data_in_serial <= 1'b0;
    else
        r_data_in_serial <= data_in_serial;
end

//CRC校验工作信号产生
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        r_crc_en <= 1'b0;
    else if(crc_en)
        r_crc_en <= 1'b1;
    else if(crc_done)
        r_crc_en <= 1'b0;
    else
        r_crc_en <= r_crc_en;
end

//CRC校验周期计数器
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt <= 'd0;
    else if(r_crc_en)
        if(cnt == CRC_WIDTH + DATA_WIDTH - 1)
            cnt <= 'd0;
        else
            cnt <= cnt + 1'b1;
    else
        cnt <= 'd0;
end

//CRC校验结束信号打拍
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        r_crc_done <= 1'b0;
    else
        r_crc_done <= crc_done;
end

//CRC校验结束信号产生
assign crc_done = (cnt == CRC_WIDTH + DATA_WIDTH - 1) ? 1'b1 : 1'b0;

//CRC校验码有效信号
assign dout_vld = r_crc_done;

//CRC校验结果
assign data_out = dout_vld ? r_data_out[CRC_WIDTH-1:0] : 'd0;

endmodule

3.2 线性移位寄存器法串行CRC校验RTL级代码

  线性移位寄存器法串行CRC校验的RTL级代码,相比于长除法的RTL代码,其区别主要在如下几点(假设采用的CRC校验模型CRC校验码为CRC_WIDTH,需要进行CRC校验的二进制数据的位宽为DATA_WIDTH):

  • 寄存器少了一个,长除法中寄存器长度为CRC_WIDTH+1,而线性移位寄存器只需要CRC_WIDTH个寄存器,线性移位寄存器法使用的寄存器更少
  • 线性移位寄存器法计算CRC校验码只需要经过DATA_WIDTH个时钟周期,就可以计算得到CRC校验码,而长除法需要CRC_WIDTH+DATA_WIDTH个时钟周期才能得到CRC校验码,线性移位寄存器法计算CRC校验码消耗时钟周期更少
  • 两者的递推公式不一致,也就导致了电路的不一致(可返回查看《2.1 串行CRC校验电路推导》的内容)
`timescale 1ns/1ns

module crc4_d10_serial_2
#(
    parameter   CRC_WIDTH  = 4, //CRC校验码宽度
    parameter   DATA_WIDTH = 5  //CRC校验数据长度
)
(
    input   wire                        clk           , //时钟信号
    input   wire                        rst_n         , //复位信号
    input   wire                        crc_en        , //CRC校验使能信号
    input   wire                        data_in_serial, //输入校验数据(串行)
    output  wire    [CRC_WIDTH-1:0]     data_out      , //输出CRC校验码(并行输出)
    output  wire                        dout_vld        //输出CRC校验码有效信号
);

reg                              r_data_in_serial; //输出校验数据打一拍
reg   [CRC_WIDTH-1:0]            r_data_out; //移位寄存器数据
reg   [$clog2(DATA_WIDTH)-1:0]   cnt       ; //计数器(用于计数产生校验结束)
reg                              r_crc_en  ; //CRC校验工作信号(在该信号为高电平期间进行CRC校验)
reg                              r_crc_done; //CRC校验结束信号打一拍

wire                             crc_done  ; //CRC校验结束信号

//移位寄存器
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        r_data_out <= 'd0;
    else if(r_crc_en) begin
        r_data_out[0] <= r_data_out[3] ^ r_data_in_serial;
        r_data_out[1] <= r_data_out[3] ^ r_data_out[0] ^ r_data_in_serial;
        r_data_out[2] <= r_data_out[1];
        r_data_out[3] <= r_data_out[2];
    end
    else
        r_data_out <= 4'd0;
end

//输入数据打拍
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        r_data_in_serial <= 1'b0;
    else
        r_data_in_serial <= data_in_serial;
end

//CRC校验工作信号产生
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        r_crc_en <= 1'b0;
    else if(crc_en)
        r_crc_en <= 1'b1;
    else if(crc_done)
        r_crc_en <= 1'b0;
    else
        r_crc_en <= r_crc_en;
end

//CRC校验周期计数器
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        cnt <= 'd0;
    else if(r_crc_en)
        if(cnt == DATA_WIDTH - 1)
            cnt <= 'd0;
        else
            cnt <= cnt + 1'b1;
    else
        cnt <= 'd0;
end

//CRC校验结束信号打拍
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        r_crc_done <= 1'b0;
    else
        r_crc_done <= crc_done;
end

//CRC校验结束信号产生
assign crc_done = (cnt == DATA_WIDTH - 1) ? 1'b1 : 1'b0;

//CRC校验码有效信号
assign dout_vld = r_crc_done;

//CRC校验结果
assign data_out = dout_vld ? r_data_out : 'd0;

endmodule

3.3 并行CRC校验RTL级代码

`timescale 1ns/1ns

module crc4_d10_parallel
#(
    parameter   CRC_WIDTH  = 4, //CRC校验码宽度
    parameter   DATA_WIDTH = 5  //CRC校验数据长度
)
(
    input   wire                        clk             , //时钟信号
    input   wire                        rst_n           , //复位信号
    input   wire                        crc_en          , //CRC校验使能信号
    input   wire    [CRC_WIDTH-1:0]     crc_initial     , //寄存器初始值
    input   wire    [DATA_WIDTH-1:0]    data_in_parallel, //输入校验数据(串行)
    output  wire    [CRC_WIDTH-1:0]     data_out        , //输出CRC校验码(并行输出)
    output  wire                        dout_vld          //输出CRC校验码有效信号
);

reg    [CRC_WIDTH-1:0]     r_data_out;
reg                        r_dout_vld;

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        r_data_out <= 'd0;
    else if(crc_en)begin
        r_data_out[3] <= crc_initial[1] ^ crc_initial[2] ^ data_in_parallel[2] ^ data_in_parallel[3];
        r_data_out[2] <= crc_initial[0] ^ crc_initial[1] ^ crc_initial[3] ^ data_in_parallel[1] ^ data_in_parallel[2] ^ data_in_parallel[4];
        r_data_out[1] <= crc_initial[0] ^ crc_initial[2] ^ data_in_parallel[0] ^ data_in_parallel[1] ^ data_in_parallel[3];
        r_data_out[0] <= crc_initial[2] ^ crc_initial[3] ^ data_in_parallel[0] ^ data_in_parallel[3] ^ data_in_parallel[4];
    end
    else
        r_data_out <= 'd0;
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        r_dout_vld <= 1'b0;
    else if(crc_en)
        r_dout_vld <= 1'b1;
    else
        r_dout_vld <= 1'b0;
end

assign data_out = r_data_out;
assign dout_vld = r_dout_vld;

endmodule

四、仿真结果

4.1 长除法串行CRC校验仿真结果

4.1.1 TestBench

`timescale 1ns/1ns
module tb_crc4_d10_serial_1();
    parameter   CRC_WIDTH  = 4 ; //CRC校验码宽度
    parameter   DATA_WIDTH = 10; //CRC校验数据长度
    reg                         clk           ;
    reg                         rst_n         ;
    reg                         crc_en        ;
    reg                         data_in_serial;
    wire    [CRC_WIDTH-1:0]     data_out      ;
    wire                        dout_vld      ;

    initial begin
        clk = 1'b0;
        rst_n = 1'b1;
        crc_en <= 1'b0;
        data_in_serial <= 1'b0;
        
        #20
        rst_n = 1'b0;        
        #200
        rst_n = 1'b1;

        #20
        data_in_serial <= 1'b1;
        crc_en <= 1'b1;
        #20
        data_in_serial <= 1'b1;
        crc_en <= 1'b0;
        #20
        data_in_serial <= 1'b0;
        #20
        data_in_serial <= 1'b1;
        #20
        data_in_serial <= 1'b0;
        #20
        data_in_serial <= 1'b1; 
        #20
        data_in_serial <= 1'b1;
        #20
        data_in_serial <= 1'b0;        
        #20
        data_in_serial <= 1'b1; 
        #20
        data_in_serial <= 1'b1;
        #20
        data_in_serial <= 1'b0;    
        #20
        data_in_serial <= 1'b0;              
        #20
        data_in_serial <= 1'b0;    
        #20
        data_in_serial <= 1'b0;  
        #100
        $finish;               
    end

    always #10 clk = ~clk;
  

crc4_d10_serial_1
#(
    .CRC_WIDTH (CRC_WIDTH ), //CRC校验码宽度
    .DATA_WIDTH(DATA_WIDTH)  //CRC校验数据长度
)
crc4_d10_serial_1_inst
(
    .clk           (clk           ),
    .rst_n         (rst_n         ),
    .crc_en        (crc_en        ),
    .data_in_serial(data_in_serial),
    .data_out      (data_out      ),
    .dout_vld      (dout_vld      ) 
);

endmodule

4.1.2仿真结果

CRC校验原理与FPGA实现(含推导过程)_第13张图片

  查看仿真结果,可以看到串行输入数据11010110110000(后4位0000为长除法填充的4个0,实际CRC校验的数据为1101011011),得到的CRC校验结果为1110。可以通过ip33.com验证计算结果的正确性:1101011011的十六进制表示为10’h35B,设置参数模型为自定义(因为标准的CRC-4参数模型CRC-4/ITU是包含输入数据反转和输出数据反转操作的,而我们编写的CRC校验代码中没有进行输入数据反转和输出数据反转),设置宽度WIDTH、多项式POLY、初始值INIT、结果异或值XOROUT、输入数据反转、输出数据反转等参数,计算得到10’h35B的使用CRC-4(x4+x+1)模型得到的CRC校验码为1110,与仿真结果一致,仿真通过。

CRC校验原理与FPGA实现(含推导过程)_第14张图片

4.2 长除法串行CRC校验仿真结果

4.2.1 TestBench

`timescale 1ns/1ns
module tb_crc4_d10_serial_2();
    parameter   CRC_WIDTH  = 4 ; //CRC校验码宽度
    parameter   DATA_WIDTH = 10; //CRC校验数据长度
    reg                         clk           ;
    reg                         rst_n         ;
    reg                         crc_en        ;
    reg                         data_in_serial;
    wire    [CRC_WIDTH-1:0]     data_out      ;
    wire                        dout_vld      ;

    initial begin
        clk = 1'b0;
        rst_n = 1'b1;
        crc_en <= 1'b0;
        data_in_serial <= 1'b0;
        
        #20
        rst_n = 1'b0;        
        #200
        rst_n = 1'b1;
        
        #20
        data_in_serial <= 1'b1;
        crc_en <= 1'b1;
        #20
        data_in_serial <= 1'b1;
        crc_en <= 1'b0;
        #20
        data_in_serial <= 1'b0;
        #20
        data_in_serial <= 1'b1;
        #20
        data_in_serial <= 1'b0;
        #20
        data_in_serial <= 1'b1; 
        #20
        data_in_serial <= 1'b1;
        #20
        data_in_serial <= 1'b0;        
        #20
        data_in_serial <= 1'b1; 
        #20
        data_in_serial <= 1'b1;
        #20
        data_in_serial <= 1'b0;    
        #20
        data_in_serial <= 1'b0;              
        #20
        data_in_serial <= 1'b0;    
        #20
        data_in_serial <= 1'b0;  
        #100
        $finish;               
    end

    always #10 clk = ~clk;
  

crc4_d10_serial_2
#(
    .CRC_WIDTH (CRC_WIDTH ), //CRC校验码宽度
    .DATA_WIDTH(DATA_WIDTH)  //CRC校验数据长度
)
crc4_d10_serial_2_inst
(
    .clk           (clk           ),
    .rst_n         (rst_n         ),
    .crc_en        (crc_en        ),
    .data_in_serial(data_in_serial),
    .data_out      (data_out      ),
    .dout_vld      (dout_vld      ) 
);

endmodule

4.1.2仿真结果

  查看仿真结果,经过10个时钟周期,生成的CRC校验码为1110,仿真通过。
CRC校验原理与FPGA实现(含推导过程)_第15张图片

4.3 并行CRC校验仿真结果

4.3.1 TestBench

`timescale 1ns/1ns
module tb_crc4_d10_parallel();
    parameter   CRC_WIDTH  = 4 ; //CRC校验码宽度
    parameter   DATA_WIDTH = 5; //CRC校验数据长度
    reg                         clk             ;
    reg                         rst_n           ;
    reg                         crc_en          ;
    reg     [CRC_WIDTH-1:0]     crc_initial     ;
    reg     [DATA_WIDTH-1:0]    data_in_parallel;
    wire    [CRC_WIDTH-1:0]     data_out        ;
    wire                        dout_vld        ;

    initial begin
        clk = 1'b0;
        rst_n = 1'b1;
        crc_en <= 1'b0;
        data_in_parallel <= 5'b00000;
        crc_initial <= 4'b0000;
        
        #20
        rst_n = 1'b0;        
        #200
        rst_n = 1'b1;
        
        #20
        data_in_parallel <= 5'b10101;
        crc_en <= 1'b1;
        #20
        crc_en <= 1'b0;  
        #100
        $finish;               
    end

    always #10 clk = ~clk;
  

crc4_d10_parallel
#(
    .CRC_WIDTH (CRC_WIDTH ), //CRC校验码宽度
    .DATA_WIDTH(DATA_WIDTH)  //CRC校验数据长度
)
crc4_d10_parallel_inst
(
    .clk                (clk                ),
    .rst_n              (rst_n              ),
    .crc_en             (crc_en             ),
    .crc_initial        (crc_initial        ),      
    .data_in_parallel   (data_in_parallel   ),
    .data_out           (data_out           ),
    .dout_vld           (dout_vld           ) 
);

endmodule

4.3.2 仿真结果

CRC校验原理与FPGA实现(含推导过程)_第16张图片

CRC校验原理与FPGA实现(含推导过程)_第17张图片

写在最后

  在本文,我们学习了CRC校验的基本原理、CRC校验的长除法手算,并对CRC串行电路与并行电路进行推导,其中串行电路包括长除法对应的电路和线性移位寄存器法设计的电路。那么,无论是长除法、线性移位寄存器法设计的串行CRC校验电路,还是并行CRC校验电路,究其本质,都是在探讨电路中的寄存器现态的次态的关系,但不同在于现态表示的是什么,次态表示的又是什么,如下表所示。

寄存器现态 寄存器次态
长除法(串行CRC) 当前层级被除数 下一层级被除数
线性移位寄存器法(串行CRC) 当前整个序列的模二除多项式的余数 添加1比特数据后整个序列的模二除多项式的余数
并行CRC 1100

另外,在本文中,我们还介绍了三个CRC校验相关的常用网站:

  • ip33.com(计算CRC校验结果)
  • Easics(生成并行CRC校验代码)
  • Generator for CRC HDL code(生成并行CRC校验代码)

  本文到此结束,欢迎评论区交流探讨。

在这里插入图片描述

你可能感兴趣的:(通信相关,fpga开发,CRC,循环冗余校验,Verilog)