前言
用Verilog算CRC已经有了两个比较不错的网站,可以在线生成Verilog代码,如:
这两个网站能够生成任意CRC算法的Verilog单步计算模块,单步的概念是只输入一次数据就算出CRC,这适用于数据长度较短的的情形。
因为CRC算法的主要部分是异或运算,如果单步计算时数据太长则组合逻辑路径就会很长,易造成时序问题,所以对于长数据的CRC运算,推进使用多步算法,也就是分多个时钟周期算出CRC,基本等价于在长的异或逻辑中插入多级寄存器。关于多步CRC的Verilog实现,参考本系列的下一篇博客。
CRC算法的主体是异或运算,但仍然包括输入反转,输出反转,输出异或等操作,上述网站生成的代码没有将这些操作包含进去,所以,在应用时还需要在模块外部进行一些额外的处理。
我更喜欢傻瓜式的模块,将实现细节都封装在模块内部,外部调用就会非常便捷,因此我自编了实现任意CRC算法的Verilog单步计算模块,将所有关于CRC的计算步骤均封装为模块内部,并通过parameter来更改不同参数,以适应所有的CRC算法。
对于CRC计算主体——异或运算,我编写了Python程序,只需输入不同的CRC配置参数,即可生成可直接复制到Verilog模块中的异或运算代码。
至此,本博文实现了适用于任意CRC算法的单步Verilog模块,最后仿真验证了CRC计算的正确性。
本博文提供的模块和Python代码的易用性已超越上述两个网站
,你不再需要去关注上述网站生成的代码怎么用,只需要仔细看懂本博文,你就能实现CRC自由!
一. 数学原理
参考:CRC算法原理与实现05——并行计算公式推导
通用CRC算法公式:
二. 任意CRC的Verilog代码——单步
输入数据,单步计算出CRC,因为CRC计算的主要逻辑是纯组合逻辑的异或操作,所以,单步CRC适用于短数据的情况,太长的数据从避免组合逻辑太长的角度考虑需要多步计算。单步CRC部分代码如下:
2.1 模块端口定义
module myCrcOneStep
#(
parameter DIN_WIDTH = 4, // 输入数据位宽, 取值范围为4的倍数: 如4, 8, 12, ...
parameter WIDTH = 16, // CRC宽度, 取值范围4~64
parameter [0:0] REFLECT_IN = 1, // 输入是否翻转, 取值范围0或1, 1表示反转, 0表示不反转
parameter [WIDTH-1 : 0] XOR_IN = 'hFFFF, // 输入异或值, 取值范围全1或全0
parameter [0:0] REFLECT_OUT = 1, // 输出是否翻转, 取值范围0或1, 1表示反转, 0表示不反转
parameter [WIDTH-1 : 0] XOR_OUT = 'hFFFF // 输出异或值, 取值范围全1或全0
)(
output wire [WIDTH-1 : 0] crc_out, // CRC输出
output reg crc_out_valid, // CRC输出是否有效, 高电平有效
input wire [DIN_WIDTH-1 : 0] din, // 输入数据
input wire din_valid, // 输入数据有效, 高电平有效
input wire clk,
input wire rstn
);
2.2 输入寄存与补0
//++ 输入寄存与补齐到整数字节 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// DIN_WIDTH向大取最接近的8的倍数, 如1~8取8, 9~16取16, 17~24取24, ...
localparam DIN_WIDTH_TO_8 = ((DIN_WIDTH + 7) / 8) * 8;
reg [DIN_WIDTH_TO_8-1 : 0] din_r;
always @(posedge clk) begin
if (~rstn)
din_r <= 'd0;
else if (din_valid)
din_r <= {{(DIN_WIDTH % 8){1'b0}}, din};
end
//-- 输入寄存与补齐到整数字节 ------------------------------------------------------------
2.3 输入按字节反转
//++ 输入按字节反转 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
wire [DIN_WIDTH_TO_8-1:0] din_reflected;
generate
if (REFLECT_IN == 1) begin
// 对每个字节进行位反转
genvar i;
for (i = 0; i < DIN_WIDTH_TO_8 / 8; i = i + 1) begin
assign din_reflected[i*8+7 : i*8] = { din_r[i*8], din_r[i*8+1], din_r[i*8+2], din_r[i*8+3],
din_r[i*8+4], din_r[i*8+5],din_r[i*8+6], din_r[i*8+7]
};
end
end else begin
assign din_reflected = din_r;
end
endgenerate
//-- 输入按字节反转 ------------------------------------------------------------
2.4 输入低位补0后高位与初始值异或
//++ 输入低位补0后高位与初始值异或 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
wire [DIN_WIDTH_TO_8+WIDTH-1 : 0] din_xor;
assign din_xor = {din_reflected, {(WIDTH){1'b0}}} ^ {XOR_IN, {(DIN_WIDTH_TO_8){1'b0}}};
//-- 输入低位补0后高位与初始值异或 ------------------------------------------------------------
2.5 模2运算
这部分是CRC计算的核心
,对于不同的CRC算法,这部分代码会不一样,本文提供的Python程序
可根据输入的CRC配置与输入数据位宽生成这段代码,无需手动编写。
//++ 模2计算 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
wire [WIDTH-1 : 0] crc_calc;
// 此部分代码由Python程序生成 请勿手动修改 begin
/*
单步计算CRC
CRC宽度: 5
CRC多项式: 0x05
输入数据位宽: 12
*/
assign crc_calc[0] = din_xor[5] ^ din_xor[8] ^ din_xor[10] ^ din_xor[11] ^ din_xor[14]
^ din_xor[15] ^ din_xor[16] ^ din_xor[17] ^ din_xor[18];
assign crc_calc[1] = din_xor[6] ^ din_xor[9] ^ din_xor[11] ^ din_xor[12] ^ din_xor[15]
^ din_xor[16] ^ din_xor[17] ^ din_xor[18] ^ din_xor[19];
assign crc_calc[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] ^ din_xor[19] ^ din_xor[20];
assign crc_calc[3] = din_xor[6] ^ din_xor[8] ^ din_xor[9] ^ din_xor[12] ^ din_xor[13]
^ din_xor[14] ^ din_xor[15] ^ din_xor[16] ^ din_xor[20];
assign crc_calc[4] = din_xor[7] ^ din_xor[9] ^ din_xor[10] ^ din_xor[13] ^ din_xor[14]
^ din_xor[15] ^ din_xor[16] ^ din_xor[17];
// 此部分代码由Python程序生成 请勿手动修改 end
//-- 模2计算 ------------------------------------------------------------
2.6 输出反转
//++ 输出反转 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
wire [WIDTH-1 : 0] crc_reflected;
generate
if (REFLECT_OUT == 1) begin // 输出是否翻转
genvar i;
for (i = 0; i < WIDTH; i = i + 1) begin : reverse
assign crc_reflected[i] = crc_calc[WIDTH-1-i];
end
end else begin
assign crc_reflected = crc_calc;
end
endgenerate
//-- 输出反转 ------------------------------------------------------------
2.7 输出异或
//++ 输出异或 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
assign crc_out = crc_reflected ^ XOR_OUT; // 输出结果与XOR_OUT异或
always @(posedge clk) begin
if (~rstn)
crc_out_valid <= 1'b0;
else if (din_valid)
crc_out_valid <= 1'b1;
else
crc_out_valid <= 1'b0;
end
//-- 输出异或 ------------------------------------------------------------
三. 用Python程序生成单步CRC模2运算的Verilog代码
核心代码是根据一. 数学原理
中的并行计算公式,生成具体的单步计算公式,如下所示。
3.1 单步CRC计算公式
def _crc_one_step_formula(self, din_width: int) -> np.ndarray:
# Verilog CRC计算单步并行公式
N = self.N
# 判断din_width是否大于0
if din_width == 0:
raise ValueError("din_width must be greater than 0")
# 生成数据行向量, 输入位宽变为8的倍数, 然后加上N
total_width = ((din_width + 7) // 8) * 8 + N
d_symbols = symbols(' '.join([f'd{i:02d}' for i in range(total_width)]))
D = np.array([list(d_symbols[::-1])])
# 取前total_width-d_min个元素
d_min = min(din_width, N)
D = D[:, :(total_width - d_min)]
# 后补d_min个0
D = np.concatenate((D, np.zeros((1, d_min), int)), axis=1)
# 生成状态转移矩阵
T = self.T
# 进行并行CRC计算
verilog_one_step = 0
j, i = divmod(total_width, 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_one_step += 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_one_step[0, :i] += term_last[0, :]
return verilog_one_step
3.2 根据公式生成Verilog代码
最后代码会自动复制到剪切板,直接粘贴即可。
def generate_verilog_crc_one_step_code(self, din_width) -> str:
"""
将输入行向量转换为Verilog crc_calc赋值语句
:param din_width: 输入数据宽度
:return: 生成的Verilog代码字符串
"""
verilog_crc_one_step_list = []
verilog_crc_one_step_list.append('// 此部分代码由Python程序生成 请勿手动修改 begin')
verilog_crc_one_step_list.append('/*')
verilog_crc_one_step_list.append('单步计算CRC')
verilog_crc_one_step_list.append('CRC宽度: ' + str(self.N))
verilog_crc_one_step_list.append('CRC多项式: '
+ f'0x{self.crc_config.poly:0{(self.N+3)//4}x}')
verilog_crc_one_step_list.append('输入数据位宽: ' + str(din_width))
verilog_crc_one_step_list.append('*/')
# 单步CRC计算公式
row_vector = self._crc_one_step_formula(din_width)
crc_list = [str(item) for item in row_vector[0]][::-1]
for i in range(len(crc_list)):
item = crc_list[i].replace('+', '^')
item = re.sub(r'\bd(\d{2})\b', lambda m: f"din_xor[{int(m.group(1))}]", item)
new_item = f'assign crc_calc[{i}] = ' + item + ';'
# 对过长的行进行分割
split_items = self._split_long_line(new_item)
verilog_crc_one_step_list.extend(split_items)
verilog_crc_one_step_list.append('// 此部分代码由Python程序生成 请勿手动修改 end')
verilog_crc_one_step_code = '\n'.join(verilog_crc_one_step_list)
# 代码复制到剪贴板, 便于直接粘贴
pyperclip.copy(verilog_crc_one_step_code)
return verilog_crc_one_step_code
四. 多种CRC算法的仿真验证
测试环境:Vivado 2021.2
4.1 CRC-5-USB
-
多项式: 0x05
-
初始异或值: 0x1F
-
输入反转:是 -
输出反转:是 -
最终异或值: 0x1F
4.1.1 生成Verilog代码
import myCRC
# 生成单步CRC的Verilog代码
crc_custom = myCRC.CRCConfig(
# 只需要这两个参数
width = 5,
poly = 0x1F,
)
# 直接用以下代码效果一样
# crc_custom = myCRC.CRC_5_USB # 可选CRC_8_MAXIM, CRC_16_CCITT等myCRC类中定义好的CRC配置
din_width = 12
verilog_code = myCRC.CRCVerilog(crc_custom).generate_verilog_crc_one_step_code(din_width)
print(verilog_code)
运行结果:
// 此部分代码由Python程序生成 请勿手动修改 begin
/*
单步计算CRC
CRC宽度: 5
CRC多项式: 0x1f
输入数据位宽: 12
*/
assign crc_calc[0] = din_xor[5] ^ din_xor[6] ^ din_xor[11] ^ din_xor[12] ^ din_xor[17]
^ din_xor[18];
assign crc_calc[1] = din_xor[5] ^ din_xor[7] ^ din_xor[11] ^ din_xor[13] ^ din_xor[17]
^ din_xor[19];
assign crc_calc[2] = din_xor[5] ^ din_xor[8] ^ din_xor[11] ^ din_xor[14] ^ din_xor[17]
^ din_xor[20];
assign crc_calc[3] = din_xor[5] ^ din_xor[9] ^ din_xor[11] ^ din_xor[15] ^ din_xor[17];
assign crc_calc[4] = din_xor[5] ^ din_xor[10] ^ din_xor[11] ^ din_xor[16] ^ din_xor[17];
// 此部分代码由Python程序生成 请勿手动修改 end
4.1.2 Testbench(部分)
//++ 实例化待测模块 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
localparam DIN_WIDTH = 12; // 4的倍数
localparam WIDTH = 5;
localparam REFLECT_IN = 1;
localparam XOR_IN = 5'h1F;
localparam REFLECT_OUT = 1;
localparam XOR_OUT = 5'h1F;
logic [WIDTH-1 : 0] crc_out;
logic crc_out_valid;
logic [DIN_WIDTH-1 : 0] din;
logic din_valid;
logic clk;
logic rstn;
myCrcOneStep #(
.DIN_WIDTH (DIN_WIDTH ),
.WIDTH (WIDTH ),
.REFLECT_IN (REFLECT_IN ),
.XOR_IN (XOR_IN ),
.REFLECT_OUT (REFLECT_OUT),
.XOR_OUT (XOR_OUT )
) myCrcOneStep_inst (.*);
//-- 实例化待测模块 ------------------------------------------------------------
initial begin
rstn = 0;
#(CLKT * 10)
rstn = 1;
din = 'h234;
din_valid = 1;
#(CLKT*1)
din = 'h300;
#(CLKT*1)
din = 'h101;
#(CLKT*1)
din = 'h007;
#(CLKT*1)
din_valid = 0;
#(CLKT * 10) $stop;
end
4.1.3 仿真验证
从上图可以看出:
-
输入 'h234
‘,CRC为01
-
输入 'h300
‘,CRC为05
-
输入 'h101
‘,CRC为07
-
输入 'h007
‘,CRC为0c
与CRC网页计算结果对比,完全一致,各位同学自行测试,部分结果如下图所示。
4.2 CRC-16-MAXIN
-
多项式: 0x8005
-
初始异或值: 0x0000
-
输入反转:是 -
输出反转:是 -
最终异或值: 0xFFFF
4.2.1 生成Verilog代码
import myCRC
# 生成单步CRC的Verilog代码
crc_custom = myCRC.CRCConfig(
# 只需要这两个参数
width = 16,
poly = 0x8005,
)
din_width = 12
verilog_code = myCRC.CRCVerilog(crc_custom).generate_verilog_crc_one_step_code(din_width)
print(verilog_code)
运行结果:
// 此部分代码由Python程序生成 请勿手动修改 begin
/*
单步计算CRC
CRC宽度: 16
CRC多项式: 0x8005
输入数据位宽: 12
*/
assign crc_calc[0] = din_xor[16] ^ din_xor[17] ^ din_xor[18] ^ din_xor[19] ^ din_xor[20]
^ din_xor[21] ^ din_xor[22] ^ din_xor[23] ^ din_xor[24] ^ din_xor[25]
^ din_xor[26] ^ din_xor[27] ^ din_xor[28] ^ din_xor[29] ^ din_xor[31];
assign crc_calc[1] = din_xor[17] ^ din_xor[18] ^ din_xor[19] ^ din_xor[20] ^ din_xor[21]
^ din_xor[22] ^ din_xor[23] ^ din_xor[24] ^ din_xor[25] ^ din_xor[26]
^ din_xor[27] ^ din_xor[28] ^ din_xor[29] ^ din_xor[30];
assign crc_calc[2] = din_xor[16] ^ din_xor[17] ^ din_xor[30];
assign crc_calc[3] = din_xor[17] ^ din_xor[18] ^ din_xor[31];
assign crc_calc[4] = din_xor[18] ^ din_xor[19];
assign crc_calc[5] = din_xor[19] ^ din_xor[20];
assign crc_calc[6] = din_xor[20] ^ din_xor[21];
assign crc_calc[7] = din_xor[21] ^ din_xor[22];
assign crc_calc[8] = din_xor[22] ^ din_xor[23];
assign crc_calc[9] = din_xor[23] ^ din_xor[24];
assign crc_calc[10] = din_xor[24] ^ din_xor[25];
assign crc_calc[11] = din_xor[25] ^ din_xor[26];
assign crc_calc[12] = din_xor[12] ^ din_xor[26] ^ din_xor[27];
assign crc_calc[13] = din_xor[13] ^ din_xor[27] ^ din_xor[28];
assign crc_calc[14] = din_xor[14] ^ din_xor[28] ^ din_xor[29];
assign crc_calc[15] = din_xor[15] ^ din_xor[16] ^ din_xor[17] ^ din_xor[18] ^ din_xor[19]
^ din_xor[20] ^ din_xor[21] ^ din_xor[22] ^ din_xor[23] ^ din_xor[24]
^ din_xor[25] ^ din_xor[26] ^ din_xor[27] ^ din_xor[28] ^ din_xor[30]
^ din_xor[31];
// 此部分代码由Python程序生成 请勿手动修改 end
4.2.2 Testbench(部分)
localparam DIN_WIDTH = 12; // 4的倍数
localparam WIDTH = 16;
localparam REFLECT_IN = 1;
localparam XOR_IN = 16'h0000;
localparam REFLECT_OUT = 1;
localparam XOR_OUT = 16'hFFFF;
4.2.3 仿真验证
-
输入 'h234
‘,CRC为42FF
-
输入 'h300
‘,CRC为0FFF
-
输入 'h101
‘,CRC为AF3F
-
输入 'h007
‘,CRC为3DBE
与CRC网页计算结果对比,完全一致,各位同学自行测试,部分结果如下图所示。
4.3 CRC-32
-
多项式: 0x8005
-
初始异或值: 0x0000
-
输入反转:是 -
输出反转:是 -
最终异或值: 0xFFFF
4.3.1 生成Verilog代码
import myCRC
# 生成单步CRC的Verilog代码
crc_custom = myCRC.CRCConfig(
# 只需要这两个参数
width = 32,
poly = 0x04C11DB7,
)
din_width = 36
verilog_code = myCRC.CRCVerilog(crc_custom).generate_verilog_crc_one_step_code(din_width)
print(verilog_code)
运行结果:
略,太长了
4.3.2 Testbench(部分)
localparam DIN_WIDTH = 36; // 4的倍数
localparam WIDTH = 32;
localparam REFLECT_IN = 1;
localparam XOR_IN = 32'hFFFFFFFF;
localparam REFLECT_OUT = 1;
localparam XOR_OUT = 32'hFFFFFFFF;
...
initial begin
rstn = 0;
#(CLKT * 2.6)
rstn = 1;
din = 'h8_2340_1230;
din_valid = 1;
#(CLKT*1)
din = 'h5_AAAA_BCBC;
#(CLKT*1)
din = 'h3_8740_1000;
#(CLKT*1)
din = 'h0_0000_1441;
#(CLKT*1)
din_valid = 0;
#(CLKT * 5) $stop;
end
4.3.3 仿真验证
-
输入 'h823401230
‘,CRC为6A6271B3
-
输入 'h5AAAABCBC
‘,CRC为725C47A9
-
输入 'h387401000
‘,CRC为CB549CDE
-
输入 'h000001441
‘,CRC为E957514E
与CRC网页计算结果对比,完全一致,各位同学自行测试,部分结果如下图所示。
五. 源码与工程分享
用于生成Verilog代码的Python脚本和对应的Jupyter Notebook文件开源,两处仓库同步。
Gitee:myCRC: 计算CRC的代码
Github:zhengzhideakang/myCRC: 计算CRC的代码
Vivado仿真工程则通过网盘分享,VerilogOneStep Vivado2021.2工程 20250210.7z。
没有回复内容