数据填充对CRC计算的影响

Impact of data padding on CRC calculation

我在硬件的每个周期(每个周期 64B)计算大量数据的 CRC。为了并行计算CRC,我想对小数据块计算CRC,然后并行异或。

方法:

  1. 我们把数据分成小块(64B数据分成8块 每个 8B)。
  2. 然后我们计算所有块的CRC 单独(8B 块并行 8 个 CRC)。
  3. 最后计算 填充数据的 CRC。 This answer 指出 CRC 填充数据是通过将旧的 CRC 乘以 x^n 获得的。

因此,我正在为一小块数据计算 CRC,然后将其与移位 'i' 次的 0x1 CRC 相乘,如下所示。

简而言之,我正在努力完成以下工作:

例如:CRC-8 on this site:

Input Data=(0x05 0x07) CRC=0x54
Step-1: Data=0x5 CRC=0x1B
Step-2: Data=0x7 CRC=0x15
Step-3: Data=(0x1 0x0) CRC=0x15
Step-4: Multiply step-1 CRC and step-3 CRC with primitive polynomial 0x7. So, I calculate (0x1B).(0x15) = (0x1 0xC7) mod 0x7.
Step-5: Calculate CRC Data=(0x1 0xC7) CRC=0x4E (I assume this is same as (0x1 0xC7) mod 0x7)
Step-6: XOR the result to get the final CRC. 0x4E^0x15=0x5B

正如我们所见,第 6 步的结果不是正确的结果。

有人可以帮我计算填充数据的CRC吗?或者我在上面的例子中哪里出错了?

如你的图所示,需要计算0x05 0x00,(A,0)的CRC,和0x00 0x07,(0,B)的CRC,然后互斥-或那些在一起。在您链接的网站上计算,您分别得到 0x410x15。异或那些一起,瞧,你得到 0x540x05 0x07 的 CRC。

(0,B) 有一个捷径,因为对于这个 CRC,一串零的 CRC 为零。您可以计算 0x07 的 CRC,并得到与 0x00 0x07 相同的结果,即 0x15.

请参阅 crcany 了解如何组合 CRC。 crcany 将生成 C 代码来计算任何指定的 CRC,包括组合 CRC 的代码。它采用一种技术,将 n 零应用于 O(log(n)) 时间而不是 O(n )时间。

不是计算然后调整多个 CRC,而是可以将数据字节无进位相乘以形成一组 16 位“折叠”乘积,然后对其进行异或运算,并对异或运算执行单个模运算“折叠”产品。优化的模运算使用两个无进位倍数,因此在生成所有折叠产品并将其异或在一起之前可以避免这种情况。无进位乘法使用 XOR 而不是 ADD,无借位除法使用 XOR 而不是 SUB。 Intel 有一个关于这个的 pdf 文件,使用 XMM 指令 PCLMULQDQ(无进位乘法),其中一次读取 16 个字节,分成两个 8 字节的组,每个组折叠成一个 16 字节的产品,两个 16 字节的产品是异或形成一个 16 字节的产品。使用8个XMM寄存器存放折叠产品,一次处理128个字节。 (对于 AVX512 和 ZMM 寄存器,每次 256 个字节)。

https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf

假设您的硬件可以实现无进位乘法,它采用两个 8 位操作数并产生 16 位(技术上是 15 位)乘积。

设message = M = 31 32 33 34 35 36 37 38。在这种情况下CRC(M) = C7

pre-calculated constants (all values shown in hex):

2^38%107 = DF    cycles forwards 0x38 bits
2^30%107 = 29    cycles forwards 0x30 bits
2^28%107 = 62    cycles forwards 0x28 bits
2^20%107 = 16    cycles forwards 0x20 bits
2^18%107 = 6B    cycles forwards 0x18 bits
2^10%107 = 15    cycles forwards 0x10 bits
2^08%107 = 07    cycles forwards 0x08 bits
2^00%107 = 01    cycles forwards 0x00 bits

16 bit folded (cycled forward) products (can be calculated in parallel):    
31·DF = 16CF
32·29 = 07E2
33·62 = 0AC6
34·16 = 03F8
35·6B = 0A17
36·15 = 038E
37·07 = 0085
38·01 = 0038
        ----
V =     1137    the xor of the 8 folded products
CRC(V) = 113700 % 107 = C7

为避免模运算必须使用无借位除法,可以使用无进位乘法计算 CRC(V)。例如

V = FFFE
CRC(V) = FFFE00 % 107 = 23.

实现,同样是十六进制的所有值(十六进制 10 = 十进制 16),⊕ 是 XOR。

input:
V = FFFE
constants:
P = 107                  polynomial
I = 2^10 / 107 = 107     "inverse" of polynomial
                         by coincidence, it's the same value
2^10 % 107 = 15          for folding right 16 bits
fold the upper 8 bits of FFFE00 16 bits to the right:
U = FF·15 ⊕ FE00 = 0CF3 ⊕ FE00 = F2F3  (check: F2F3%107 = 23 = CRC)
Q = ((U>>8)·I)>>8 = (F2·107)>>8 = ...
to avoid a 9 bit operand, split up 107 = 100 ⊕ 7 
Q = ((F2·100) ⊕ (F2·07))>>8 = ((F2<<8) ⊕ (F2·07))>>8 = (F200 ⊕ 02DE)>>8 = F0DE>>8 = F0
X = Q·P = F0·107 = F0·100 ⊕ F0·07 = F0<<8 ⊕ F0·07 = F000 ⊕ 02D0 = F2D0
CRC = U ⊕ X = F2F3 ⊕ F2D0 = 23

由于CRC是8位,所以最后两步不需要高8位,但对整体计算帮助不大。

X = (Q·(P&FF))&FF = (F0·07)&FF = D0
CRC = (U&FF) ⊕ X = F3 ⊕ D0 = 23

生成 2^0x10 / 0x107 和 2 % 0x107 的幂的示例程序:

#include <stdio.h>

typedef unsigned char  uint8_t;
typedef unsigned short uint16_t;

#define poly 0x107

uint16_t geninv(void)           /* generate 2^16 / 9 bit poly */
{
uint16_t q = 0x0000u;           /* quotient */
uint16_t d = 0x0001u;           /* initial dividend = 2^0 */
    for(int i = 0; i < 16; i++){
        d <<= 1;
        q <<= 1;
        if(d&0x0100){           /* if bit 8 set */
            q |= 1;             /*   q |= 1 */
            d ^= poly;          /*   d ^= poly */
        }
    }
    return q;                   /* return inverse */
}

uint8_t powmodpoly(int n)       /* generate 2^n % 9 bit poly */
{
uint16_t d = 0x0001u;           /* initial dividend = 2^0 */
    for(int i = 0; i < n; i++){
        d <<= 1;                /* shift dvnd left */
        if(d&0x0100){           /* if bit 8 set */
            d ^= poly;          /*   d ^= poly */
        }
    }
    return (uint8_t)d;          /* return remainder */
}

int main()
{
    printf("%04x\n", geninv());
    printf("%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
           powmodpoly(0x00), powmodpoly(0x08), powmodpoly(0x10), powmodpoly(0x18),
           powmodpoly(0x20), powmodpoly(0x28), powmodpoly(0x30), powmodpoly(0x38),
           powmodpoly(0x40), powmodpoly(0x48));
    printf("%02x\n", powmodpoly(0x77));  /* 0xd9, cycles crc backwards 8 bits */
    return 0;
}

2^0x10 / 0x107 的长手示例。

                            100000111    quotient
                  -------------------
divisor 100000111 | 10000000000000000    dividend
                    100000111
                    ---------
                          111000000
                          100000111
                          ---------
                           110001110
                           100000111
                           ---------
                            100010010
                            100000111
                            ---------
                                10101    remainder

我不知道您的硬件设计中可以有多少个寄存器,但假设有五个 16 位寄存器用于保存折叠值,以及两个或八个 8 位寄存器(取决于折叠的并行程度已经完成了)。然后按照 Intel 论文,折叠所有 64 个字节的值,一次折叠 8 个字节,并且只需要一个模运算。寄存器大小,fold# = 16 位,reg# = 8 位。请注意,2 模多边形的幂是预先计算的常数。

foldv = prior buffer's folding value, equivalent to folded msg[-2 -1]
reg0 = foldv>>8
reg1 = foldv&0xFF
foldv  = reg0·((2^0x18)%poly)    advance by 3 bytes
foldv ^= reg1·((2^0x10)%poly)    advance by 2 bytes
fold0 = msg[0 1] ^ foldv         handling 2 bytes at a time
fold1 = msg[2 3]
fold2 = msg[4 5]
fold3 = msg[6 7]
for(i = 8; i < 56; i += 8){
    reg0  = fold0>>8
    reg1  = fold0&ff
    fold0  = reg0·((2^0x48)%poly)    advance by 9 bytes
    fold0 ^= reg1·((2^0x40)%poly)    advance by 8 bytes
    fold0 ^= msg[i+0 i+1]
    reg2  = fold1>>8                 if not parallel, reg0
    reg3  = fold1&ff                  and reg1
    fold1  = reg2·((2^0x48)%poly)    advance by 9 bytes
    fold1 ^= reg3·((2^0x40)%poly)    advance by 8 bytes
    fold1 ^= msg[i+2 i+3]
    ...
    fold3 ^= msg[i+6 i+7]
}
reg0  = fold0>>8
reg1  = fold0&ff
fold0  = reg0·((2^0x38)%poly)    advance by 7 bytes
fold0 ^= reg1·((2^0x30)%poly)    advance by 6 bytes
reg2  = fold1>>8                 if not parallel, reg0
reg3  = fold1&ff                  and reg1
fold1  = reg2·((2^0x28)%poly)    advance by 5 bytes
fold1 ^= reg3·((2^0x20)%poly)    advance by 4 bytes
fold2 ...                        advance by 3 2 bytes
fold3 ...                        advance by 1 0 bytes
foldv = fold0^fold1^fold2^fold3

假设最终缓冲区有 5 个字节:

foldv = prior folding value, equivalent to folded msg[-2 -1]
reg0 = foldv>>8
reg1 = foldv&0xFF
foldv  = reg0·((2^0x30)%poly)    advance by 6 bytes
foldv ^= reg1·((2^0x28)%poly)    advance by 5 bytes
fold0 = msg[0 1] ^ foldv
reg0  = fold0>>8
reg1  = fold0&ff
fold0  = reg0·((2^0x20)%poly)    advance by 4 bytes
fold0 ^= reg1·((2^0x18)%poly)    advance by 3 bytes
fold1 = msg[2 3]
reg2  = fold1>>8
reg3  = fold1&ff
fold1  = reg0·((2^0x10)%poly)    advance by 2 bytes
fold1 ^= reg1·((2^0x08)%poly)    advance by 1 bytes
fold2 = msg[4]                   just one byte loaded
fold3 = 0
foldv = fold0^fold1^fold2^fold3
now use the method above to calculate CRC(foldv)