跳转到内容
新建笔记

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 > b
a < b
a >= b
a <= b
a == b
a != 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'b1000
wire log_and = a && b; // 1'b1,因为 a、b 都非零

!a 是 1 位逻辑结果;~a 与 a 同宽并逐位取反。

单目 &、|、^ 会把一个向量归约成 1 位:

wire all_one = &data; // 所有位都为 1 时为 1
wire any_one = |data; // 任一位为 1 时为 1
wire 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,便于看波形和约束时序。

每个表达式按四步检查:

  1. 标出所有操作数的位宽;
  2. 标出 signed/unsigned;
  3. 估算数学结果范围;
  4. 明确扩展后再运算,确保目标足够宽。

例:计算 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 和关键路径的实际情况。

  1. 分别计算 4'b1010 && 4'b0101 与 4'b1010 & 4'b0101。
  2. 写表达式判断 8 位 data 是否所有位为 1。
  3. 无损计算两个 12 位无符号数之和,结果应声明多少位?
  4. 用拼接把 4 位 nibble 扩展成 8 位无符号数。

答案:1'b1 与 4'b0000;&data;13 位;{4'b0, nibble}。

上一篇:信号与模块 · 返回目录 · 下一篇:组合逻辑