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

这篇具有很好参考价值的文章主要介绍了CRC校验原理与FPGA实现(含推导过程)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

写在前面

  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实现,通信相关,fpga开发,CRC,循环冗余校验,Verilog

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实现,通信相关,fpga开发,CRC,循环冗余校验,Verilog

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实现,通信相关,fpga开发,CRC,循环冗余校验,Verilog
  如果采用方法而进行CRC校验,直接将接收端接收到的数据+CRC校验码作为被除数,进行长除法,得到结果为0,校验通过,数据接收正确,数据接收正确,如下图所示。
crc校验原理与fpga实现,通信相关,fpga开发,CRC,循环冗余校验,Verilog

1.2.2.2 数据段出错

  假设在接收端接收到的数据为11010100111110,这里在数据段中的第7比特出现错误,通过计算,得到计算的4比特CRC校验码为0101,与接收端接收到4比特CRC校验码1110进行对比,不相等,所以接收端接收到的数据+CRC中出现错误,如下图所示(这里使用方法一进行计算,方法二参照前面的例子进行计算,这里不展示)。
crc校验原理与fpga实现,通信相关,fpga开发,CRC,循环冗余校验,Verilog

1.2.2.3 CRC校验码段出错

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

二、CRC校验电路设计

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

2.1 串行CRC校验电路推导

2.1.1 长除法电路推导

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

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

  • 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实现,通信相关,fpga开发,CRC,循环冗余校验,Verilog
  那么,上述对应的是长除法的推导与电路,那么,网上的大多数教程给的电路如下图所示,这又是怎么实现的?
crc校验原理与fpga实现,通信相关,fpga开发,CRC,循环冗余校验,Verilog

  对比这两个电路,刚开始,我以为这里只是简单的把输入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实现,通信相关,fpga开发,CRC,循环冗余校验,Verilog

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实现,通信相关,fpga开发,CRC,循环冗余校验,Verilog

  根据上图,我们就可以分别得到输入数据为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实现,通信相关,fpga开发,CRC,循环冗余校验,Verilog
  根据上图,我们就可以分别得到寄存器初始值分别为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实现,通信相关,fpga开发,CRC,循环冗余校验,Verilog

  查看仿真结果,可以看到串行输入数据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实现,通信相关,fpga开发,CRC,循环冗余校验,Verilog

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实现,通信相关,fpga开发,CRC,循环冗余校验,Verilog

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实现,通信相关,fpga开发,CRC,循环冗余校验,Verilog

crc校验原理与fpga实现,通信相关,fpga开发,CRC,循环冗余校验,Verilog

写在最后

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

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

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

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

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

crc校验原理与fpga实现,通信相关,fpga开发,CRC,循环冗余校验,Verilog文章来源地址https://www.toymoban.com/news/detail-769567.html

到了这里,关于CRC校验原理与FPGA实现(含推导过程)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Verilog】CRC校验码生成器原理及verilog实现

    目录 一、CRC的基本原理  二、CRC生成步骤 2.1举个栗子 三、Verilog实现 四、参考资料 4.1 CRC在线计算器 CRC :Cyclic Redundancy Check循环冗余校验码         将被处理的报文比特序列当做一个二进制多项式A(x)的系数,任意一个由二进制位串组成的代码都可以和一个系数仅为‘0’和

    2024年02月11日
    浏览(36)
  • CRC循环冗余校验 (Cyclic Redundancy Check) 原理/电路实现/Verilog实现

    目录 1 什么是CRC循环冗余校验? 2 CRC校验的原理 2.1 多项式表示 2.2 模二 多项式除法 2.3 传输端  2.4 接收端 3 CRC码的产生 3.1 产生CRC码步骤 3.2 Verilog实现 4 电路实现原理—线性反馈移位寄存器 4.1 循环移位寄存器结构 4.2 最大长度移位寄存器  4.3 多项式除法电路(线性反馈移位

    2024年02月04日
    浏览(40)
  • CRC校验码生成逻辑的实现原理详解——结合C语言和Verilog语言代码分析

    因为前段时间用到CRC校验码,所以在网上找到了很多有关CRC校验码计算原理以及生成CRC校验码的代码实现(包括C语言和Verilog语言的实现)的文章,但关于CRC校验码代码实现的原理未能找到相关文章,于是自己结合C语言和Veirlog语言的实现代码以及CRC校验码的计算原理,对CR

    2023年04月22日
    浏览(100)
  • MODBUS RTU 通信协议 CRC16校验算法

    CRC校验码是一个2个字节(16位二进制)的数。 发送端:发送的数据计算CRC校验码----发送:数据+CRC校验码 接收端:收到数据后重新计算CRC校验码,然后和接收到数据中的CRC校验码进行比较,判断是否相等。 如果不相等:数据传输过程中出错,给出错误应答。 CRC16 校验源码

    2024年02月16日
    浏览(51)
  • CRC32校验算法原理及计算说明

    本文主要介绍如何使用CRC32校验算法计算得出FCS(Frame Check Sequence帧检验序列). 参考: 1、CRC32加密算法原理 2、CRC多项式对应代码 作用:在远距离数据通信中,为确保高效而无差错地传送数据,对数据进行校验即差错控制。 CRC原理实际上就是在一个K位二进制数据序列之后附

    2023年04月22日
    浏览(44)
  • 从原理到代码理解CRC循环冗余校验

    概述:本文详细介绍了CRC循环冗余计算的数学原理,算法中使用的参数说明,并以Modbus协议中的CRC-16算法为例,进行手算验证,同时提供LabVIEW和C语言的直接计算CRC-16 值的代码以及C的查表计算CRC-16代码和代码原理的说明。 初次接触CRC校验是因为项目需要上位机软件来记录P

    2024年02月04日
    浏览(43)
  • CRC校验(2):CRC32查表法详解、代码实现及CRC反转

    对于现在的CPU来说,基本上都在硬件上实现了CRC校验。但我们还是想用软件来实现一下CRC的代码,这样可以更深入地理解里面的原理。所以这一节就来详细地解释如何使用查表法从软件上来实现CRC-32的校验。另外,CRC还有一种反转的情况,实际上反转和不反转没有什么太大的

    2024年02月09日
    浏览(248)
  • FPGA verilog设计的MODBUS CRC算法

    已经测试通过。 `timescale 1ns / 1ps // // Company: // Engineer: // // Create Date: 20:14:12 05/18/2023 // Design Name: // Module Name: Modbus_CRC // Project Name: // Target Devices: // Tool versions: // Description: // // Dependencies: // // Revision: // Revision 0.01 - File Created // Additional Comments: // // module Modbus_CRC( input clk, input rst

    2024年02月06日
    浏览(45)
  • CRC校验码详解+Verilog实现(含代码)

    目录 CRC码简介 CRC校验码生成步骤  CRC码生成多项式  CRC校验码Verilog实现 CRC即循环冗余校验,是一种数字通信中的常用信道编码技术。其特征是信息段和校验字段的长度可以任意选定 CRC码是由2部分组成的,前部分是信息码,后部分是校验码,如果CRC码长共 n bit,信息码长

    2023年04月08日
    浏览(47)
  • CRC16/ModBus校验与C语言实现方法

    目录 一、什么是CRC16/Modbus校验 二、CRC16/Modbus校验的计算步骤 三、CRC16/Modbus校验的C语言实现         CRC16/Modbus校验在设备通信时可以验证数据帧的完整性,从而避免错误数据。其应用十分广泛,主要应用于Modbus通信协议等,类似的校验方法还有许多,例如和校验,本文主

    2024年02月07日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包