Verilator MULTITOP:--top-module 指定与层级裁剪
Verilator MULTITOP 出现在同一次编译发现了多个顶层模块时。所谓顶层,是没有被其他模块例化的层级根,不是文件列表里的第一个文件,也不一定叫 top。把全部 RTL、测试包装和调试模块一次性传给 lint,最容易遇到它;这与 Verilator MULTITOP 之外常见的模块定位失败不同,后者是找不到定义,本篇则是入口不止一个。
本文要回答的问题
Verilator MULTITOP为什么把两个合法模块判成一次失败编译?- 5.020 中
%Warning-MULTITOP与末尾%Error分别表示什么? --top-module除了消除告警,会怎样改变展开后的设计层级?- 怎样选择正确顶层,并用 lint 与层级输出复跑确认?
读这张图要抓住“本次”二字:多个顶层可以同时存在于仓库,却不应同时成为同一次编译的根。修复是明确编译边界,不是删除另一个合法模块,也不是把告警文本压掉。
一、完整报错:两个互不例化的模块都是顶层
最小源码包含 chip_top 和 debug_top。前者做两位加法,后者计算八位奇偶校验;二者没有例化关系,也没有语法错误。它们分别作为独立设计时都能成立,放进同一份文件清单后便形成两个层级根。
module chip_top (
input logic [1:0] a,
input logic [1:0] b,
output logic [2:0] sum
);
assign sum = a + b;
endmodule
module debug_top (
input logic [7:0] sample,
output logic parity
);
assign parity = ^sample;
endmodule判断“互不例化”要看模块之间的关系,而不是看它们是否写在同一个文件。若 chip_top 内存在 debug_top u_debug (...),debug_top 就会成为子节点,整棵树只有 chip_top 一个根;本例没有这条例化语句,所以声明顺序无法建立父子关系。反之,把两个模块拆成两个文件也不会自动解决问题,只要仍被同一次命令读入,根的数量就不变。
执行 verilator --lint-only multitop.sv,5.020 给出下面的原始诊断。它先点名第二个根 debug_top,再分别列出 chip_top 与 debug_top 的声明位置,建议用 --top-module 选择顶层。
%Warning-MULTITOP: multitop.sv:9:8: Multiple top level modules
: ... Suggest see manual; fix the duplicates, or use --top-module to select top.
... For warning description see https://verilator.org/warn/MULTITOP?v=5.020
... Use "/* verilator lint_off MULTITOP */" and lint_on around source to disable this message.
: ... Top module 'chip_top'
1 | module chip_top (
| ^~~~~~~~
: ... Top module 'debug_top'
9 | module debug_top (
| ^~~~~~~~~
%Error: Exiting due to 1 warning(s)
broken exit=1这里有一个容易读错的地方:专码行是 Warning,进程却退出 1。末尾 Error 不是说源码又产生了第二种语法错误,而是当前告警致错策略不允许带着这条告警结束。即使把 MULTITOP 单独设为非致命,两个根仍然存在,自动选择的结果未必符合本次构建目标。
二、工具版本:5.020 同次运行对照修复前后
实验使用 Debian 打包的 Verilator 5.020。下面的记录来自同一份源码、同一个容器:broken 路径复现多顶层,fixed 路径加入 --top-module chip_top,随后输出 XML 层级供计数。三段放在一起,可以排除换源码、换版本造成的差异。
记录中的关键数字很克制:默认 lint 因 1 条 MULTITOP 告警退出 1;指定 chip_top 后没有诊断并退出 0;展开后的 XML 只含 1 个模块,debug_top 不在其中。末尾 PASS 是脚本对这些条件的逐项核对,不代表额外跑过功能仿真。
版本信息要随报错一起保存。不同版本或不同告警参数可能改变专码的致命性、提示顺序和默认顶层处理,不能把这里的退出码套到所有旧工程。复现时应同时记录完整命令、源文件版本与 verilator --version,尤其在 CI 与本地结果不一致时;只看一张缺少参数的报错片段,很容易把告警策略差异误判成层级差异。
三、根因:顶层选择决定层级裁剪边界
Verilator 需要从唯一根开始展开例化关系。未指定时,它发现两个候选根,无法用文件名或源码顺序替你决定哪一个才是产品入口。--top-module chip_top 给出的正是这个边界:本次编译以 chip_top 为根,其下只保留可达的例化层级;独立的 debug_top 不再作为另一个根参与展开。
| 调用与检查 | 实测结果 | 能证明什么 |
|---|---|---|
verilator --lint-only multitop.sv | MULTITOP,退出 1 | 两个根使本次目标不明确 |
加 --top-module chip_top | 无诊断,退出 0 | 唯一根已指定,lint 通过 |
| 同一顶层下输出 XML | 模块数 1,无 debug_top | 未选根不在展开层级里 |
层级裁剪不等于不读取其他源码。前端仍可能发现文件中的语法问题,不能利用顶层选择把坏代码藏进仓库;它裁剪的是本次展开与后续处理的设计范围。也不要把 XML 检查当成每次构建的必需步骤,它只是这里用来观察层级边界的证据,日常修复仍以目标 lint 或仿真命令通过为准。
这个边界还解释了为什么指定顶层可能改变后续告警集合:未被选中的独立树不再进入同样的展开流程,原本属于它的连接和优化问题未必继续出现。但这不是用 --top-module 清除告警的理由。若另一棵树也要交付,就应给它单独安排 lint 目标;只有明确属于废弃代码时才从文件集移除。顶层选项表达“这次检查谁”,并不证明“其他代码都正确”。
四、排查:先确认编译目标,再处理多余入口
拿到 MULTITOP,先从诊断列出的模块反查文件清单。若一个是交付顶层、另一个是独立测试包装,应为它们建立不同编译目标,各自指定 --top-module。若团队确实需要双顶层联合仿真,则要新增一个包装模块,把两者都例化进去,再以包装模块为唯一顶层;不能在原命令上机械任选一个。
| 排查发现 | 真正的边界问题 | 建议动作 |
|---|---|---|
| RTL 与 testbench 混在一次 lint | 两类检查目标混用 | 拆分文件清单并分别指定顶层 |
| 多个独立 IP 被一次传入 | 每个 IP 都是合法根 | 为每个 IP 建独立目标 |
| 两个根需要一起仿真 | 缺少共同父节点 | 新增包装模块并例化两者 |
| 同名模块意外重复出现 | 文件集或依赖可能重复 | 先查重复来源,不直接选根 |
还要留意条件编译。某些包装模块只在宏开启时例化子模块,宏变化后原来的子节点可能变成额外顶层。因此排查不能只读某一份静态文件列表,应展开实际命令中的 -D、文件列表引用和自动生成源码。CI 与本地宏配置不同时,同一仓库完全可能一边通过、一边 MULTITOP。把最终生效的命令打印出来,比逐个文件凭印象数模块可靠。
图中蓝色是读取事实,红色是必须作出的目标判断,橙色对应两种合理改法,绿色是共同出口。若所谓“多余顶层”来自误把整个 tb/ 目录加入 RTL lint,应修文件清单;若每个顶层本来就是独立用例,则在脚本里拆分调用。单纯加 -Wno-MULTITOP 绕开了入口决策,不属于这两类修复。
五、修复与复跑:指定 chip_top 并验证只剩一个根
本例目标是检查加法器,因此源码不变,只在原命令加入 --top-module chip_top。随后用同一选择输出临时 XML,统计模块名并检查另一个根是否消失。
verilator --lint-only --top-module chip_top multitop.sv
verilator --xml-only --xml-output chip_top.xml --top-module chip_top multitop.sv实测第一条命令无诊断、退出 0,第二条命令生成的层级中模块数为 1,且没有 debug_top。这同时回答了两个问题:MULTITOP 已解除,选择的入口也确实限定了展开范围。落地到工程时,应把顶层名与源文件清单一并放入对应构建目标,避免某条脚本仍沿用无顶层旧命令;切换检查 debug_top 时则显式换成它,而不是依赖工具猜测。
复跑还要确认使用的不是旧构建产物。清理本次输出目录,让 Verilator 重新读取当前文件清单,再核对退出码和诊断。若指定了一个拼错或不存在的顶层,通常会转成找不到顶层的另一类错误,这时应修正模块名,而不是退回无顶层调用。对回归脚本,可以像本例一样保留一个最小双顶层用例:默认路径必须出现 MULTITOP,目标路径必须零诊断通过,防止以后重构参数时悄悄丢掉入口约束。
实验环境
- 机器/系统:macOS 主机,所有 Verilator 命令均在已有
geek-eda:0.2Linux 容器中执行 - 工具版本:Verilator 5.020 2024-01-01 rev (Debian 5.020-1)
- 日期:2026-10-29
- 完整命令:在实验目录执行
docker run --rm -v "$PWD":/work -w /work geek-eda:0.2 make run;脚本依次运行默认--lint-only、带--top-module chip_top的--lint-only与同顶层的--xml-only,并核对专码、退出码和层级模块数。
代码获取
双顶层源码、复现脚本、Makefile 和运行说明位于公开仓 experiments/ts-verilator-warn-multitop。进入目录后执行 make run,或使用上面的容器命令即可复现。
这一篇在知识体系里的位置
本篇属于 EDA 报错排查系列 的 Verilator 编译入口节点,处理的是“定义太多导致根不唯一”。找不到模块定义时转到 Verilator 模块定位失败排查;需要对照综合工具的顶层设置,可看 Verilator MULTITOP 与 Yosys 顶层检查的关系。三者都围绕层级入口,但报错条件和修复对象并不相同。
总结
Verilator MULTITOP 的根因是一次编译存在多个未被例化的顶层。5.020 实测中,默认 lint 报 1 条 MULTITOP 告警并退出 1;加入 --top-module chip_top 后无诊断、退出 0,XML 层级只余 1 个模块且不含 debug_top。正确做法是先明确本次目标,再指定唯一顶层或增加包装顶层,最后复跑验证,而不是只压掉告警。