续写《verilog中parameter的计算》
本文分析了Verilog代码中参数计算存在的问题及改进方案。原代码在计算Fcw时存在32位整数溢出风险,导致计算结果错误。AI建议使用64位强制运算,并优化代码可读性。实测验证发现改进后功能正常,但DDS计时存在约±1个时钟周期的抖动。文章提供了完整的改进代码,包含64位参数计算、32位累加器和计时检测逻辑,通过Modelsim仿真和上板测试验证了解决方案的有效性。
·
接着前文verilog中parameter的计算,用AI来分析一下。
与AI的对话:
提问:以下代码有什么问题吗?
localparam fclk = 10000000;
//单位ms
localparam t = 80;
localparam B_DDS = 32;
localparam [63:0]Fcw = (2**B_DDS)*1000/(fclk*t);
reg over = 0;
reg [31:0] cnt = 0;
always@(posedge CLK)begin
{over,cnt} <= cnt+Fcw[31:0];
end
AI回答:这段 Verilog 代码在逻辑设计和语法上存在几个比较隐蔽但致命的问题,特别是关于数据溢出和硬件实现可行性。
以下是详细的分析与修正建议:
- 核心计算溢出问题
这是最严重的问题。在计算 Fcw 时,你使用了 ( 2 ∗ ∗ B _ D D S ) ∗ 1000 (2**B\_DDS)*1000 (2∗∗B_DDS)∗1000。
计算过程: 2 32 2^{32} 232 已经是 4,294,967,296,再乘以 1000,结果大约是 4.29 × 10 12 4.29 \times 10^{12} 4.29×1012。
Verilog 限制: 在 Verilog 中,整数字面量默认通常是 32 位。即使你赋值给一个 [63:0] 的位宽,中间计算过程如果没指定位宽,就会在 32 位溢出。
结果: ( 2 ∗ ∗ 32 ) ∗ 1000 (2**32) * 1000 (2∗∗32)∗1000 在 32 位运算下直接变成 0。这会导致你的 Fcw 最终计算结果为 0,累加器永远不会工作。 - 位宽匹配与赋值
在 always 块中,你使用了:{over,cnt} <= cnt + Fcw[31:0];
虽然这在语法上能通过,但将一个 64 位的参数 Fcw 截断为 32 位赋给累加器,配合上述的计算溢出,逻辑上基本是失效的。 - 本地参数计算的精度
Verilog 的 localparam 在进行除法时是取整运算。
f c l k = 10 7 fclk = 10^7 fclk=107
t = 80 t = 80 t=80
f c l k × t = 10 7 × 80 = 800 , 000 , 000 fclk \times t = 10^7 \times 80 = 800,000,000 fclk×t=107×80=800,000,000
如果分子计算正确(处理了 64 位),结果如果不是整数,小数部分会丢失,导致计时不准。
老三点评:
第一点说 ( 2 ∗ ∗ B _ D D S ) ∗ 1000 (2**B\_DDS)*1000 (2∗∗B_DDS)∗1000溢出了,计算结果是0,但是我用modelsim仿真,显示Fcw的值为5368。
第三点说计时不准,下面验证。
AI接着建议了修改方案:
如果你是想实现一个在 80ms 内完成 32 位累加器翻转的逻辑,建议按以下方式修改:
// 明确指定 64 位常数,防止中间过程溢出
localparam [63:0] FCLK_HZ = 10_000_000;
localparam [63:0] T_MS = 80;
localparam [63:0] B_DDS = 32;
// 重新调整计算顺序,先乘后除,并强制 64 位运算
// 公式:(2^32 * 1000) / (fclk * t)
localparam [63:0] Fcw = (64'd1 << B_DDS) * 64'd1000 / (FCLK_HZ * T_MS);
reg [31:0] cnt = 0;
reg over = 0;
always @(posedge CLK) begin
// 使用 33 位运算来捕获进位
{over, cnt} <= cnt + Fcw[31:0];
end
改进点说明:
- 强制位宽: 使用 64’d1 << B_DDS 和 64’d1000 强制编译器进入 64 位运算模式。
- 可读性: 使用 _ 分隔数字(如 10_000_000)提高代码可读性。
老三点评:这段程序经过上板验证,功能正常。
dds产生的计时确实有抖动,通过上板测试发现计时时钟个数 c n t 1 cnt1 cnt1在800104和800105之间跳动:让AI写个testbench:
仿真结果:
最后附上完整代码:
`timescale 1ns / 1ps
module dds#(
parameter WIDTH_CNT = 32
)(
input clk,
output [WIDTH_CNT-1:0]cnt_time,
output pulse
);
// 明确指定 64 位常数,防止中间过程溢出
localparam [63:0] FCLK_HZ = 10_000_000;
localparam [63:0] T_MS = 80;
localparam [63:0] B_DDS = 32;
// 重新调整计算顺序,先乘后除,并强制 64 位运算
// 公式:(2^32 * 1000) / (fclk * t)
localparam [63:0] Fcw = (64'd1 << B_DDS) * 64'd1000 / (FCLK_HZ * T_MS);
reg [31:0] cnt = 0;
reg over = 0;
always @(posedge clk) begin
// 使用 33 位运算来捕获进位
{over, cnt} <= cnt + Fcw[31:0];
end
assign pulse = over;
wire rst_cnt;
wire add_cnt;
wire end_cnt;
//用cnt1检测dds的抖动
reg [31:0] cnt1 = 0;
always@(posedge clk)begin
if(rst_cnt)
cnt1 <= 0;
else if(add_cnt)
if(end_cnt)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1;
end
assign rst_cnt = 0;
assign add_cnt = 1;
assign end_cnt = add_cnt && (over);
assign cnt_time = cnt1;
endmodule
更多推荐








所有评论(0)