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

news2024/11/25 7:05:40

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校验结果

  下面这张图是常用的一些参数模型。
在这里插入图片描述

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

在这里插入图片描述

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校验,直接将接收端接收到的数据+CRC校验码作为被除数,进行长除法,得到结果为0,校验通过,数据接收正确,数据接收正确,如下图所示。
在这里插入图片描述

1.2.2.2 数据段出错

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

1.2.2.3 CRC校验码段出错

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

二、CRC校验电路设计

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

2.1 串行CRC校验电路推导

2.1.1 长除法电路推导

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

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

  • 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

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

在这里插入图片描述
  那么,上述对应的是长除法的推导与电路,那么,网上的大多数教程给的电路如下图所示,这又是怎么实现的?
在这里插入图片描述

  对比这两个电路,刚开始,我以为这里只是简单的把输入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校验产生的余数。

在这里插入图片描述

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得出),如下图所示:

在这里插入图片描述

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

输入数据CRC校验码
000010011
000100110
001001100
010001011
100000101

  于是,根据 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校验码,如下图所示:
在这里插入图片描述
  根据上图,我们就可以分别得到寄存器初始值分别为0001、0010、0100、1000在采用CRC-4(x4+x+1)且输入数据为00000对应的CRC校验码,如下表:

寄存器初始值CRC校验码
00010110
00101100
01001011
10000101

  于是,根据 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仿真结果

在这里插入图片描述

  查看仿真结果,可以看到串行输入数据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,与仿真结果一致,仿真通过。

在这里插入图片描述

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,仿真通过。
在这里插入图片描述

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

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

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

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

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

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1033730.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Excel 语法

目录 语法 逐步创建公式 对单元格使用公式 另一个例子 语法 Excel中的一个公式用于进行数学计算。公式总是以单元格中键入的等号开头&#xff0c;然后是您的计算。 注意&#xff1a;您可以通过选择单元格并键入等号&#xff08;&#xff09;来声明该单元格 逐步创建公式…

探讨大型公共建筑能耗监测与信息管理系统研究及应用

安科瑞 华楠 摘要&#xff1a;文章通过阐述大型公共建筑能耗现状&#xff0c;突出大型公共建筑实施节能监管的必要性&#xff0c;并在系统总结运用技术手段实施建筑能耗监测的基础上&#xff0c;介绍了江苏省建筑能耗监测系统研究过程中的技术创新和应用情况。 关键词&#x…

宝塔部署node后使用pm2管理上传文件路径失效问题

如何进行文件上传&#xff1f; node上传文件 vue3 elementPlus 组件封装 在本地或者以宝塔终端的形式允许 上传后是没问题的&#xff0c;直接默认对multer直接写入路径就可以了 const multer require(multer) const upload multer({ dest: ./public/avataruploads/ }) …

TypeScript- 对于对象键名(包括函数键值)不确定的接口,可以使用字符串索引的形式

AXIOS树配置项 有一个需求&#xff0c;通过JSON数据&#xff0c;第一层是对应的页面对象&#xff08;比如是用户页面&#xff09;&#xff0c;第二层是该页面的API请求名&#xff08;比如用户的增删改查&#xff09;&#xff0c;第三层是该API的配置信息&#xff08;比如&…

大数取模运算Barrett reduction

Barrett reduction 约减概述 约减的定义(reduction): z ( m o d p ) z \pmod p z(modp) 优化约减的目的:取模操作的底层实现往往使用到的是除法&#xff0c;而除法操作往往是较为耗时的&#xff0c;因此需要把除法操作替换为不那么费时的其他操作。 Barrett 约减概述 单模数…

Python线程和进程

1、深度解析Python线程和进程 一篇文章带你深度解析Python线程和进程 - 知乎使用Python中的线程模块&#xff0c;能够同时运行程序的不同部分&#xff0c;并简化设计。如果你已经入门Python&#xff0c;并且想用线程来提升程序运行速度的话&#xff0c;希望这篇教程会对你有所帮…

FL Studio21.2中文版数字音乐制作软件

现在的FL也可以像splice一样啦&#xff0c;需要什么样的声音只需在fl里搜索&#xff0c;就会自动展示给你! FL Studio 简称FL&#xff0c;全称&#xff1a;Fruity Loops Studio&#xff0c;国人习惯叫它"水果"。软件现有版本是 FL Studio 21&#xff0c;已全面升级支…

Flask 数据库 连接池、DBUtils、http 连接池

1、DBUtils 简介、使用 DBUtils 简介 DBUtils 是一套用于管理 数据库 "连接池" 的Python包&#xff0c;为 "高频度、高并发" 的数据库访问提供更好的性能&#xff0c;可以自动管理连接对象的创建和释放。并允许对非线程安全的数据库接口进行线程安全包装…

基于若依ruoyi-nbcio增加flowable流程待办消息的提醒,并提供右上角的红字数字提醒(六)

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 这个部分主要是前端方面的。 1、在Navbar.vue显示右上角的图标栏里增加一项显示消息提醒的组件 <el-…

【数据结构】二叉树之堆的实现

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;数据结构 &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、二叉树的顺序结构 &#x1f4d2;1.1顺序存储 &#x1f4d2;1.2堆的性质…

【力扣每日一题】2023.9.23 树上的操作

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 这是一道程序设计类的题目&#xff0c;题目比较长&#xff0c;我稍微概括一下。 构造函数中给我们一个数组&#xff0c;第i个元素表示第…

进程的状态和转换

一、进程的状态 1.创建态&#xff1a;进程正在被创建的阶段。此阶段&#xff0c;操作系统会为进程分配资源、初始化PCB。 2.就绪态&#xff1a;进程创建完毕即进入就绪态。此阶段&#xff0c;进程具备已经具备所有运行条件&#xff0c;但是此时CPU比较繁忙&#xff0c;没有空闲…

5+非肿瘤+WGCNA+单细胞

今天给同学们分享一篇5非肿瘤WGCNA单细胞的生信文章“Integrative network-based analysis on multiple Gene Expression Omnibus datasets identifies novel immune molecular markers implicated in non-alcoholic steatohepatitis”&#xff0c;这篇文章于2023年3月16日发表…

基于SpringBoot的房产销售系统

目录 前言 一、技术栈 二、系统功能介绍 用户功能模块 管理员功能模块 销售经理功能模块 前台首页功能模块 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手…

Java对比对象修改前与修改后字段发生的变化

开发过程中&#xff0c;我们通常会对系统操作人对系统的操作进行记录&#xff0c;记录操作前后某个字段的变化&#xff0c;如下图 2. 提供一个工具类&#xff0c;可以比较一个对象里面&#xff0c;源对象&#xff0c;与修改后的对象&#xff0c;有哪些字段发生了改变&#…

地球系统模式(CESM)应用及进阶

目前通用地球系统模式&#xff08;Community Earth System Model&#xff0c;CESM&#xff09;在研究地球的过去、现在和未来的气候状况中具有越来越普遍的应用。CESM由美国NCAR于2010年07月推出以来&#xff0c;一直受到气候学界的密切关注。近年升级的CESM2.0在大气、陆地、海…

在pandas中使matplotlib动态画子图的两种方法【推荐gridspec】

先上对比图&#xff0c; 第一种方法&#xff0c;这里仅展示1个大区&#xff0c;多个的话需要加一层循环就可以了&#xff0c;主要是看子图的画法 当大区下面的国家为1个或2个时&#xff0c;会进行报错 # 获取非洲国家列表 african_countries df[df[大区] 南亚大区][进口国…

ARM DAY3

硬件模块与总线连接&#xff1a;各种硬件模块&#xff08;如 GPIO 控制器&#xff09;与 CPU&#xff08;或内核&#xff09;通过总线进行连接。这个总线负责数据和指令的传输。 特殊功能寄存器&#xff08;SFRs&#xff09;的角色&#xff1a;每个硬件模块内部都有一组特殊功…

PowerDesigner 连接 MYSQL

我使用的是powerDesigner16的版本&#xff0c;使用前先保证安装了 mysql odbc 驱动包 选择&#xff1a;文件 -> 反向工程 -> database… 一大波图片正在来袭。。。 点击确认 至此连接成功

解决方案 | 如何构建市政综合管廊安全运行监测系统?

如何构建市政综合管廊安全运行监测系统&#xff1f;WITBEE万宾城市生命线智能监测仪器&#xff0c;5年免维护设计&#xff0c;集成10多项结构与气体健康监测指标&#xff0c;毫秒级快速响应&#xff0c;时刻感知综合管廊运行态势