CRC算法原理与实现08——Verilog多步计算任意CRC-Anlogic-安路社区-FPGA CPLD-ChipDebug

CRC算法原理与实现08——Verilog多步计算任意CRC

图片[1]-CRC算法原理与实现08——Verilog多步计算任意CRC-Anlogic-安路社区-FPGA CPLD-ChipDebug

书接上文(任意CRC算法实现——单步Verilog),本文介绍多步Verilog模块,即分多个时钟周期,输入多次数据,最终算出CRC。此模块适用于长数据的CRC计算情形。

一. 模块框图与信号说明

图片[2]-CRC算法原理与实现08——Verilog多步计算任意CRC-Anlogic-安路社区-FPGA CPLD-ChipDebug

20250218182628299-image

信号说明:

分类 信号名称 输入/输出 说明
参数 DIN_WIDTH 输入数据位宽, 取值范围为8的倍数: 如8, 16, 32, …
  LAST_DIN_WIDTH 最后一段的有效数据位宽,其值为 8 的倍数,且最小值为 8,最大值为 DIN_WIDTH,高位为有效数据
  WIDTH CRC宽度, 取值范围4~64
  REFLECT_IN 输入是否翻转, 取值范围0或1, 1表示反转, 0表示不反转
  XOR_IN 输入异或值, 取值范围全1或全0
  REFLECT_OUT 输出是否翻转, 取值范围0或1, 1表示反转, 0表示不反转
  XOR_OUT 输出异或值, 取值范围全1或全0
crc输出 crc_out output 位宽[WIDTH-1 : 0],CRC校验值
  crc_out_valid output CRC输出是否有效指示, 高电平有效
输入数据 din input 位宽[DIN_WIDTH-1 : 0],输入数据
  din_valid input 输入数据有效指示, 高电平有效
  din_first input 第一个输入数据指示, 高电平有效
  din_last input 最后一个输入数据指示, 高电平有效

注意:

  1. 多步计算CRC, 用于长数据, 避免单步CRC计算长数据时组合逻辑过长的问题
  2. 数据位宽DIN_WIDTH必须是8的倍数, 否则在处理输入按字节反转时会出错
  3. 数据位宽DIN_WIDTH必须≥CRC宽度WIDTH, 否则在处理初始异或时会出错, 对于初始异或值为0的CRC算法无此要求
  4. 补0已在模块内部完成, 外部输入无需考虑最后的补0
  5. 必须给出最后一段数据的位宽LAST_DIN_WIDTH, 它必须小于等于DIN_WIDTH
  6. 它适用于长数据无法整数分割为多个DIN_WIDTH段的场合,如果能整数分割, 则LAST_DIN_WIDTH可不指定, 它默认等于DIN_WIDTH
  7. 当DIN_WIDTH大于等于WIDTH时, 此模块也可以单步算出CRC, 即LAST_DIN_WIDTH=DIN_WIDTH,din_valid, din_first, din_last同时为1, 且两个valid之间至少间隔一个clk周期

二. 任意CRC的Verilog代码——多步

其它部分与单步Verilog基本一致,此处省略,主要差别在模2计算部分,有一个类似反馈的机制,即上一步的计算结果作为下一步的计算输入。

对于不同的CRC算法,这部分代码会不一样,本文提供的Python程序可根据输入的CRC配置与输入数据位宽生成这段代码,无需手动编写。

//++ 模2计算 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
wire [WIDTH-1 : 0] crc_calc;
wire [WIDTH-1 : 0] crc_calc_last;
reg  [WIDTH-1 : 0] crc;
always @(posedge clk) begin
  if (~rstn)
    crc <= 'd0;
  else if (din_first)
    crc <= 'd0;
  else if (din_valid_r2)
    crc <= crc_calc;
end

// 此部分代码由Python程序生成 请勿手动修改 begin
/*
CRC宽度: 5
CRC多项式: 0x05
输入数据位宽: 16
最后一段数据位宽: 16
*/
assign crc_calc[0] = crc[0] ^ crc[1] ^ crc[2] ^ din_xor[5] ^ din_xor[8] ^ din_xor[10]
                    ^ din_xor[11] ^ din_xor[14] ^ din_xor[15];
assign crc_calc[1] = crc[0] ^ crc[1] ^ crc[2] ^ crc[3] ^ din_xor[1] ^ din_xor[6] ^ din_xor[9]
                    ^ din_xor[11] ^ din_xor[12] ^ din_xor[15];
assign crc_calc[2] = crc[3] ^ crc[4] ^ din_xor[2] ^ din_xor[5] ^ din_xor[7] ^ din_xor[8]
                    ^ din_xor[11] ^ din_xor[12] ^ din_xor[13] ^ din_xor[14] ^ din_xor[15];
assign crc_calc[3] = crc[0] ^ crc[4] ^ din_xor[3] ^ din_xor[6] ^ din_xor[8] ^ din_xor[9]
                    ^ din_xor[12] ^ din_xor[13] ^ din_xor[14] ^ din_xor[15];
assign crc_calc[4] = crc[0] ^ crc[1] ^ din_xor[0] ^ din_xor[4] ^ din_xor[7] ^ din_xor[9]
                    ^ din_xor[10] ^ din_xor[13] ^ din_xor[14] ^ din_xor[15];
// 最后一段数据的计算代码, 已考虑了补CRC宽度个0
assign crc_calc_last[0] = crc[1] ^ crc[2] ^ crc[4] ^ din_xor[0] ^ din_xor[3] ^ din_xor[5]
                    ^ din_xor[6] ^ din_xor[9] ^ din_xor[10] ^ din_xor[11] ^ din_xor[12]
                    ^ din_xor[13];
assign crc_calc_last[1] = crc[2] ^ crc[3] ^ din_xor[1] ^ din_xor[4] ^ din_xor[6] ^ din_xor[7]
                    ^ din_xor[10] ^ din_xor[11] ^ din_xor[12] ^ din_xor[13] ^ din_xor[14];
assign crc_calc_last[2] = crc[1] ^ crc[2] ^ crc[3] ^ din_xor[0] ^ din_xor[2] ^ din_xor[3]
                    ^ din_xor[6] ^ din_xor[7] ^ din_xor[8] ^ din_xor[9] ^ din_xor[10]
                    ^ din_xor[14] ^ din_xor[15];
assign crc_calc_last[3] = crc[0] ^ crc[2] ^ crc[3] ^ crc[4] ^ din_xor[1] ^ din_xor[3] ^ din_xor[4]
                    ^ din_xor[7] ^ din_xor[8] ^ din_xor[9] ^ din_xor[10] ^ din_xor[11]
                    ^ din_xor[15];
assign crc_calc_last[4] = crc[0] ^ crc[1] ^ crc[3] ^ crc[4] ^ din_xor[2] ^ din_xor[4] ^ din_xor[5]
                    ^ din_xor[8] ^ din_xor[9] ^ din_xor[10] ^ din_xor[11] ^ din_xor[12];
// 此部分代码由Python程序生成 请勿手动修改 end
//-- 模2计算 ------------------------------------------------------------

三. 用Python程序生成多步CRC模2运算的Verilog代码

核心代码是生成最后一步的计算公式,最后一步需要补0,如下所示。

def _crc_multi_step_formula_last(self, din_width: int, last_din_width: int) -> np.ndarray:
        # Verilog CRC计算多步并行公式
        N = self.N
        # 判断last_din_width是否大于0, 并且小于等于N
        if last_din_width == 0:
            raise ValueError("last_din_width must be greater than 0")
        if last_din_width > din_width:
            raise ValueError("last_din_width can't be greater than din_width")
        # 生成状态行向量
        c_symbols = symbols(' '.join([f'c{i:02d}' for i in range(N)]))
        C = np.array([list(c_symbols[::-1])])
        # 生成数据行向量
        d_symbols = symbols(' '.join([f'd{i:02d}' for i in range(din_width)]))
        D = np.array([list(d_symbols[::-1])])
        # 取前last_din_width个数据
        D = D[:, :last_din_width]
        # 将D后补N个0
        D = np.concatenate((D, np.zeros((1, N), int)), axis=1)
        # 生成状态转移矩阵
        T = self.T
        # 进行并行CRC计算
        verilog_multi_step_last = 0
        # 第一项
        verilog_multi_step_last += np.dot(C,  np.linalg.matrix_power(T,  last_din_width+N) % 2)
        # 中间项
        j, i = divmod(last_din_width+N, N)
        for k in range(j):
            start_idx = k * N
            end_idx = (k + 1) * N
            sub_D = D[:, start_idx:end_idx]
            exp = N * (j - 1 - k) + i
            T_exp = np.linalg.matrix_power(T,  exp) % 2
            term = np.dot(sub_D,  T_exp)
            verilog_multi_step_last += term
        # 处理余数项
        if i > 0:
            sub_D_last = D[:, j * N: j * N + i]
            eye_matrix = np.eye(i,  dtype=int)
            term_last = np.dot(sub_D_last, eye_matrix)
            verilog_multi_step_last[0, :i] += term_last[0, :]
        return verilog_multi_step_last

四. 多种CRC算法的仿真验证

测试环境:Vivado 2021.2

4.1 CRC-5-USB

  • 多项式:0x05
  • 初始异或值:0x1F
  • 输入反转:是
  • 输出反转:是
  • 最终异或值:0x1F

4.1.1 生成Verilog代码

import myCRC

# myCRC示例用法
crc_custom = myCRC.CRCConfig(
    # 只需要这两个参数
    width       = 5    ,
    poly        = 0x05,
)

din_width = 16
last_din_width = 16
verilog_code = myCRC.CRCVerilog(crc_custom).generate_verilog_crc_multi_step_code(din_width, last_din_width)
print(verilog_code)

运行结果:

// 此部分代码由Python程序生成 请勿手动修改 begin
/*
多步计算CRC
CRC宽度: 5
CRC多项式: 0x05
输入数据位宽: 16
最后一段数据位宽: 16
*/
assign crc_calc[0] = crc[0] ^ crc[1] ^ crc[2] ^ din_xor[5] ^ din_xor[8] ^ din_xor[10] 
                    ^ din_xor[11] ^ din_xor[14] ^ din_xor[15];
assign crc_calc[1] = crc[0] ^ crc[1] ^ crc[2] ^ crc[3] ^ din_xor[1] ^ din_xor[6] ^ din_xor[9] 
                    ^ din_xor[11] ^ din_xor[12] ^ din_xor[15];
assign crc_calc[2] = crc[3] ^ crc[4] ^ din_xor[2] ^ din_xor[5] ^ din_xor[7] ^ din_xor[8] 
                    ^ din_xor[11] ^ din_xor[12] ^ din_xor[13] ^ din_xor[14] ^ din_xor[15];
assign crc_calc[3] = crc[0] ^ crc[4] ^ din_xor[3] ^ din_xor[6] ^ din_xor[8] ^ din_xor[9] 
                    ^ din_xor[12] ^ din_xor[13] ^ din_xor[14] ^ din_xor[15];
assign crc_calc[4] = crc[0] ^ crc[1] ^ din_xor[0] ^ din_xor[4] ^ din_xor[7] ^ din_xor[9] 
                    ^ din_xor[10] ^ din_xor[13] ^ din_xor[14] ^ din_xor[15];
// 最后一段数据的计算代码, 已考虑了补CRC宽度个0
assign crc_calc_last[0] = crc[1] ^ crc[2] ^ crc[4] ^ din_xor[0] ^ din_xor[3] ^ din_xor[5] 
                    ^ din_xor[6] ^ din_xor[9] ^ din_xor[10] ^ din_xor[11] ^ din_xor[12] 
                    ^ din_xor[13];
assign crc_calc_last[1] = crc[2] ^ crc[3] ^ din_xor[1] ^ din_xor[4] ^ din_xor[6] ^ din_xor[7] 
                    ^ din_xor[10] ^ din_xor[11] ^ din_xor[12] ^ din_xor[13] ^ din_xor[14];
assign crc_calc_last[2] = crc[1] ^ crc[2] ^ crc[3] ^ din_xor[0] ^ din_xor[2] ^ din_xor[3] 
                    ^ din_xor[6] ^ din_xor[7] ^ din_xor[8] ^ din_xor[9] ^ din_xor[10] 
                    ^ din_xor[14] ^ din_xor[15];
assign crc_calc_last[3] = crc[0] ^ crc[2] ^ crc[3] ^ crc[4] ^ din_xor[1] ^ din_xor[3] ^ din_xor[4] 
                    ^ din_xor[7] ^ din_xor[8] ^ din_xor[9] ^ din_xor[10] ^ din_xor[11] 
                    ^ din_xor[15];
assign crc_calc_last[4] = crc[0] ^ crc[1] ^ crc[3] ^ crc[4] ^ din_xor[2] ^ din_xor[4] ^ din_xor[5] 
                    ^ din_xor[8] ^ din_xor[9] ^ din_xor[10] ^ din_xor[11] ^ din_xor[12];
// 此部分代码由Python程序生成 请勿手动修改 end

4.1.2 Testbench(部分)

localparam DIN_WIDTH = 16; // 8的倍数
localparam LAST_DIN_WIDTH = 16;
localparam WIDTH = 5;
localparam REFLECT_IN = 1;
localparam XOR_IN = 5'h1F;
localparam REFLECT_OUT = 1;
localparam XOR_OUT = 5'h1f;
...
initial begin
  rstn = 0;
  #(CLKT * 10.6)
  rstn = 1;
  din = 'h1200;
  din_valid = 1;
  din_first = 1;
  din_last  = 0;
  #(CLKT*1)
  din = 'h368F;
  din_valid = 1;
  din_first = 0;
  din_last  = 1;
  #(CLKT*1)
  din = 'h7878;
  din_valid = 1;
  din_first = 1;
  din_last  = 0;
  #(CLKT*1)
  din = 'h0001;
  din_valid = 1;
  din_first = 0;
  din_last  = 1;
  #(CLKT*1)
  #(CLKT * 10) $stop;
end

4.1.3 仿真验证

20250218182315515-image

图片[5]-CRC算法原理与实现08——Verilog多步计算任意CRC-Anlogic-安路社区-FPGA CPLD-ChipDebug从上图可以看出:

  1. 输入'h1200368F‘,CRC为01
  2. 输入'h78780001‘,CRC为08

与CRC网页计算结果对比,完全一致,各位同学自行测试,部分结果如下图所示。

20250218182332141-image

图片[7]-CRC算法原理与实现08——Verilog多步计算任意CRC-Anlogic-安路社区-FPGA CPLD-ChipDebug

4.2 CRC-16-MAXIN

  • 多项式:0x8005
  • 初始异或值:0x0000
  • 输入反转:是
  • 输出反转:是
  • 最终异或值:0xFFFF

4.2.1 生成Verilog代码

import myCRC

# myCRC示例用法
crc_custom = myCRC.CRCConfig(
    # 只需要这两个参数
    width       = 16    ,
    poly        = 0x8005,
)

din_width = 16
last_din_width = 16
verilog_code = myCRC.CRCVerilog(crc_custom).generate_verilog_crc_multi_step_code(din_width, last_din_width)
print(verilog_code)

运行结果:略。

4.2.2 Testbench(部分)

localparam DIN_WIDTH = 16; // 8的倍数
localparam LAST_DIN_WIDTH = 16;
localparam WIDTH = 16;
localparam REFLECT_IN = 1;
localparam XOR_IN = 16'h0000;
localparam REFLECT_OUT = 1;
localparam XOR_OUT = 16'hFFFF;

4.2.3 仿真验证

20250218182426200-image

图片[9]-CRC算法原理与实现08——Verilog多步计算任意CRC-Anlogic-安路社区-FPGA CPLD-ChipDebug

  1. 输入'h1200368F‘,CRC为83AC
  2. 输入'h78780001‘,CRC为86A6

与CRC网页计算结果对比,完全一致,各位同学自行测试,部分结果如下图所示。

20250218182440976-image

图片[11]-CRC算法原理与实现08——Verilog多步计算任意CRC-Anlogic-安路社区-FPGA CPLD-ChipDebug

4.3 CRC-32

  • 多项式:0x04C11DB7
  • 初始异或值:0xFFFFFFFF
  • 输入反转:是
  • 输出反转:是
  • 最终异或值:0FFFFFFFF

4.3.1 生成Verilog代码

import myCRC

# myCRC示例用法
crc_custom = myCRC.CRCConfig(
    # 只需要这两个参数
    width       = 32    ,
    poly        = 0x04C11DB7,
)

din_width = 32
last_din_width = 16
verilog_code = myCRC.CRCVerilog(crc_custom).generate_verilog_crc_multi_step_code(din_width, last_din_width)
print(verilog_code)

运行结果:略。

4.3.2 Testbench(部分)

localparam DIN_WIDTH = 32; // 8的倍数
localparam LAST_DIN_WIDTH = 16;
localparam WIDTH = 32;
localparam REFLECT_IN = 1;
localparam XOR_IN = 32'hFFFF_FFFF;
localparam REFLECT_OUT = 1;
localparam XOR_OUT = 32'hFFFF_FFFF;
...
initial begin
  rstn = 0;
  #(CLKT * 10.6)
  rstn = 1;
  din = 'h12004578;
  din_valid = 1;
  din_first = 1;
  din_last  = 0;
  #(CLKT*1)
  din = 'h368F0002;
  din_valid = 1;
  din_first = 0;
  din_last  = 1;
  #(CLKT*1)
  din = 'h78780000;
  din_valid = 1;
  din_first = 1;
  din_last  = 0;
  #(CLKT*1)
  din = 'h00010032;
  din_valid = 1;
  din_first = 0;
  din_last  = 1;
  #(CLKT*1)
  din_valid = 0;
  din_first = 0;
  din_last  = 0;
  #(CLKT * 10) $stop;
end

4.3.3 仿真验证

20250218182543516-image

图片[13]-CRC算法原理与实现08——Verilog多步计算任意CRC-Anlogic-安路社区-FPGA CPLD-ChipDebug

  1. 输入'h12004578368F‘,CRC为FDEF9C10
  2. 输入'h787800000001‘,CRC为E816B597

与CRC网页计算结果对比,完全一致,各位同学自行测试,部分结果如下图所示。

20250218182553833-image

图片[15]-CRC算法原理与实现08——Verilog多步计算任意CRC-Anlogic-安路社区-FPGA CPLD-ChipDebug

五. 源码与工程分享

用于生成Verilog代码的Python脚本和对应的Jupyter Notebook文件开源,两处仓库同步。

Gitee:myCRC: 计算CRC的代码

Github:zhengzhideakang/myCRC: 计算CRC的代码

Vivado仿真工程则通过网盘分享,VeirlogMultiStep Vivado2021.2工程 20250211.7z。

 
 
 
请登录后发表评论

    没有回复内容