Verilator UNSIGNED:有符号与无符号比较恒假修复
Verilator UNSIGNED 表示一个比较因为无符号运算规则而成为常量,典型形式是 unsigned_expr < 0 永远为假。隐蔽之处在于:表达式里明明出现了 signed,只要同层还混着无符号操作数,整个加法仍可能按无符号解释。本篇用计数器与有符号增量的最小用例复现专码,并在运算前统一类型、保留负数语义。隐式扩位引起的另一类数值问题见 Verilator UNSIGNED 相邻的 WIDTHEXPAND 诊断。
本文要回答的问题
- 为什么
count + delta < 0中即使有signed delta,比较仍然恒假? UNSIGNED与一般常量比较告警有什么区别?- 如何沿着算术表达式定位是哪一步失去了有符号性?
- 怎样显式扩位修复,并用退出码确认 lint 真正通过?
读这张图要抓住第三步:最后一个 < 0 只是暴露问题的位置,真正让比较恒假的是它左边的加法已经成为无符号表达式。只把 0 改成 0'sd0 无法挽回这个结果,必须回到操作数进入运算之前处理。
一、Verilator UNSIGNED 完整报错:比较被直接判为常量
下面的 count 是 8 位无符号计数,delta 是 8 位有符号增量。设计意图是在两者之和为负时置位 underflow,例如 count=3、delta=-5 时应得到真。
module unsigned_broken (
input wire [7:0] count,
input wire signed [7:0] delta,
output wire underflow
);
assign underflow = (count + delta) < 0;
endmodule实际 lint 给出的核心行是:
%Warning-UNSIGNED: unsigned_broken.sv:6:40: Comparison is constant due to unsigned arithmetic
%Error: Exiting due to 1 warning(s)插入符指向 <,信息则明确写着 unsigned arithmetic。这说明问题不是端口连接或位宽不足,而是语言规则已经能静态证明比较结果。默认告警致错策略下,进程随后以 1 退出;末尾 %Error 不代表另有一个语法错误。
二、工具版本与复跑记录:5.020 给出独立专码
实验使用 Verilator 5.020。该版本会把这种无符号常量比较报告为 UNSIGNED,比只写「表达式可能有问题」更容易定位。不要急着用 lint_off 消音:恒假的组合逻辑可能让保护分支永远不可达,仿真与综合都不会替设计补上原本想要的有符号语义。
记录中 broken 的专码、退出码 1,fixed 的无诊断、退出码 0 以及最后的 PASS 都来自同一次运行。PASS 还核对了 broken 确实包含 %Warning-UNSIGNED,避免源码改错后两边都安静、却被误判为修复成功。
== broken ==
%Warning-UNSIGNED: unsigned_broken.sv:6:40: Comparison is constant due to unsigned arithmetic
%Error: Exiting due to 1 warning(s)
broken exit=1
== fixed ==
fixed diagnostics: none
fixed exit=0
PASS: mixed-sign comparison reported UNSIGNED; fixed lint exited 0三、根因:混合运算先变成无符号,再与零比较
SystemVerilog 表达式的有符号性取决于整个上下文与操作数。对本例的同层加法,只要 count 是无符号,混合的 count + delta 就按无符号运算理解;delta 声明中的 signed 不会单独支配结果。随后得到的位向量与零比较,自然不可能小于零。
以 3 + (-5) 为例,8 位 delta 的位型是 8'hFB。若按有符号解释,它是 -5,结果应为 -2;若按无符号解释,它是 251,8 位加法得到 254,仍是一个非负数。位型计算并没有「少了负号」,错误发生在数值解释与比较语义上。同理,把条件写成 count + delta >= 0 会得到恒真分支,也不能实现向下越界检测。
| 观察位置 | 表面写法 | 实际解释 | 后果 |
|---|---|---|---|
| 输入声明 | count 无符号、delta 有符号 | 两种类型进入同一加法 | 不能凭 delta 推断结果 signed |
| 加法结果 | count + delta | 按无符号表达式处理 | 负值意图丢失 |
| 最终比较 | 加法结果 < 0 | 无符号数始终大于等于零 | 条件恒假并触发 UNSIGNED |
这与 Verilator UNSIGNED 相关的 CMPCONST 常量比较项目要区分:UNSIGNED 的关键证据是无符号性导致结果固定;位宽截断、越界常量也可能形成恒真或恒假,但修复入口不同。
四、排查清单:从比较符反向追到操作数声明
遇到专码时,不要只看插入符所在的 <。先把完整比较表达式拆开,逐层确认运算结果的类型,再判断应该改声明、改转换,还是改边界条件。
红色节点是报错入口,蓝色节点完成拆解和类型判断,橙色节点才是真正修改,绿色节点只接受可复跑的通过结果。实际项目可照下面的清单核对,尤其不要漏掉参数、宏和函数返回值,它们同样可能把无符号性带进表达式。
- 比较是否真需要负数:如果物理量不可能为负,
< 0本身可能就是多余条件,应删除或改成上界检查。 - 每个操作数的声明:检查端口、局部变量、参数和函数返回值,不要只检查名字里带
signed的那个。 - 位宽是否足够:把 8 位无符号数直接强转为 signed,可能让
8'hFF从 255 变成 -1;转换前要增加前导零。 - 常量是否带类型与位宽:修复后使用
9'sd0,让比较边界和表达式位宽保持一致。 - 是否用消音掩盖问题:只有确认恒假比较就是设计意图时才考虑局部豁免,并写明原因。
五、修复与复跑:先扩位,再统一为 signed
本例需要保留 count + delta 的负值,因此不能简单给原表达式套一个 $signed。8 位无符号 count 的最大值是 255,直接解释成 8 位有符号数会变成 -1。正确做法是把 count 前面补一个零,得到 9 位正数;delta 则复制符号位扩到 9 位。
module unsigned_fixed (
input wire [7:0] count,
input wire signed [7:0] delta,
output wire underflow
);
wire signed [8:0] signed_count = {1'b0, count};
wire signed [8:0] signed_delta = {delta[7], delta};
assign underflow = (signed_count + signed_delta) < 9'sd0;
endmodule这样两个操作数与加法结果都具有明确的 signed 属性,9 位也能覆盖本例和的范围。signed_count=3、signed_delta=-5 时,比较对象保持 -2,underflow 可以为真。复跑 fixed 没有任何诊断,退出码为 0;同一脚本仍确认 broken 触发一次专码并退出 1,最终输出 PASS。
如果设计只要求检测向下越界,也可以在业务层改写成等价的无符号边界比较,但必须证明位宽、回绕和取值范围都满足条件。本篇选择显式 signed 运算,是因为它最直接地保留原始数学意图,并让审阅者不用重新推导一套回绕公式。
实验环境
- 机器/系统:Intel x86_64 Mac / macOS 26.5.2,实验在
geek-eda:0.2容器中运行 - 工具版本:Verilator 5.020 2024-01-01 rev (Debian 5.020-1)
- 日期:2026-10-30
- 完整命令:
docker run --rm -v "$PWD":/work -w /work geek-eda:0.2 make run;脚本分别执行verilator --lint-only -Wall unsigned_broken.sv与verilator --lint-only -Wall unsigned_fixed.sv,保存诊断并核对 broken 非零、fixed 零退出和专码内容。
代码获取
broken/fixed 源码、Makefile、复现脚本与原始输出均在公开仓 experiments/ts-verilator-warn-unsigned。进入该目录后沿用上面的容器命令执行 make run,即可重复完整对照。
这一篇在知识体系里的位置
本篇属于 EDA 报错排查系列 的 Verilator 类型与常量比较分支:无符号性导致恒假看本篇,一般常量比较看 Verilator UNSIGNED 的相邻专码 CMPCONST,扩位方向不明确看 Verilator UNSIGNED 之外的 WIDTHEXPAND 排查。更多开源工具报错的分类入口见 开源 EDA 报错排查导航。
总结
Verilator UNSIGNED 的核心不是看到一个 unsigned 变量就消告警,而是找出哪一步让比较失去负数语义。本例中无符号 count 与有符号 delta 的混合加法使 < 0 恒假,Verilator 5.020 实测 broken 退出 1;两侧先显式扩为 9 位 signed、再与 9'sd0 比较后,fixed 无诊断并退出 0。修复要同时守住类型和取值范围,不能用直接强转或消音代替边界分析。