Verilator lint 入门流程:把 --lint-only -Wall 接进存量工程的第一小时
更新时间:2026-11-03。写完 RTL 之后、仿真和综合之前,最便宜的一道质量门是 lint:不生成任何产物、不写 testbench,只让工具把代码读一遍挑毛病。系统的 lint 工作流(告警分级、waiver 豁免管理)在 verilator lint 静态检查实操 里讲过,本篇只解决「第一小时」的问题——拿到一个别人写的 FIFO 工程,第一条命令该怎么敲、输出怎么读、告警怎么修。九类高频告警的图鉴见 Verilog lint 高频告警矩阵实测。
本文要回答的问题
verilator --lint-only和平时用来做仿真的--cc模式到底差在哪,会不会生成文件?-Wall、-Wno-fatal、--top-module各自什么时候加,不加会怎样?- lint 输出的
%Warning-WIDTHEXPAND: 文件:行:列四段式怎么读? - 存量工程第一次 lint 报了一堆告警,按什么顺序修,怎样算「干净」?
先给一张概念卡,把这一步的边界框出来:
读这张卡抓住一个边界:lint 是纯静态检查,它不生成 C++、不编译、不需要你准备激励,所以一条命令通常在一秒内返回;也正因为它不跑电路,读写计数写反这类功能笔误它抓不到,那是仿真的职责。
一、这一步在数字设计流程里的位置
一个典型的开源数字设计流程是「写 RTL → lint 静态检查 → 写 testbench 仿真 → 综合」。lint 卡在 RTL 冻结后的第一关:上游只给你 .v 源码,它把问题以「文件:行:列」的形式还回给你;通过之后,同一份源码才值得送去仿真和综合。
读这张图注意那个回到 RTL 的分叉:lint 的价值不是「打印一堆英文」,而是退出码——有未豁免告警时退出码非零,所以它能被脚本和 CI 机械地判成通过或失败,不需要人去读输出。本篇的工程就两个文件:fifo_top.v 是顶层,只做一层例化;sync_fifo.v 是一个 4 深度、8 位宽的同步 FIFO。
二、命令逐段解读
对接入存量工程的第一小时,完整命令是这一条:
docker run --rm -v "$PWD":/work -w /work geek-eda:0.3-sta \
verilator --lint-only -Wall --top-module fifo_top \
fifo_top.v sync_fifo.v前半段是 docker 包装(把当前目录挂进容器、在容器内同路径执行),工具本体的参数逐个拆:
| 参数 | 作用 | 不加会怎样 |
|---|---|---|
--lint-only | 只做 lint:解析、精化、跑告警规则,不生成 C++ 与 obj_dir/ | 默认进 --cc 仿真模式,会吐出一整个 obj_dir/ |
-Wall | 打开全部风格与可疑构造告警(WIDTHEXPAND、UNUSEDSIGNAL、DECLFILENAME 等) | 默认只开一个小集合,位宽、未用信号这类高频问题会被静默放过 |
-Wno-fatal | 告警照打印,但不把退出码顶成非零 | 默认 lint-only 下任何 Warning 都致命,退出码 1 |
--top-module fifo_top | 指定层次根是哪一个 module | 多文件且存在多个「没被例化」的模块时,无法确定根,报 MULTITOP |
| 文件列表 | 参与检查的全部 RTL,多文件按任意顺序列出 | 只给一个文件时,例化到的子模块要靠 -I/搜索路径找,找不到报 cannot find module |
-Wno-fatal 只建议在「摸底」阶段用一次——存量工程第一次接入往往有几十上百条告警,你想先看全量清单而不是让它第一条就退出。日常提交门禁里不要加,让告警保持致命,才能挡住新代码。多文件工程里文件顺序无所谓,Verilator 会先建立全部模块定义再做精化;但所有文件都要显式列出(或用 .f 文件列表),它不会像综合工具那样自动按模块名去磁盘上找文件。
工程长到几十个文件后,命令行列举很快不可维护,通行做法是维护一份 rtl.f,每行一个源码路径,执行时写 verilator --lint-only -Wall --top-module fifo_top -f rtl.f。接入 CI 时更简单:lint 不依赖波形库、不依赖图形界面,一条容器命令挂在提交流水线里,退出码非零就挡住合并——这是整条数字设计流程里成本最低的自动化门禁,没有之一。
三、输入输出文件长什么样
输入就是普通 RTL。本次的 FIFO 用指针加计数实现,存量版本里有两段可疑代码:
reg [2:0] count; // 0..4,3 位
wire [3:0] status;
wire dbg;
assign dbg = wr_en & rd_en; // 只驱动、不消费
assign status = count; // 3 位直接赋给 4 位输出只有终端文本,分四段:告警类型与位置、层次实例、带行号源码片段加 ^ 指向、豁免提示。下面这份实录是对存量 RTL 的第一次 lint(容器:Verilator 5.020):
读这份输出按类型分级。%Warning-WIDTHEXPAND 是位宽类:右边的 count 只有 3 位、左边 status 要 4 位,Verilog 会隐式零扩展——本例恰好等价于想要的补零,但隐式扩展在别处可能吞掉符号位,规则要求你把扩位意图显式写出来。同家族的 WIDTHTRUNC(窄赋值给宽之后反向截断)危险得多。%Warning-UNUSEDSIGNAL 是卫生类:dbg 算了没人看,通常是重构残留。最后一行 %Error: Exiting due to 2 warning(s) 不是编译错误,而是 lint-only 的策略:未豁免 Warning 一律致命,所以退出码是 1。单条告警(如 UNOPTFLAT 组合环、UNDRIVEN 未驱动)的逐条病因可以查 UNDRIVEN 信号未驱动排查 和 WIDTH 位宽告警速查。
作为对照,同一工程加 -Wno-fatal 再跑,两条告警原样打印,退出码变成 0——这就是摸底用法。
四、修告警与复跑干净
修法遵循「能显式就别隐式、是废线就删」:
// 修复 1:零扩展意图写在代码里,WIDTHEXPAND 消除
assign status = {1'b0, count};
// 修复 2:dbg 是调试残留,整根删除,UNUSEDSIGNAL 消除注意一个边界:存量代码里还有一处功能笔误(写指针递增时 count 反而减 1),lint 全程没说话——静态规则不判断语义对错,这处要靠 testbench 仿真抓,修复版里一并改正。复跑同一条命令,并用 --cc 对照证明 lint-only 确实不产文件:
读这段实录抓两个对比:lint-only 干净时一个字都不打印、退出码 0;而把 --lint-only 换成 --cc 后立刻出现 obj_dir/,里面是 Vfifo_top.cpp/.h/.mk 等 C++ 仿真模型——这就是两个模式的本质差别,一个只检查,一个产仿真模型。
接入新工程的新手高频错记四个:① 文件列表漏了子模块文件,报 cannot find module,把全部 RTL 列全;② 文件名与模块名不一致触发 DECLFILENAME,遵守「一名一文」或定点豁免,别全局关;③ 看到退出码 1 就去翻 %Error,其实根因是上面的 Warning,从第一条 Warning 开始读;④ 贪图干净用命令行 -Wno-WIDTH 整类压掉,正确做法是改代码,确属误报才用 /* verilator lint_off ... */ 局部包裹并注明理由。
实验环境
- 机器/系统:Mac mini(Docker Desktop)/ 容器镜像 geek-eda:0.3-sta(Debian 基础)
- 工具版本:
Verilator 5.020 2024-01-01 rev (Debian 5.020-1)(容器内verilator --version真实输出) - 日期:2026-11-03
- 完整命令:
bash run.sh(容器内依次执行verilator --version、对rtl_bad/的--lint-only -Wall、加-Wno-fatal复跑、--cc对照、对rtl_good/复跑)
代码获取
存量版(rtl_bad/,含两条 lint 告警与一处功能笔误)、修复版(rtl_good/)与复现脚本已放到公开仓库:geek-doc.cn/experiments/flow-verilator-lint-first,装好 docker 后执行 make lint-bad 复现告警、make lint-good 验证零告警,或直接 bash run.sh 一把跑完。
这一篇在知识体系里的位置
- 工作流深化:verilator lint:--lint-only 静态检查实操、告警分级与 waiver 豁免(本篇是它的「第一次接入」前置版);
- 告警图鉴:Verilog lint 高频告警矩阵实测(九类告警各造一个最小案例);
- 下一步:lint 干净后写 testbench,可接 同步 FIFO Verilog 实现与回归实测 走仿真闭环。
总结
verilator --lint-only -Wall --top-module <顶> <文件列表> 是接入存量工程的第一条命令:lint-only 只读 RTL、零产物、秒级返回,-Wall 打开全量告警,有未豁免 Warning 退出码即非零;WIDTHEXPAND 这类告警用显式扩位消除、UNUSEDSIGNAL 这类废线直接删,修完复跑到「无输出、退出码 0」才算干净——但 lint 管规矩不管功能,语义笔误仍要靠仿真抓。