Verilator --Werror 告警升级致命退出:LATCH 默认 fatal 与 -Wno-fatal 分级豁免
本篇属于 EDA 报错排查系列,聚焦 Verilator lint 告警的致命等级而不是告警本身。同簇的 %Warning-LATCH 排查讲的是「为什么会推断出锁存器、RTL 怎么改」;本篇回答的是另一个常把新人绕进去的问题:为什么同一条告警,有时候只打印警告、退出码却是 0,有时候却以
%Error收尾、退出码 1 把 CI 直接卡住。
本文要回答的问题
- 同一条 LATCH 告警,为什么默认运行退出码 1,加了
-Wno-fatal就退出码 0? -Werror-LATCH、-Wno-fatal、-Wno-LATCH三个开关各改的是什么,同时出现时谁覆盖谁?- 裸写
-Werror为什么不生效,正确的选项形态是什么? - CI 里该保留哪种等级语义,临时豁免一条告警的合规写法有哪些?
一、完整报错原文与现象
实验用的 latch_bad.v 是一个缺 else 分支的组合 always 块,en 为低时 q 没有任何赋值,Verilator 据此推断出锁存器。不少从 GCC 迁来的使用者第一反应是给命令行加一个裸 --Werror 或 -Werror,但在 5.020 上这两种写法都到不了 lint 阶段,直接在参数解析时被拒:
$ verilator --lint-only --Werror latch_bad.v
%Error: Invalid option: --Werror... Suggested alternative: '-Werror-'
exit code: 1
$ verilator --lint-only -Werror latch_bad.v
%Error: Invalid option: -Werror... Suggested alternative: '-Werror-'
exit code: 1报错信息里的 Suggested alternative: '-Werror-' 已经给出提示:Verilator 的升级开关必须带消息名后缀,裸形态不是合法选项。再看不加任何等级开关的默认行为,以下为容器内真机输出原文:
$ verilator --lint-only latch_bad.v
%Warning-LATCH: latch_bad.v:6:1: Latch inferred for signal 'q' (not all control paths of combinational always assign a value)
: ... Suggest use of always_latch for intentional latches
6 | always @(*) begin
| ^~~~~~
... For warning description see https://verilator.org/warn/LATCH?v=5.020
... Use "/* verilator lint_off LATCH */" and lint_on around source to disable this message.
%Error: Exiting due to 1 warning(s)
exit code: 1注意最后两行的措辞:消息头是 %Warning-LATCH,说明它的出身是警告;但收尾是 %Error: Exiting due to 1 warning(s),进程以退出码 1 结束。也就是说,警告被按致命错误处理了,而这并不需要你显式加任何 --Werror 类选项——LATCH 属于 Verilator 的默认致命告警集合。
作为对照,同一份 RTL 只加一个 -Wno-fatal,告警原文一字不差地打印,结局却完全不同:
$ verilator --lint-only -Wno-fatal latch_bad.v
%Warning-LATCH: latch_bad.v:6:1: Latch inferred for signal 'q' (not all control paths of combinational always assign a value)
: ... Suggest use of always_latch for intentional latches
6 | always @(*) begin
| ^~~~~~
... For warning description see https://verilator.org/warn/LATCH?v=5.020
... Use "/* verilator lint_off LATCH */" and lint_on around source to disable this message.
exit code: 0消息还在,%Error 收尾没了,退出码归 0。这就是「告警」与「致命」两个概念被拆开后的样子:开关改变的是处置等级,不是检测结果。七步实验的完整节奏先给一张全景卡:
二、工具与版本
本篇全部结论来自 Linux 容器内的真机实验:镜像 geek-eda:0.2(Debian bookworm 基底),Verilator 5.020,命令统一走 verilator --lint-only 只做 lint 检查、不生成 C++ 仿真骨架。选择 lint-only 是因为告警等级机制在这个模式下最纯粹——没有后续编译步骤干扰,进程退出码完全由告警处置结果决定。
版本差异需要交代一句:Verilator 4.x 时代部分告警的默认等级与 5.x 不同,LATCH 在 5.020 上确认是默认致命;但 -Wno-fatal、-Werror-<message>、-Wno-<message> 这套选项语法从 4.x 延续至今,文中的开关用法在新旧版本上通用,只有「默认到底致不致命」需要以本机 verilator --version 对应的文档为准。
三、根本原因:告警自带等级,开关只改等级
Verilator 的每条 lint 消息都有一个内置等级:普通警告或默认致命(fatal)。LATCH、MULTIDRIVEN 这类暗示真实硬件隐患的消息被放进致命集合,所以裸跑就以 %Error 收尾;UNUSED 之类风格类消息默认只是警告。命令行开关做的事情,是在这条默认等级线上做加减,而不是开关检测本身。
读这张图抓一条覆盖链:-Wno-fatal 是全局动作,把所有告警一律软化;-Werror-LATCH 是单条动作,把指定消息重新提回致命。真机第三步把两者写在同一条命令里(-Wno-fatal -Werror-LATCH),结果消息头直接变成 %Error-LATCH、收尾是 Exiting due to 1 error(s)、退出码 1——细粒度的单条开关压过全局软化,这就是 CI 分级策略能成立的语法基础。
另一个高频踩坑点是选项形态。Verilator 的升级选项必须带消息名后缀,完整形态是 -Werror-<message>,例如 -Werror-LATCH;并不存在 GCC 风格的裸 -Werror——真机实验开头两步已经证实,无论单横线还是双横线,参数解析器都直接回 %Error: Invalid option,并附上 Suggested alternative: '-Werror-' 的提示,进程在 lint 开始前就以退出码 1 结束。同理,降级与静默也是「前缀 + 消息名」的配对形态:-Wno-fatal(全局去致命化)与 -Wno-LATCH(关掉这一条)。记住这个配对表,就不会在命令行里拼出不存在的选项。
四、排查清单与 CI 门禁语义
CI 场景下,lint 任务的价值全靠退出码承接:脚本或流水线 runner 只认进程退出码,0 放行、非 0 阻断。等级开关怎么选,本质是在决定「哪些书写问题有权拦住合并」。
按这张图的分工,拿到告警被致命退出时照清单逐项核对:
- 先看收尾行:
Exiting due to N warning(s)说明是「警告被按致命处理」,Exiting due to N error(s)说明消息本身已被升级为 error(比如-Werror-LATCH后的%Error-LATCH),两者措辞不同,决定了你下一步是查默认等级还是查命令行覆盖。 - 清点命令行上的等级开关:有没有
-Wno-fatal全局软化、有没有针对该消息的-Werror-<message>或-Wno-<message>;多个开关同时出现时,单条选项覆盖全局选项。 - 检查选项后缀:任何
-Werror/-Wno都必须带消息名,裸写的-Werror不是合法选项,不要拿 GCC 的肌肉记忆套 Verilator。 - 区分豁免位置:命令行
-Wno-LATCH对整个编译单元生效、影响面大;源码内/* verilator lint_off LATCH */ ... lint_on */只圈定一小段、意图可读,局部豁免优先用后者,写法细节见 lint_off pragma 用法。 - 守住 CI 默认值:流水线里保持默认致命等级,不要图省事全局挂
-Wno-fatal,否则 LATCH 这类硬件隐患会静默流入综合;本地调试想先看全量告警时再临时加。 - 豁免必须留痕可回溯:无论命令行还是 pragma,豁免理由写进提交说明或代码注释,并尽快用 RTL 修复替换,消音不能成为终态。
五、修复代码与复跑确认
等级开关只改变处置,真正闭环仍要回到 RTL。报错版的问题是 en 为低时 q 没有赋值:
module latch_bad (
input wire en,
input wire d,
output reg q
);
always @(*) begin
if (en)
q = d;
end
endmodule补全 else 分支,让 q 在所有控制路径上都有确定值,锁存器推断的前提消失,告警自然不再产生:
module latch_fixed (
input wire en,
input wire d,
output reg q
);
always @(*) begin
if (en)
q = d;
else
q = 1'b0;
end
endmodule七个步骤、七种命令形态的完整真机记录在一次 bash run.sh 中顺序跑完,退出码序列 1/1/0/1/1/0/0:
读这份记录看四个对照点:开头两步裸写 --Werror 与 -Werror 都只拿到 Invalid option,证明升级开关必须带消息名;第三步 -Wno-fatal 打印同样的 %Warning-LATCH 但退出码 0,证明告警检测与致命处置是两件事;第四步是默认行为,同一条消息自带 fatal、退出码 1;第五步 -Wno-fatal -Werror-LATCH 同时出现,消息头升级为 %Error-LATCH、退出码 1,证明单条升级覆盖全局软化;最后两步 -Wno-LATCH 无任何输出退出码 0、修复版 RTL 同样零输出退出码 0——两个 0 的含义截然不同,前者是消音、后者是问题不存在,CI 策略只应接受第二种。
实验环境
- 机器/系统:Linux 容器环境(Docker 镜像 geek-eda:0.2,Debian bookworm)
- 工具版本:Verilator 5.020
- 日期:2026-11-11
- 完整命令:
bash run.sh,内部依次执行verilator --lint-only --Werror latch_bad.v(退出码 1,Invalid option)、verilator --lint-only -Werror latch_bad.v(退出码 1,Invalid option)、verilator --lint-only -Wno-fatal latch_bad.v(退出码 0)、verilator --lint-only latch_bad.v(退出码 1)、verilator --lint-only -Wno-fatal -Werror-LATCH latch_bad.v(退出码 1)、verilator --lint-only -Wno-LATCH latch_bad.v(退出码 0)、verilator --lint-only latch_fixed.v(退出码 0)
代码获取
复现用源码(latch_bad.v、latch_fixed.v)与一键脚本 run.sh 在公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/ts-verilator-werror-fatal。本地备好 geek-eda:0.2 镜像后,在该目录执行 docker run --rm -v "$PWD":/work -w /work geek-eda:0.2 bash run.sh 即可一次跑出七步退出码序列。
这一篇在知识体系里的位置
本篇讲告警的等级与门禁,同簇三篇各管一段:锁存器为什么会被推断、RTL 层面怎么修,看 %Warning-LATCH: Latch inferred 排查;源码内局部豁免的 pragma 写法与作用域,看 Verilator lint_off/lint_on 用法;同属分支不全家族的 case 缺 default 问题,看 %Warning-CASEINCOMPLETE 排查。整个 Verilator 报错簇的入口与更多工具的报错索引在 EDA 报错排查系列。
总结
Verilator 的 lint 消息自带默认等级,LATCH 在 5.020 上默认就是致命项,不需要、也不能靠裸 --Werror/-Werror 升级——两种裸写法都会在参数解析阶段拿到 Invalid option,合法形态是带消息名后缀的 -Werror-LATCH;-Wno-fatal 全局软化所有告警但不关闭检测,-Wno-LATCH 单条静默,两者同时出现时单条开关覆盖全局开关。CI 应保持默认致命等级守住硬件隐患,临时豁免优先用源码内 lint_off 小范围圈定并留痕,最终闭环仍靠补全分支这类 RTL 修复,让告警从「被消音」变成「不再产生」。