%Warning-UNUSEDSIGNAL: Signal is not used 排查:Verilator 信号未使用告警速查
更新时间:2026-10-26。FAQ 速查页,属于 EDA 报错排查系列。
%Warning-UNUSEDSIGNAL: Signal is not used是 Verilator 开-Wall后命中率高居前列的告警:信号被驱动了,却没有任何地方读它。注意版本差异——Verilator 4.x(含本机 4.038)里这条告警码叫 UNUSED,5.x 起才改名为 UNUSEDSIGNAL,报错原文与lint_off写法都跟着变,本文两个名字都会用到。它和 %Warning-UNDRIVEN 是一对镜像:一个缺读者,一个缺写者。
首屏答案:这个告警本身不改功能——多一根没人读的线,综合工具多半直接优化掉,代价是浪费一点面积、刷屏告警淹没问题。真正要判断的是「该有读者却没有」还是「本来就不该存在」:前者是漏连,功能 bug 的前兆,必须补上消费它的逻辑;后者是死代码,整根删掉。只有确认是刻意保留(预留调试口、留给 ECO 的 spare 信号)时才用 lint_off 局部豁免——豁免代码名必须与版本匹配:4.x 写 lint_off UNUSED,5.x 写 lint_off UNUSEDSIGNAL,写错名字会直接报 Unknown lint message code。
本文要回答的问题
%Warning-UNUSEDSIGNAL: Signal is not used报错串每个字段是什么意思,UNUSED 与 UNUSEDSIGNAL 是什么关系?- 信号明明在端口表/赋值语句里出现了,为什么还说没人用?
- 「删掉 / 补上消费逻辑 / lint_off 豁免」三条处置路分别什么场景用?
lint_off的正确写法是什么,为什么照抄网上的 UNUSEDSIGNAL 写法在旧版上直接报错?
一、报错原文与现象
最小复现只有三行:模块端口 debug_cfg 接了进来,但 assign 里只用了 a,没有任何逻辑读它:
module n4_bad(input wire a, input wire debug_cfg, output wire y);
assign y = a; // debug_cfg 接进来却没人用
endmodule对这个文件跑 verilator --lint-only -Wall,真实告警如下:
读这段输出抓四个字段:第一,%Warning-UNUSED 后面 n4_bad.v:1:40 指向声明位置(第 1 行第 40 列的端口名),并直接给出信号名 'debug_cfg'——UNUSED 报告的是声明处,与 UNDRIVEN 一致;第二,In instance n4_bad 给出层次上下文,大工程里靠它区分同名信号;第三,末尾提示行给出消音写法 lint_off UNUSED——这个 code 名就是当前版本的合法拼写,照抄它就是最稳的写法来源;第四,%Error: Exiting due to 1 warning(s) 表示 lint 模式下告警被提升为退出错误,CI 里这条会卡住构建。5.x 版本里同一段输出的告警码会印成 %Warning-UNUSEDSIGNAL,其余字段不变。
二、病因分诊:死代码还是漏连
UNUSED 告警的处置没有唯一答案,先做一道二选一的分诊:
| 告警形态 | 真实身份 | 处置 |
|---|---|---|
| 重构后残留的旧信号,新旧逻辑都不读它 | 死代码 | 整根删除(声明 + 驱动它的逻辑) |
| 调试用的观测线,仿真完忘了删 | 死代码 | 删除,或包进 `ifdef DEBUG 整体移除 |
| 本该参与运算的信号被漏写进表达式 | 漏连 | 补上消费逻辑(本例处置二) |
| 子模块 input 接了线,模块内部没用 | 接口冗余或漏连 | 查规格书:该用就补逻辑,不该用就删端口 |
| 预留的调试口 / spare 信号,刻意保留 | 有意留白 | lint_off 局部豁免 + 注释说明 |
| 同时报 UNDRIVEN 和 UNUSED | 纯死代码 | 连声明带逻辑整根删,别犹豫 |
判断标准一句话:这个信号应不应该影响输出? 应该——是漏连,找到它该参与的表达式补上;不应该——是死代码或留白,删或豁免。分诊错了代价不小:把漏连当死代码删掉,功能就静悄悄错了;把死代码当漏连硬接进逻辑,反而引入新 bug。
实际工程里还有两个高频伪装形态值得点名。一是位选式使用:wire [7:0] cfg 只有低 4 位进了表达式,高 4 位没人读,部分版本会对未使用的比特位单独报 UNUSED(5.x 拆出了更细的 UNUSEDBIT),处置同本篇,但删的时候按位段删而不是整根删。二是端口级未使用:本例的 debug_cfg 就是端口形态——模块边界上的 UNUSED 往往意味着接口设计变了、调用方还在按旧接口连线,此时先和上游对齐意图再动手,比在模块内部悄悄豁免更负责任。
三、三种处置逐一展开
下面这张卡把三条处置路放在一起对照,颜色表达角色:绿是补逻辑的正路,蓝是删死代码的清洁路,紫是刻意保留的豁免路。
处置一:删掉。 本例如果确认 debug_cfg 是早期调试接口、产品逻辑根本不需要,就把端口从模块定义里删掉,调用处的连线一并清理:
module n4_delete(input wire a, output wire y);
assign y = a; // 处置一:确认死代码后删掉端口
endmodule# command: verilator --lint-only -Wall /tmp/errlog-evidence-b4/src/n4_delete.v
# date: 2026-10-26T01:42:58+08:00
# host-toolchain: Yosys 0.9 (git sha1 1979e0b) / Verilator 4.038 2020-07-11 rev v4.036-114-g0cd4a57ad / iverilog 11.0
---
---
# exit_code: 0两个 --- 之间没有任何输出,-Wall 全量告警清零,这就是干净状态。删端口别忘了同步改所有例化点,否则会引出 PINNOTFOUND 类新报错。
处置二:补上消费逻辑。 如果 debug_cfg 的设计意图就是调试模式下翻转输出,那 UNUSED 是在提醒你「接了线却忘了用」——补上真实逻辑:
module n4_use(input wire a, input wire debug_cfg, output wire y);
assign y = debug_cfg ? ~a : a; // 处置二:其实是漏连,补上真实逻辑
endmodule同样跑 verilator --lint-only -Wall,输出与处置一一样干净(见 fix-use.log)。这条路是三者里唯一改变功能的,补完务必补一条定向测试验证新逻辑,别只验 lint 干净就收工。
处置三:lint_off 豁免——先看版本坑。 刻意保留的信号(预留调试口)用 pragma 局部包裹:
/* verilator lint_off UNUSEDSIGNAL */
wire unused_ok = debug_cfg;
/* verilator lint_on UNUSEDSIGNAL */但这行写法在 4.038 上直接报错——网上抄来的 UNUSEDSIGNAL 是 5.x 的新名字,旧版不认:
# command: verilator --lint-only -Wall /tmp/errlog-evidence-b4/src/n4_lintoff.v
# date: 2026-10-26T01:42:58+08:00
# host-toolchain: Yosys 0.9 (git sha1 1979e0b) / Verilator 4.038 2020-07-11 rev v4.036-114-g0cd4a57ad / iverilog 11.0
---
%Error: /tmp/errlog-evidence-b4/src/n4_lintoff.v:2:3: Unknown verilator lint message code: 'UNUSEDSIGNAL', in '/*verilator lint_off UNUSEDSIGNAL*/'
---
# exit_code: 0修法很简单:pragma 里的 code 名照抄本机告警原文里那个名字——4.x 写 /* verilator lint_off UNUSED */,5.x 写 UNUSEDSIGNAL。不确定就跑一次 lint 看原文,告警里自带正确拼写,这是最权威的来源。两个更稳的习惯:一是 lint_off / lint_on 只包裹目标信号那一两行,绝不整文件关闭;二是有个零 pragma 的取巧写法——Verilator 对名字以 unused 开头的信号默认豁免 UNUSED(上面 wire unused_ok 的命名就是故意踩这个约定),改名比加 pragma 更不易随版本翻车。
三条路怎么选,再给一个对照表收口:
| 处置 | 适用场景 | 风险 | 后续动作 |
|---|---|---|---|
| 删掉 | 确认死代码(重构残留、忘删调试线) | 删错真信号 | 改全例化点,重跑 lint |
| 补逻辑 | 漏连,本该参与输出 | 引入新 bug | 补定向测试验功能 |
| lint_off | 刻意保留(预留口 / spare) | 掩盖真问题、code 名随版本失效 | 注释写意图,升级工具链时复核 |
四、预防与纪律
- 端口接入即消费:每加一个 input 端口,立刻写下它参与的逻辑;预留口当场加
lint_off与注释,不留「回头再说」。 - 重构后跑一次
-Wall:UNUSED 批量冒头通常是重构残留的信号清单,照着删一轮比逐行检查快。 - pragma code 名照抄告警原文:不换版本不猜拼写,升级 Verilator 后全库 grep 一遍旧 code 名。
- 读写同属一个编译分支:调试用信号连声明带消费一起包进
`ifdef DEBUG,避免 Release 构建报 UNUSED。 - CI 零告警门禁:
verilator --lint-only -Wall进流水线,告警计数非零即构建失败,豁免必须落在代码注释里可审计。
实验环境
- 机器/系统:云主机 / Ubuntu 22.04
- 工具版本:Verilator 4.038 2020-07-11 rev v4.036-114-g0cd4a57ad(照抄 log 头
host-toolchain行);Yosys 0.9、iverilog 11.0 同机备用 - 日期:2026-10-26
- 完整命令:
verilator --lint-only -Wall /tmp/errlog-evidence-b4/src/n4_bad.v(复现告警);verilator --lint-only -Wall /tmp/errlog-evidence-b4/src/n4_delete.v、.../n4_use.v(验证处置一、二);verilator --lint-only -Wall /tmp/errlog-evidence-b4/src/n4_lintoff.v(复现旧版不认 UNUSEDSIGNAL 的报错)
代码获取
复现用四份源码(n4_bad.v 复现版、n4_delete.v 删除处置、n4_use.v 补读者处置、n4_lintoff.v lint_off 处置)与四份 lint log 在公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/ts-verilator-unusedsignal-UNUSED。装好 Verilator 后逐份跑 verilator --lint-only -Wall <文件名> 即可对照四种处置的告警变化。
这一篇在知识体系里的位置
本篇是 EDA 报错排查系列 中 Verilator lint 告警簇的一员,与 %Warning-UNDRIVEN(缺写者的镜像问题,功能风险更高)成对使用:UNDRIVEN 管驱动链完整性,UNUSED 管消费链完整性;同一信号两报齐发即是死代码,整根删除。
一句话总结
%Warning-UNUSEDSIGNAL: Signal is not used(旧版告警码 UNUSED)= 信号有驱动无读者,先分诊是漏连还是死代码:漏连就补上消费逻辑并补测试,死代码就连声明带驱动整根删,只有刻意保留的预留信号才用 lint_off 局部豁免——pragma 的 code 名照抄本机告警原文(4.x 写 UNUSED、5.x 写 UNUSEDSIGNAL),照抄网上的新名字在旧版上会直接报 Unknown lint message code。