【问题标题】:CRC-32 algorithm from HDL to software从 HDL 到软件的 CRC-32 算法
【发布时间】:2018-06-22 14:11:30
【问题描述】:

我在 Verilog 中实现了一个伽罗瓦线性反馈移位寄存器(以及在 MATLAB 中,主要是为了模拟 HDL 设计)。它运行良好,据我所知,我使用 MATLAB 计算 CRC-32 字段,然后将它们包含在我的 HDL 模拟中以验证数据包是否正确到达(使用 CRC-32 填充数据),这会产生良好的结果。

问题是我希望能够计算我在软件中实现的 CRC-32,因为我将使用 Raspberry Pi 通过 FPGA 中的 GPIO 输入数据,但我无法这样做。我试过this online calculator,使用相同的参数,但从来没有得到相同的结果。

这是我用来计算 CRC-32 的 MATLAB 代码:

N = 74*16;
data = [round(rand(1,N)) zeros(1,32)];
lfsr = ones(1,32);
next_lfsr = zeros(1,32);

for i = 1:length(data)
    next_lfsr(1) = lfsr(2);
    next_lfsr(2) = lfsr(3);
    next_lfsr(3) = lfsr(4);
    next_lfsr(4) = lfsr(5);
    next_lfsr(5) = lfsr(6);
    next_lfsr(6) = xor(lfsr(7),lfsr(1));
    next_lfsr(7) = lfsr(8);
    next_lfsr(8) = lfsr(9);
    next_lfsr(9) = xor(lfsr(10),lfsr(1));
    next_lfsr(10) = xor(lfsr(11),lfsr(1));
    next_lfsr(11) = lfsr(12);
    next_lfsr(12) = lfsr(13);
    next_lfsr(13) = lfsr(14);
    next_lfsr(14) = lfsr(15);
    next_lfsr(15) = lfsr(16);
    next_lfsr(16) = xor(lfsr(17), lfsr(1));
    next_lfsr(17) = lfsr(18);
    next_lfsr(18) = lfsr(19);
    next_lfsr(19) = lfsr(20);
    next_lfsr(20) = xor(lfsr(21),lfsr(1));
    next_lfsr(21) = xor(lfsr(22),lfsr(1));
    next_lfsr(22) = xor(lfsr(23),lfsr(1));
    next_lfsr(23) = lfsr(24);
    next_lfsr(24) = xor(lfsr(25), lfsr(1));
    next_lfsr(25) = xor(lfsr(26), lfsr(1));
    next_lfsr(26) = lfsr(27);
    next_lfsr(27) = xor(lfsr(28), lfsr(1));
    next_lfsr(28) = xor(lfsr(29), lfsr(1));
    next_lfsr(29) = lfsr(30);
    next_lfsr(30) = xor(lfsr(31), lfsr(1));
    next_lfsr(31) = xor(lfsr(32), lfsr(1));
    next_lfsr(32) = xor(data2(i), lfsr(1));

    lfsr = next_lfsr;
end

crc32 = lfsr;

请看,我首先使用 32 个零填充来计算 CRC-32(LFSR 最后剩下的就是我的 CRC-32,如果我这样做,用这个 CRC-32 替换零,我的LFSR最后也变空了,就是验证通过了)。

我使用的多项式是 CRC-32 的标准:04C11DB7。另请参阅顺序似乎是颠倒的,但这只是因为它被镜像为在 MSB 中具有输入。当输入相同时,使用这种表示和镜像的结果相同,只是结果也会被镜像。

任何想法都会有很大帮助。

提前致谢

【问题讨论】:

    标签: matlab fpga crc hdl crc32


    【解决方案1】:

    您的 CRC 不是 CRC。输入的最后 32 位实际上不参与计算,除了异或到结果中。也就是说,如果你用零替换最后 32 位数据,进行计算,然后用结果“crc32”异或最后 32 位数据,那么你将得到相同的结果。

    所以你永远不会让它匹配另一个 CRC 计算,因为它不是 CRC。

    C 中的这段代码复制了您的函数,其中数据位来自 p 处的一系列 n 字节,最低有效位在前,结果是 32 位值:

    unsigned long notacrc(void const *p, unsigned n) {
        unsigned char const *dat = p;
        unsigned long reg = 0xffffffff;
        while (n) {
            for (unsigned k = 0; k < 8; k++)
                reg = reg & 1 ? (reg >> 1) ^ 0xedb88320 : reg >> 1;
            reg ^= (unsigned long)*dat++ << 24;
            n--;
        }
        return reg;
    }
    

    您可以立即看到数据的最后一个字节与最终的寄存器值进行异或运算。不太明显的是最后 4 个字节只是异或。这个完全等效的版本很明显:

    unsigned long notacrc_xor(void const *p, unsigned n) {
        unsigned char const *dat = p;
        // initial register values
        unsigned long const init[] = {
            0xffffffff, 0x2dfd1072, 0xbe26ed00, 0x00be26ed, 0xdebb20e3};
        unsigned xor = n > 3 ? 4 : n;       // number of bytes merely xor'ed
        unsigned long reg = init[xor];
        while (n > xor) {
            reg ^= *dat++;
            for (unsigned k = 0; k < 8; k++)
                reg = reg & 1 ? (reg >> 1) ^ 0xedb88320 : reg >> 1;
            n--;
        }
        switch (n) {
            case 4:
                reg ^= *dat++;
            case 3:
                reg ^= (unsigned long)*dat++ << 8;
            case 2:
                reg ^= (unsigned long)*dat++ << 16;
            case 1:
                reg ^= (unsigned long)*dat++ << 24;
        }
        return reg;
    }
    

    在那里你可以看到消息的最后四个字节,或者如果它是三个或更少字节的消息的全部,与最后的寄存器值异或。

    实际的 CRC 必须使用所有输入数据位来确定何时对寄存器进行异或多项式。最后一个函数的内部部分是 CRC 实现的样子(尽管更有效的版本使用预先计算的表来一次处理一个或更多字节)。这是一个计算实际 CRC 的函数:

    unsigned long crc32_jam(void const *p, unsigned n) {
        unsigned char const *dat = p;
        unsigned long reg = 0xffffffff;
        while (n) {
            reg ^= *dat++;
            for (unsigned k = 0; k < 8; k++)
                reg = reg & 1 ? (reg >> 1) ^ 0xedb88320 : reg >> 1;
            n--;
        }
        return reg;
    }
    

    那个叫做crc32_jam,因为它实现了一种叫做“JAMCRC”的特殊CRC。该 CRC 与您尝试实施的最接近。

    如果您想使用真正的 CRC,则需要更新您的 Verilog 实现。

    【讨论】:

    • 嘿,马克。首先,我想说非常感谢您花时间写出如此详细的答案。我会尽快仔细查看它,但我只想说我理解我的“消息”的最后 32 位不参与计算本身,因为它们是推动所有数据到 LFSR,我已经看到它总是以这种方式实现的。我知道它甚至没有必要成为一个零向量,并且一些算法在进行计算时使用另一条消息来填充数据,并在接收器中与之进行比较。再次感谢!
    • 虽然 CRC-n 是通过通过移位寄存器运行增强消息(即在末尾附加 n 个零位)来定义的,它们从未以这种方式实现,因为没有必要。您需要为消息中的位数运行移位寄存器,仅此而已。请参阅Ross William's tutorial 的第 10 节。
    • 耶稣,我刚刚测试了它,它有效!这也使传输部分的事情变得更容易。我使用增强消息的主要目标是直接与接收器中的 N 大小的零向量进行比较,我想不使用它只是意味着比较发送和接收计算?顺便说一句,我仍然不清楚我上面的实现是否不是 CRC,正如你所提到的,或者只是没有必要这样做。您认为我的系统无法检测到正确的 CRC-32 等错误吗?再次感谢你,一千次。马克,你帮我省了很多麻烦。
    • 1.不,你可以只找零。例如,如果您在消息之后立即向 JAMCRC 提供消息的 CRC(以 little-endian 顺序),则生成的 CRC 为零。 2. 如果你总是在你的消息之后输入 32 个零位,那么你的实现就是一个 CRC,虽然初始值是0xdebb20e3,而不是你想要的0xffffffff。 3. 如果你总是在消息后面加上 32 个零,那么它将有效地检测错误。如果你不这样做,只是提供消息位,那么它就不会那么有效。