CRC 原理

CRC实例

假设要传输的原始数据为1101011011B,发送方和接收方在通信前约定好的除数为10011B。由于除数10011B是五位数(5bit),那么假设余数(即CRC码)为四位数(4bit)。因为现在余数未知,所以在进行模二除法运算前先将余数设为0000B,即待发送的数据为11010110110000B。下面开始进行模二除法运算来确定余数(即CRC码),更多计算细节,请查询模二除法:
在这里插入图片描述
可见余数(即CRC码)为1110B,因此发送方实际发送的是11010110111110B。

总结规律

将每次的计算结果定位res,使用的多项式称为p,正常的多项式是POLY=10011,异常为0,则:

每次迭代,根据res的首位决定p,若res的首位是1,则p=POLY;若res的首位是0,则p=0, 或者跳过此次迭代,上面的例子中就是碰到0后直接跳到后面的非零位。每次计算出结果后左移一位,即丢弃最高位。

如上图所示,直接的CRC-32校验代码如下:

/******************************************************************************
 * Name:    CRC-32/MPEG-2  x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1
 * Poly:    0x4C11DB7
 * Init:    0xFFFFFFF
 * Refin:   False
 * Refout:  False
 * Xorout:  0x0000000
 * Note:
 *****************************************************************************/
uint32_t crc32_mpeg_2(uint8_t *data, uint16_t length)
{
    uint8_t i;
    uint32_t crc = 0xffffffff;  // Initial value
    while(length--)
    {
        crc ^= (uint32_t)(*data++) << 24;
        {
            if ( crc & 0x80000000 )
                crc = (crc << 1) ^ 0x04C11DB7;
            else
                crc <<= 1;
        }
    }
    return crc;
}

CRC参数模型

Width:CRC码的位宽,CRC32即为32位位宽。
Poly:省略最高位的多项式,因为更利于编程实现,即移位之后不需要考虑最高位;0x4C11DB7实际为0x104C11DB7,为32+1==33位。
Init:CRC码初始值,因为原始数据可能以不同位数的0开头,所以需要设置初始值来区分;
Refin是输入逆向标志位,如果为真,表示输入数据需要先进行逆向(即按位倒序),再进行后续运算;
Refout是最后输出逆向标志位,如果为真,表示CRC码需要先进行逆向(即按位倒序),再输出;
Xorout是最后输出异或值,是输出CRC码前与其进行异或运算的数;
Check是以UTF-8字符串"123456789"(作为8位字符数组)当做输入原始数据计算出的CRC码;
Residue是以无错误码字当做输入原始数据计算出的、不进行输出异或运算的CRC码,其具体含义有待进一步考究;
Name是算法名称。

举例

原始数据:0x34,使用CRC-8/MAXIN参数模型:

POLY = 0x31 = 0011 0001(最高位1已经省略)
INIT = 0x00
XOROUT = 0x00
REFIN = TRUE
REFOUT = TRUE

实际计算过程:

  1. 原始数据 = 0x34 = 0011 0100,多项式 = 0x31 = 1 0011 0001

  2. INIT = 00,原始数据高8位和初始值进行异或运算保持不变。

  3. REFIN为TRUE,需要先对原始数据进行翻转:0011 0100 > 0010 1100

  4. 原始数据左移8位,即后面补8个0:0010 1100 0000 0000

  5. 把处理之后的数据和多项式进行模2除法,求得余数:
    原始数据:0010 1100 0000 0000 = 10 1100 0000 0000
    多项式:1 0011 0001
    模2除法取余数低8位:1111 1011

  6. 因为REFOUT为TRUE,对结果进行翻转得到最终的CRC-8值:1101 1111 = 0xDF

  7. XOROUT进行异或,1111 1011 xor 0000 0000 = 1111 1011

  8. 数据+CRC:0011 0100 1101 1111 = 34DF,相当于原始数据左移8位+余数。
    在这里插入图片描述
    在实际计算的过程中,每次异或的最高位都会丢弃,因此在实际的代码中将poly最高位省略,相应的将crc寄存器左移一位。
    得到如下的计算代码: crc = (crc << 1) ^ 0x04C11DB7

代码优化

对于如下的代码我们举个例子,对0xA3进行crc32_mpeg_2的计算

for (i = 0; i < 8; ++i)
{
    if ( crc & 0x80000000 )
        crc = (crc << 1) ^ 0x04C11DB7;
    else
        crc <<= 1;
}

在这里插入图片描述
从上述4次计算过程可以发现,影响最终结果的只有0xA3中的A与POLY(0x04C11DB7),我们可以将该计算过程进行整合:

(0xA3 << 4)(POLY<<3)(POLY<<1)

因为POLY是固定的,异或运行有结合律,我们可以根据不同的A值,将最终0xA3需要异或的数据X提前计算出来,加速运算的过程。

同样我们也可以一次处理8bit的数据,因此我们可以得到查表法的代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
 
#define POLY 0x04C11DB7
void get_table(uint32_t *table)
{
    uint32_t res;
    for(int i=0;i<256;i++)
    {
        res = (i<<24);
          
        for (int j = 0; j < 8; ++j)
        {
            if((res&(1<<31)) != 0)
            {
                res = (res<<1)^POLY;
            }
            else
            {
                res = (res<<1);
            }
        }
        table[i] = res;
    }
}
  
uint32_t crc32_mpeg_2_table(uint8_t *data,uint32_t len, uint32_t *table)
{    
    uint8_t i;
    uint32_t crc = 0xffffffff;  // Initial value
    uint8_t index;
    while(len--)
    {
        index = (crc>>24)^(*data++)&0xff;
        crc = (crc<<8)^table[index];
    }
    return crc;
}
 
 
int main()
{
    uint8_t data[10] = {0x01,0x02,0x03,0x04};
    uint32_t table[256];
    get_table(table);
    uint32_t ret = crc32_mpeg_2_table(data,4,table);
    printf("res = %x\n",ret);
}    

逆运算

对于一些CRC算法,在实际运算时需要考虑原始数据的逆向与输出结果的逆向,为了实际的代码,考虑将整个运算过程逆向,例如CRC32算法的参数模型为:

  • Name: CRC-32 x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1
  • Poly: 0x4C11DB7
  • Init: 0xFFFFFFFF
  • Refin: True
  • Refout: True
  • Xorout: 0xFFFFFFFF
  • Alias: CRC_32/ADCCP
  • Use: WinRAR,ect.

在实际运算时需要现将原始数据翻转,最后还需要将结果翻转,如图所示:
在这里插入图片描述
那我们在实际计算时,可以将CRC的原始值,POLY均进行翻转,计算时移位的方向也进行翻转,可以得到直接计算公式如下:

/******************************************************************************
 * Name:    CRC-32 x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1
 * Poly:    0x4C11DB7
 * Init:    0xFFFFFFFF
 * Refin:   True
 * Refout:  True
 * Xorout:  0xFFFFFFFF
 * Alias:   CRC_32/ADCCP
 * Use:     WinRAR,ect.
 *****************************************************************************/
uint32_t crc32(uint8_t *data, uint16_t length)
{
    uint8_t i;
    uint32_t crc = 0xffffffff;  // Initial value
    while(length--)
    {
        crc ^= *data++;
        {
            if ( crc & 1 )
                crc = (crc >> 1) ^ 0xEDB88320;
            else
                crc >>= 1;
        }
    }
    return crc^0xFFFFFFFF;   // ~crc
 }

同样可以得到逆向的查表法的代码

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
 
#define POLY 0xEDB88320
void get_table(uint32_t *table)
{
    uint32_t res;
    for(int i=0;i<256;i++)
    {
        res = (i);
          
        for (int j = 0; j < 8; ++j)
        {
            if((res&1) != 0)
            {
                res = (res>>1)^POLY;
            }
            else
            {
                res = (res>>1);
            }
        }
        table[i] = res;
    }
}
  
uint32_t crc32_table(uint8_t *data,uint32_t len, uint32_t *table)
{    
    uint8_t i;
    uint32_t crc = 0xffffffff;  // Initial value
    uint8_t index;
    while(len--)
    {
        index = (crc^(*data++))&0xff;
        crc = (crc>>8)^table[index];
    }
    return ~crc;
}
 
 
int main()
{
    uint8_t data[10] = {0x01,0x02,0x03,0x04};
    uint32_t table[256];
    get_table(table);
    uint32_t ret = crc32_table(data,4,table);
    printf("res = %x\n",ret);
}

硬件加速

ARMV8是支持CRC32的加速的,具体运行如下指令,查看是否包含crc32属性。
cat /proc/cpuinfo

使能硬件加速

typedef  uint32_t u32;
typedef  int64_t  s64;
typedef  uint8_t  u8;
typedef  uint64_t u64;
typedef  uint16_t u16;
 
/*
*
*   ARM hardware accelerated crc32 instruction
*
*/
#define CRC32X(crc, value) __asm__("crc32x %w[c], %w[c], %x[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32W(crc, value) __asm__("crc32w %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32H(crc, value) __asm__("crc32h %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32B(crc, value) __asm__("crc32b %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32CX(crc, value) __asm__("crc32cx %w[c], %w[c], %x[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32CW(crc, value) __asm__("crc32cw %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32CH(crc, value) __asm__("crc32ch %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
#define CRC32CB(crc, value) __asm__("crc32cb %w[c], %w[c], %w[v]":[c]"+r"(crc):[v]"r"(value))
 
static u32 crc32_arm64_le_hw(const u8 *p, unsigned int len)
{
    uint32_t crc = 0xffffffff;
    s64 length = len;
 
    while ((length -= sizeof(u64)) >= 0) {
        CRC32X(crc, *((uint64_t *)p));
        p += sizeof(u64);
    }
 
    /* The following is more efficient than the straight loop */
    if (length & sizeof(u32)) {
        CRC32W(crc, *((uint32_t *)p));
        p += sizeof(u32);
    }
    if (length & sizeof(u16)) {
        CRC32H(crc, *((uint16_t *)p));
        p += sizeof(u16);
    }
    if (length & sizeof(u8))
        CRC32B(crc, *p);
 
    return ~crc;
}
 
static u32 crc32c_arm64_le_hw(u32 crc, const u8 *p, unsigned int len)
{
    s64 length = len;
 
    while ((length -= sizeof(u64)) >= 0) {
        CRC32CX(crc, *((uint64_t *)p));
        p += sizeof(u64);
    }
 
    /* The following is more efficient than the straight loop */
    if (length & sizeof(u32)) {
        CRC32CW(crc, *((uint32_t *)p));
        p += sizeof(u32);
    }
    if (length & sizeof(u16)) {
        CRC32CH(crc, *((uint16_t *)p));
        p += sizeof(u16);
    }
    if (length & sizeof(u8))
        CRC32CB(crc, *p);
 
    return ~crc;
}

说明:

x 代表 double :__crc32cd: 一次完成8个Byte的CRC32C计算,对应crc32cx指令。

w 代表 word :__crc32cw:一次完成4个Byte的CRC32C计算,对应crc32cw指令。

h 代表 half: __crc32ch:一次完成2个Byte的CRC32C计算,对应crc32ch指令。

b 代表 byte:__crc32cb: 一次完成1个Byte的CRC32C计算,对应crc32cb指令。

Poly 取值不一样
CRC32 : 0x04C11DB7; its reversed form 0xEDB88320
CRC32C :0x1EDC6F41; its reversed form 0x82F63B78

编译指令:

aarch64-linux-gnu-gcc -O2 -mcpu=generic+crc crc32_all.c -o crc32_all

or

aarch64-linux-gnu-gcc -O2 -march=armv8-a+crc crc32_all.c -o crc32_all

参考文献

https://blog.csdn.net/u012028275/article/details/112342542
https://blog.csdn.net/qq_25814297/article/details/109402108
https://blog.csdn.net/weixin_44256803/article/details/105805628
https://www.cnblogs.com/mic-chen/p/14944988.html
https://www.zangcq.com/2020/05/17/arm-crc32-%E6%8C%87%E4%BB%A4%E5%8A%A0%E9%80%9F/
常用的crc参数模型对应代码:crc-lib-c-master.zip
包含硬件加速的crc32的代码:crc32_all.c

  • 23
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FPGA(现场可编程逻辑门阵列)是一种可编程的硬件设备,可以根据用户的需求进行灵活的配置和实现。CRC(循环冗余校验)是一种错误检测技术,常用于数据传输中。 FPGA实现CRC原理的基本过程包括以下几个步骤: 1. 首先,需要定义CRC的生成多项式。生成多项式是决定校验位长度和性能的关键因素。常用的生成多项式有CRC-8、CRC-16和CRC-32等。 2. 接下来,需要将生成多项式转化为二进制形式,并计算出对应的多项式系数。这些系数将会在后续的计算中被使用。 3. 然后,在FPGA中创建一个数据位宽与生成多项式位数相等的寄存器。该寄存器将用于存储待校验的数据。 4. 将数据与生成多项式进行异或运算,并将结果暂存在一个寄存器中。然后,将该结果右移一位,再次进行异或运算。重复这个过程,直到将所有的数据位都处理完毕。 5. 循环冗余校验码即为最后的寄存器内容。将该校验码与数据一起传输,接收方可通过相同的生成多项式和计算过程进行校验,并判断数据是否出现错误。 通过使用FPGA实现CRC校验,可以高效地处理数据,并能够在传输过程中快速检测出数据是否出现错误。FPGA的灵活性使得校验位长度和生成多项式能够根据需要进行改变,提高了系统的可定制性和应用范围。同时,FPGA的并行计算能力也使得CRC计算速度更快,能够满足高速数据传输的需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值