Verilator ASSIGNDLY 警告:Ignoring timing control on this assignment 排查与修复
更新时间:2026-10-29。FAQ 速查页,属于 EDA 报错排查系列。
%Warning-ASSIGNDLY: Ignoring timing control on this assignment/primitive是 Verilator 对#赋值延迟的处理声明:它不支持延迟,选择直接忽略。initial 块里的延迟由另一条告警管,见 Verilator INITIALDLY 警告;时序控制的整体行为差异见 Verilator 时序延迟支持边界。
首屏答案:ASSIGNDLY 的意思是「这条赋值语句带了 # 延迟,Verilator 在 --no-timing 模式下把它扔了」。两种形态:assign #5 y = a & b;(连续赋值延迟)和 q <= #10 ...(过程赋值延迟)。危险不在告警本身,而在「忽略」——事件驱动仿真器里这个延迟真实生效,Verilator 里却当不存在,同一份代码两个仿真器跑出不同波形。修法分两类:组合逻辑里的延迟大多是 testbench 遗留物,直接删掉;时序逻辑里想表达的「晚一拍」要改成显式寄存器打拍。
本文要回答的问题
- ASSIGNDLY 指认的
#延迟有哪两种写法,各自长什么样? - Verilator「忽略延迟」后,仿真结果和 iverilog 这类事件驱动仿真器差在哪?
- 什么场景下会把这个写法从老代码搬进可综合 RTL?
- 修复时哪些延迟能直接删、哪些必须改结构?
一、报错原文与现象
最小复现把两种形态放进同一个模块——连续赋值的惯性延迟加 always 块里的过程赋值延迟:
module assigndly_bad (
input wire clk,
input wire a,
input wire b,
output wire y,
output reg q
);
assign #5 y = a & b; // 形态一:连续赋值带 # 延迟
always @(posedge clk) begin
q <= #10 (a | b); // 形态二:过程赋值带 # 延迟
end
endmodule对这个文件跑 verilator --lint-only -Wall --no-timing,真实告警如下:
读这段输出抓三点。第一,每条告警的行号精确钉在 # 符号上(10:12 和 14:14),文案 Ignoring timing control on this assignment/primitive due to --no-timing 明说处理方式是忽略。第二,提示行给出消音开关 lint_off ASSIGNDLY,第五节讲为什么不能用它当修复。第三,末尾 %Error: Exiting due to 2 warning(s) 表示 lint 模式下告警提升为退出错误,CI 会卡在这里。另外注意 5.x 版本的行为变化:不显式给 --timing 或 --no-timing,带延迟的代码先报 %Error-NEEDTIMINGOPT 要求你表态,本文的告警是在 --no-timing 下才出现的形态。
二、根本原因:延迟语义被悄悄丢掉
# 延迟是 Verilog 事件调度语义的一部分,本质是行为级建模设施——testbench 里模拟走线延迟、老代码里凑合对齐时序都用它。但综合器不认它(物理上不存在「延迟 5ns 的组合逻辑」这种单元),Verilator 的两状态仿真内核也不调度这类事件。三种工具对同一行代码的处理一张卡看全:
这张卡的读法是盯住中间一栏的「忽略」二字:Verilator 不是不支持就罢工,而是报一条警告后继续按「没有延迟」编译。下面这个对照实验能看到差异有多大——同一段 assign #5 y = a & b; 在 iverilog 里跑,输入 t=10000 拉高,输出 y 拖到 t=15000 才变 1,5ns 的惯性延迟清晰可见;同样的代码喂给 Verilator,输出随输入零延迟变化。如果你的验证基准是 iverilog 的波形,而签核仿真用 Verilator,这个分歧足以让两边的断言结果对不上。
== iverilog 对照:t=10000 输入拉高,t=15000 输出才跟上 ==
t=0 a=0 b=0 y=x
t=5000 a=0 b=0 y=0
t=10000 a=1 b=1 y=0
t=15000 a=1 b=1 y=1
t=20000 a=0 b=0 y=1
t=25000 a=0 b=0 y=0典型引入场景有三种:一是把老 testbench 里模拟走线延迟的 assign # 顺手拷进了 DUT;二是从行为级模型(behavioral model)改写 RTL 时漏删延迟;三是网上抄来的「延迟对齐」技巧。三条来源共享一个特征——这段代码原本就不打算被综合,搬进 RTL 就是事故。
顺带厘清 --timing 与 --no-timing 的取舍,因为 5.x 的 %Error-NEEDTIMINGOPT 会逼你先表态。--timing 让 Verilator 真调度延迟事件,#5 生效,仿真语义向事件驱动器对齐,代价是性能明显下滑(官方口径大约慢一到数倍,视延迟事件密度而定),适合 testbench 侧必须保留延迟建模的场景;--no-timing 丢弃一切延迟换满速仿真,适合纯 RTL 签核。一条工程经验:DUT 永远走 --no-timing 且把 ASSIGNDLY 当错误看待,testbench 单独编译时按需开 --timing——两边混编又不分区处理,是最常见的「仿真跑通了但语义错了」的来源。
三、修复与复验
修复原则一句话:延迟是时序意图就用寄存器表达,是建模残留就直接删。上面例子里的两种形态恰好各对应一条路:
module assigndly_fixed (
input wire clk,
input wire a,
input wire b,
output wire y,
output reg q
);
assign y = a & b; // 形态一:组合逻辑,延迟本就是残留,删掉
reg or_d;
always @(posedge clk) begin
or_d <= a | b; // 形态二:第一拍寄存
q <= or_d; // 第二拍输出,显式打一拍
end
endmodule对修复后的文件重跑 lint,零告警、退出码 0,闭环:
== verilator --lint-only -Wall assigndly_fixed.v ==
(no output)
exit_code=0两点强调。第一,q <= #10 (a | b) 这种写法的原始意图通常是「晚一个周期生效」或「对齐另一路信号的时序」,但 #10 里的 10 是时间单位不是拍数——时钟周期变了它就歪,只有显式寄存器打拍才跟随时钟走。第二,不要用 /* verilator lint_off ASSIGNDLY */ 圈掉告警:告警消了,「延迟被忽略」这个事实没变,仿真与综合行为的差异被埋进代码里,下次换人读代码照样踩坑。testbench 侧确实需要延迟建模时,正确姿势是开 --timing(或只在 tb 文件上开)让 Verilator 真调度延迟事件,而不是假装它不存在。修复完成的标准动作永远是同一条:对修复后的文件重跑同一 lint 命令,看到零告警、退出码归零再收工。
四、排查与预防清单
- 全文搜
#加数字:grep -n '#[0-9]' rtl/一把捞出所有延迟写法,先确认哪些文件是可综合 RTL。注意别误伤参数覆盖的module #(...)与例化传参的#(.WIDTH(8)),这两个#是参数语法、不是延迟。 - 分形态处理:
assign #在组合逻辑里九成是残留,删;<= #/= #在 always 里要问意图,改寄存器打拍。 - testbench 与 DUT 分开编译:tb 里的延迟合法,用
--timing编译 tb、DUT 保持--no-timing,告警自然清零。 - 行为模型改写 RTL 时走查一遍延迟:behavioral → RTL 的改写清单里加一条「所有
#延迟已替换为寄存器或删除」。 - CI 固化
-Wall --no-timing:ASSIGNDLY 在提交阶段拦下,别等仿真对不上波形才回头找。
实验环境
- 机器/系统:Mac mini / macOS(docker geek-eda:0.2 镜像内跑 Verilator)
- 工具版本:Verilator 5.020 2024-01-01 rev (Debian 5.020-1);Icarus Verilog version 13.0 (stable) (v13_0)
- 日期:2026-10-29
- 完整命令:
verilator --lint-only -Wall --no-timing assigndly_bad.v(告警复现);iverilog -o tb_delay.vvp tb_delay.v && vvp tb_delay.vvp(延迟生效对照);verilator --lint-only -Wall assigndly_fixed.v(修复复验)
代码获取
复现用源码(assigndly_bad.v 报错版、assigndly_fixed.v 修复版、tb_delay.v 对照 testbench)在公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/verilator-warn-assigndly。装好 Verilator 后跑 verilator --lint-only -Wall --no-timing assigndly_bad.v 复现告警,再对 assigndly_fixed.v 跑同一命令验证修复;tb_delay.v 用 iverilog 跑可看到延迟真实生效的对照波形日志。
这一篇在知识体系里的位置
本篇是 EDA 报错排查系列中 Verilator lint 告警簇的一员:initial 块里的延迟看 Verilator INITIALDLY 警告,组合逻辑里的非阻塞赋值看 Verilator COMBDLY 警告,时序控制的整体支持边界与 --timing/--no-timing 选择见 Verilator 时序延迟支持边界,开源工具链报错的总入口见 开源 EDA 报错排查导航,整个系列的目录在 EDA 报错排查系列。
总结
Verilator ASSIGNDLY 警告就是「这条赋值带 # 延迟,--no-timing 下被忽略」——assign #5 和 q <= #10 两种形态,危险在忽略后同一份代码在事件驱动仿真器与 Verilator 里波形不同、综合器也不认;修复按形态分流:组合逻辑里的延迟是残留直接删,时序意图改显式寄存器打拍,testbench 侧合法延迟走 --timing 编译;修复后重跑 -Wall 零告警闭环,永远不要用 lint_off ASSIGNDLY 消音代替修复。