如何在FPGA里解CRC检验

如何在FPGA里解CRC检验,第1张

循环冗余校验码(CRC)的基本原理是:在K位信息码后再拼接R位的校验码,整个编码长度为N位,因此,这种编码也叫(N,K)码。对于一个给定的(N,K)码,可以证明存在一个最高次幂为N-K=R的多项式G(x)。根据G(x)可以生成(N-K)位信息的校验码,而G(x)叫做这个CRC码的生成多项式。 校验码的具体生成过程为:假设要发送的信息用多项式C(X)表示,将C(x)左移R位(可表示成C(x)*2R),这样C(x)的右边就会空出R位,这就是校验码的位置。用 C(x)*2R 除以生成多项式G(x)得到的余数就是校验码。 (难点是如何进行除法运算)

任意一个由二进制位串组成的代码都可以和一个系数仅为‘0’和‘1’取值的多项式一一对应。例如:代码1010111对应的多项式为x6+x4+x2+x+1,而多项式为x5+x3+x2+x+1对应的代码101111。

如果是低速设计上游数据可能是串行的 显然如果采用并行实现有些浪费硬件资源 因为还要把串行输入存储起来形成整字节的并行输入

CRC的“定义”不是唯一的 只要接收发送端采用的一致就行

不知道你看了什么资料 但是估计不是很好 不如直接看维基百科里的介绍

http://zh.wikipedia.org/wiki/%E5%BE%AA%E7%8E%AF%E5%86%97%E4%BD%99%E6%A0%A1%E9%AA%8C

“初始值”、“反射值”以及“最终异或值”

对于一些复杂的校验和来说这些十六进制数值是很重要的,如 CRC-32 以及 CRC-64。通常小于 CRC-16 的 CRC 不需要使用这些值。通常可以通过改变这些值来得到各自不同的校验和,但是校验和算法机制并没有变化

CRC 标准化问题

相同长度的CRC也可能有多种形式,据称 CRC-16 与 CRC-32 至少有 10 种形式,但没有一种在数学上是最优的

移位寄存器可以初始化成 1 而不是 0。同样,在用算法处理之前,消息的最初n个数据位要取反。这是因为未经修改的 CRC 无法区分只有起始 0 的个数不同的两条消息。而经过这样的取反过程,CRC 就可以正确地分辨这些消息了

首先,利用传统的软件技巧来优化算法,然后将其转向定制指令以加速算法。我们将讨论不同实现方法的性能比较和折衷。

CRC算法可用来校验数据在传输过程中是否被破坏。这些算法很流行,因为它们具有很高的检错率,而且不会对数据吞吐量造成太大影响,因为CRC校验位被添加进数据信息中。但是,CRC算法比一些简单的校验和算法有更大的计算量要求。尽管如此,检错率的提高使得这种算法值得去实施。

一般说来,发送端对要被发送的消息执行CRC算法,并将CRC结果添加进该消息中。消息的接收端对包括CRC结果在内的消息执行同样的CRC *** 作。如果接收端的结果与发送端的不同,这说明数据被破坏了。

CRC算法是一种密集的数学运算,涉及到二元模数除法(modulo-2 division),即数据消息被16或32位多项式(取决于所用CRC标准)除所得的余数。这种 *** 作一般通过异或和移位的迭代过程来实现,当采用16位多项式时,这相当于每数据字节要执行数百条指令。如果发送数百个字节,计算量就会高达数万条指令。因此,任何优化都会大幅提高吞吐量。

代码列表1中的CRC函数有两个自变量(消息指针和消息中的字节数),它可返回所计算的CRC值(余数)。尽管该函数的自变量是一些字节,但计算要逐位来执行。该算法并不高效,因为所有 *** 作(与、移位、异或和循环控制)都必须逐位地执行。

列表1:逐位执行的CRC算法C代码。

/*

* The width of the CRC calculation and result.

* Modify the typedef for a 16 or 32-bit CRC standard.

*/

typedef unsigned char crc

#define WIDTH (8 * sizeof(crc))

#define TOPBIT (1 <<(WIDTH - 1))

crc crcSlow(unsigned char const message[], int nBytes)

{

crc remainder = 0

/*

* Perform modulo-2 division, a byte at a time.

*/

for (int byte = 0byte <nBytes++byte)

{

/*

* Bring the next byte into the remainder.

*/

remainder ^= (message[byte] <<(WIDTH - 8))

/*

* Perform modulo-2 division, a bit at a time.

*/

for (unsigned char bit = 8bit >0"bit)

{

/*

* Try to divide the current data bit.

*/

if (remainder &TOPBIT)

{

remainder = (remainder <<1) ^ POLYNOMIAL

}

else

{

remainder = (remainder <<1)

}

}

}

/*

* The final remainder is the CRC result.

*/

return (remainder)

}

1.传统的软件优化

图3:带CRC外围电路和DMA的系统模块示意图。

让我们看一下如何利用传统的软件技巧来优化CRC算法。因为CRC *** 作中的一个 *** 作数,即多项式(除数)是常数,字节宽CRC *** 作的所有可能结果都可以预先计算并存储在一个查找表中。这样,通过一个读查找表动作就可让 *** 作按逐个字节执行下去。

采用这一算法时,需要将这些预先计算好的值存储在存储器中。选择ROM或RAM都可以,只要在启动CRC计算之前将存储器初始化就行。查找表有256个字节,表中每个字节位置包含一个CRC结果,共有256种可能的8位消息(与多项式大小无关)。

列表2示出了采用查找表方法的C代码,包括生成查找表crcInit()中数值的代码。

列表2:采用查找表方法的CRC算法C代码。

crc crcTable[256]

void crcInit(void)

{

crc remainder

/*

* Compute the remainder of each possible dividend.

*/

for (int dividend = 0dividend <256++dividend)

{

/*

* Start with the dividend followed by zeros.

*/

remainder = dividend <<(WIDTH - 8)

/*

* Perform modulo-2 division, a bit at a time.

*/

for (unsigned char bit = 8bit >0"bit)

{

/*

* Try to divide the current data bit.

*/

if (remainder &TOPBIT)

{

remainder = (remainder <<1) ^ POLYNOMIAL

}

else

{

remainder = (remainder <<1)

}

}

/*

* Store the result into the table.

*/

crcTable[dividend] = remainder

}

} /* crcInit() */

crc crcFast(unsigned char const message[], int nBytes)

{

unsigned char data

crc remainder = 0

/*

* Divide the message by the polynomial, a byte at a time.

*/

for (int byte = 0byte <nBytes++byte)

{

data = message[byte] ^ (remainder >>(WIDTH - 8))

remainder = crcTable[data] ^ (remainder <<8)

}

/*

* The final remainder is the CRC.

*/

return (remainder)

} /* crcFast() */

整个计算减少为一个循环,每字节(不是每位)有两个异或、两个移位 *** 作和两个装载指令。基本上,这里是用查找表的存储空间来换取速度。该方法比逐位计算的方法要快9.9倍,这一提高对某些应用已经足够。如果需要更高的性能,可以尝试编写汇编代码或增加查找表容量以挤出更多性能来。但是,如果需要20、50甚至500倍的性能提高,就要考虑采用硬件加速来实现该算法了。

表1:各种规模的数据模块下CRC算法测试比较结果。

2.采用定制指令方法

CRC算法由连续的异或和移位 *** 作构成,用很少的逻辑即可在硬件中简单实现。由于这一硬件模块仅需几个周期来计算CRC,采用定制指令来实现CRC计算要比采用外围电路更好。此外,无须涉及系统中任何其它外围电路或存储器。仅需要一个微处理器来支持定制指令即可,一般是指可配置微处理器。

当在硬件中实现时,算法应该每次执行16或32位计算,这取决于所采用的CRC标准。如果采用CRC-CCITT标准(16位多项式),最好每次执行16位计算。如果使用8位微处理器,效率可能不太高,因为装载 *** 作数值及返回CRC值需要额外的周期。图2示出了用硬件实现16位CRC算法的内核。

信号msg(15..0)每次被移入异或/移位硬件一位。列表3示出了在64KB数据模块上计算CRC的一些C代码例子。该实例是针对Nios嵌入式处理器。

列表3:采用定制指令的CRC计算C代码。

unsigned short crcCompute(unsigned short *data_block, unsigned int nWords)

{

unsigned short* pointer

unsigned short word

/*

* initialize crc reg to 0xFFFF

*/

word = nm_crc (0xFFFF, 1)/* nm_crc() is the CRC custom instruction */

/*

* calculate CRC on block of data

* nm_crc() is the CRC custom instruction

*

*/

for (pointer = data_blockpointer <(data_block + nWords)pointer ++)

word = nm_crc(*pointer, 0) return (word)

}

int main(void)

{

#define data_block_begin (na_onchip_memory)

#define data_block_end (na_onchip_memory + 0xffff)

unsigned short crc_result

unsigned int data_block_length = (unsigned short *)data_block_end - (unsigned short

*)data_block_begin + 1

crc_result = crcCompute((unsigned short *)data_block_begin, data_block_length)

}

采用定制指令时,用于计算CRC值的代码是一个函数调用,或宏。当针对Nios处理器实现定制指令时,系统构建工具会生成一个宏。在本例中为nm_crc(),可用它来调用定制指令。

在启动CRC计算之前,定制指令内的CRC寄存器需要先初始化。装载初始值是CRC标准的一部分,而且每种CRC标准都不一样。接着,循环将为数据模块中的每16位数据调用一次CRC定制指令。这种定制指令实现方式要比逐位实现的方法快27倍。

3.CRC外围电路方法

如果将CRC算法作为硬件外围电路来实现,并利用DMA将数据从存储器转移到外围电路,这样还可以进一步提高速度。这种方法将省去处理器为每次计算而装载数据所需要的额外周期。DMA可在此外围电路完成前一次CRC计算的时钟周期内提供新的数据。图3示出了利用DMA、CRC外围电路来实现加速的系统模块示意图。

在64KB数据模块上,利用带DMA的定制外围电路可获得比逐位计算的纯软件算法快500倍的性能。要知道,随着数据模块规模的增加,使用DMA所获得的性能也随之提高。这是因为设置DMA仅需很少的开销,设置之后DMA运行得特别快,因为每个周期它都可以传递数据。因此,若只有少数字节的数据,用DMA并不划算。

这里所讨论的所有采用CRC-CCITT标准(16位多项式)的算法都是在Altera Stratix FPGA的Nios处理器上实现的。表1示出了各种数据长度的测试比较结果,以及大致的硬件使用情况(FPGA中的存储器或逻辑单元)。

可以看出,算法所用的硬件越多,算法速度越快。这是用硬件资源来换取速度。


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/yw/12061500.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-20
下一篇2023-05-20

发表评论

登录后才能评论

评论列表(0条)

    保存