04. 运算符、表达式与位宽推导
Verilog 的表达式最终要落到硬件上。看到运算符时,不仅要问“数学结果是什么”,还要问“需要什么电路、输出几位、组合路径多长”。

算术运算
跳转到“算术运算”assign sum = a + b;assign diff = a - b;assign product = a * b;assign quotient = a / b;assign remainder = a % b;加减通常映射到加法器/减法器。乘法可能使用 FPGA 的 DSP 资源;除法和取模若除数不是 2 的幂,可能形成很大的组合逻辑或需要专用 IP/多周期算法。
当除数是 2 的幂且数据为适当的无符号数时,可用移位:
assign times_4 = data << 2;assign div_8 = data >> 3;但移位不会自动扩大左操作数位宽:8 位 data << 2 的结果仍可能只有 8 位有效表达式宽度,高位被丢弃。需要保留时先扩宽:
assign wide_times_4 = {2'b00, data} << 2;关系与相等运算
跳转到“关系与相等运算”a > ba < ba >= ba <= ba == ba != b关系表达式产生 1 位结果,可综合为比较器。注意 <= 有两种身份:
- 在表达式
a <= b中是“小于等于”; - 在过程赋值
q <= d;中是“非阻塞赋值”。
上下文决定含义。
带 x/z 的仿真中,== 的结果可能为 x;===/!== 会把 x/z 当作可比较符号,主要用于 testbench。
逻辑运算与位运算
跳转到“逻辑运算与位运算”最容易混淆的是两组“与或非”。
| 类型 | 运算符 | 作用对象 | 常见用途 | ||
|---|---|---|---|---|---|
| 逻辑 | ! && ` | ` | 把整个操作数判断为真/假 | if 条件 | |
| 按位 | ~ & ` | ^` | 每一位分别运算 | 掩码、门逻辑 |
wire [3:0] a = 4'b1010;wire [3:0] b = 4'b1100;
wire [3:0] bit_and = a & b; // 4'b1000wire log_and = a && b; // 1'b1,因为 a、b 都非零!a 是 1 位逻辑结果;~a 与 a 同宽并逐位取反。
归约运算
跳转到“归约运算”单目 &、|、^ 会把一个向量归约成 1 位:
wire all_one = &data; // 所有位都为 1 时为 1wire any_one = |data; // 任一位为 1 时为 1wire parity = ^data; // 所有位异或,可作奇偶校验不要把 &data 与 a & b 混淆:前者一个操作数,是归约;后者两个操作数,是按位与。
条件运算符
跳转到“条件运算符”assign y = sel ? a : b;它通常对应二选一多路选择器,等价于完整的 if/else 组合逻辑。条件嵌套越深,多路选择级数可能越深:
assign y = s0 ? a : s1 ? b : c;可读性下降时,改用 always @(*) 和 case。
拼接与重复
跳转到“拼接与重复”wire [7:0] byte_data = {high_nibble, low_nibble};wire [15:0] repeated = {4{4'b1010}};拼接 {a, b} 中左侧部分成为高位。重复拼接 {N{value}} 把 value 重复 N 次。常见用途包括扩展、字段打包和符号扩展:
wire signed [7:0] small;wire signed [15:0] extended = {{8{small[7]}}, small};移位与符号
跳转到“移位与符号”<<:逻辑左移,低位补 0;>>:逻辑右移,高位补 0;<<<、>>>:算术移位,其中算术右移才可能复制符号位。
算术右移是否按符号扩展取决于左操作数是否是 signed:
wire signed [7:0] negative = -8'sd8;wire signed [7:0] half = negative >>> 1; // -4运算优先级:不靠背,靠括号
跳转到“运算优先级:不靠背,靠括号”assign valid = enable && (count == LIMIT);assign masked = (data & MASK) | DEFAULT_BITS;括号既防止优先级误判,也清楚表达设计意图。复杂算式可拆成有名称的中间 wire,便于看波形和约束时序。
位宽推导的工程方法
跳转到“位宽推导的工程方法”每个表达式按四步检查:
- 标出所有操作数的位宽;
- 标出 signed/unsigned;
- 估算数学结果范围;
- 明确扩展后再运算,确保目标足够宽。
例:计算 8 位无符号数的平均值:
wire [7:0] a;wire [7:0] b;wire [8:0] full_sum = {1'b0, a} + {1'b0, b};wire [7:0] average = full_sum[8:1];如果先在 8 位内相加再右移,进位可能已丢失。
资源与时序意识
跳转到“资源与时序意识”- 固定位选择和拼接通常只是布线;
- 比较、加减需要组合逻辑;
- 大位宽乘法可能占 DSP;
- 变量移位通常需要桶形移位器;
- 除法、取模和长条件链可能成本很高。
语法短不代表硬件小。综合报告会告诉你 LUT、触发器、DSP 和关键路径的实际情况。
练习
跳转到“练习”- 分别计算
4'b1010 && 4'b0101与4'b1010 & 4'b0101。 - 写表达式判断 8 位
data是否所有位为 1。 - 无损计算两个 12 位无符号数之和,结果应声明多少位?
- 用拼接把 4 位
nibble扩展成 8 位无符号数。
答案:1'b1 与 4'b0000;&data;13 位;{4'b0, nibble}。