书接上文(任意CRC算法实现——单步Verilog),本文介绍多步Verilog模块,即分多个时钟周期,输入多次数据,最终算出CRC。此模块适用于长数据的CRC计算情形。
一. 模块框图与信号说明
信号说明:
分类 | 信号名称 | 输入/输出 | 说明 |
---|---|---|---|
参数 | 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 | 最后一个输入数据指示, 高电平有效 |
注意:
-
多步计算CRC, 用于长数据, 避免单步CRC计算长数据时组合逻辑过长的问题 -
数据位宽DIN_WIDTH必须是8的倍数, 否则在处理输入按字节反转时会出错 -
数据位宽DIN_WIDTH必须≥CRC宽度WIDTH, 否则在处理初始异或时会出错, 对于初始异或值为0的CRC算法无此要求 -
补0已在模块内部完成, 外部输入无需考虑最后的补0 -
必须给出最后一段数据的位宽LAST_DIN_WIDTH, 它必须小于等于DIN_WIDTH -
它适用于长数据无法整数分割为多个DIN_WIDTH段的场合,如果能整数分割, 则LAST_DIN_WIDTH可不指定, 它默认等于DIN_WIDTH -
当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 仿真验证
从上图可以看出:
-
输入 'h1200368F
‘,CRC为01
-
输入 'h78780001
‘,CRC为08
与CRC网页计算结果对比,完全一致,各位同学自行测试,部分结果如下图所示。
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 仿真验证
-
输入 'h1200368F
‘,CRC为83AC
-
输入 'h78780001
‘,CRC为86A6
与CRC网页计算结果对比,完全一致,各位同学自行测试,部分结果如下图所示。
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 仿真验证
-
输入 'h12004578368F
‘,CRC为FDEF9C10
-
输入 'h787800000001
‘,CRC为E816B597
与CRC网页计算结果对比,完全一致,各位同学自行测试,部分结果如下图所示。
五. 源码与工程分享
用于生成Verilog代码的Python脚本和对应的Jupyter Notebook文件开源,两处仓库同步。
Gitee:myCRC: 计算CRC的代码
Github:zhengzhideakang/myCRC: 计算CRC的代码
Vivado仿真工程则通过网盘分享,VeirlogMultiStep Vivado2021.2工程 20250211.7z。
没有回复内容