Verilog 有符号 signed 和无符号 unsigned 有什么区别?
一句话回答
Verilog 里 wire/reg 默认是无符号**,加减按二进制做模 2ⁿ 运算;只有运算两边(含中间结果)都是 signed 时才按有符号补码解释,任何一个操作数是无符号,整个表达式就退化成无符号运算。有符号数用最高位当符号位、负数以补码表示,扩位要做符号扩展而不是补 0。混用时要用 $signed()/$unsigned() 显式转换,否则比较和移位会给出反直觉结果。**
关键区别
| 方面 | 无符号(默认) | 有符号 signed |
|---|---|---|
| 取值 | 0 ~ 2ⁿ−1 | −2ⁿ⁻¹ ~ 2ⁿ⁻¹−1 |
| 解释 | 纯二进制 | 最高位为符号位,负数补码 |
| 扩位 | 高位补 0 | 高位复制符号位 |
| 右移 | 逻辑补 0 | 算术补符号位(综合看类型) |
| 比较 | 数值大小 | 按带符号大小 |
为什么常算错
verilog
reg signed [7:0] a;
reg [7:0] b;
reg signed [8:0] y;
a = -8'd10; // 实际是 8'hF6
y = a + b; // b 无符号 → 整体按无符号算!
y = a + $signed(b); // 正确:两边都 signed- 混类型传染:表达式里出现一个无符号数,全部按无符号处理,这是最常见 bug。
- 常量也要带符号:
8'd10是无符号,有符号应写8'sd10。 - 位宽/截断:结果位宽不够会直接砍掉高位,负号和进位都可能丢。
- 扩位错误:把有符号数接到更宽总线却补 0,负数立刻变成大正数;要符号扩展。
实践建议
- 数据通路(滤波器、ALU、传感器读数)统一在模块边界确定符号性,内部全程
signed。 - 用
$signed()做显式转换而不是依赖默认规则;转换前后想清楚位宽。 - 跨位宽时先扩到足够位宽(含 guard bits)再运算,避免溢出。
- 用 SystemVerilog 可对有符号比较直接
signed'()强转并在仿真里加断言检查范围。
常见坑
- 有符号变量和无符号信号直接相加/比较,结果悄悄变无符号。
- 忘记常量的
's,d常量拖垮整个表达式符号性。 - 有符号负数右移期待算术移位,却因类型/工具综合成逻辑移位。
- 结果位宽不足导致补码负溢出后被当成巨大正数。
总结
Verilog 默认无符号、按模 2ⁿ 运算,只有表达式两端及中间结果全部 signed 才按补码做有符号运算,任何一个无符号操作数都会把整体拉回无符号;有符号数负数用补码、扩位要复制符号位、常量写 'sd,混用时务必用 $signed()/$unsigned() 显式转换并预留足够位宽,才能避免比较、移位和扩位的反直觉错误。