Veloris.
返回索引
概念基础 2026-02-23

每个运算符都是一个电路:FPGA 开发者的 Verilog 运算符实战手册

6 分钟
2.0k words

每个运算符都是一个电路:FPGA 开发者的 Verilog 运算符实战手册

💡 你写了一行 assign result = data / 8;,综合工具报了一个 Warning,或者综合出了一个占用几百个 LUT 的庞大电路。而你的同事只改了一个符号——assign result = data >> 3;——资源消耗变成了

这就是 Verilog 运算符和 C 语言运算符的本质区别:Verilog 的每一个运算符都对应着一段真实的硬件电路+ 号背后是加法器,* 号背后是 DSP Slice,>> 号背后只是重新接了几根线。写 C 语言时你不用关心加法和移位的性能差异,但写 Verilog 时,选错运算符可能意味着芯片放不下你的设计

这篇文章不是运算符语法的枯燥罗列,而是帮你建立**“运算符 → 硬件电路”的映射思维**——这是 FPGA 工程师的核心能力之一。

📌 系列衔接:本文承接上一篇 Verilog 模块与信号系统


目录


1. 运算符优先级总览

先给一张总表,遇到优先级拿不准时回来查:

优先级运算符说明
最高! ~逻辑非、按位取反
* / %乘、除、取模
+ -加、减
<< >>左移、右移
< <= > >=关系比较
== != === !==等式比较
&按位与
^ ~^按位异或、同或
|按位或
&&逻辑与
||逻辑或
最低?:条件运算符

💡 工程师手记:虽然有优先级表,但我的建议是——拿不准就加括号。RTL 代码不是炫技的地方,可读性永远比简洁性重要。加括号还能避免不同综合工具对优先级理解不一致的问题。


2. 算术运算符

运算符功能硬件对应
+加法加法器
-减法加法器(补码)
*乘法乘法器(DSP Slice)
/除法⚠️ 通常不可综合
%取模⚠️ 通常不可综合
wire [8:0] sum = a + b;     // 8位加法,注意进位需要9位
wire [15:0] prod = a * b;   // 8位乘8位,结果16位

FPGA 开发者须知

  • 加法和减法是最基础的运算,综合后占用 LUT 资源,开销不大。
  • 乘法在 Xilinx FPGA 中会映射到专用的 DSP48 Slice,效率很高。但 DSP Slice 数量有限,大量乘法可能耗尽。
  • 除法和取模没有简单的硬件对应,综合工具通常报错或综合出极其庞大的电路。如果确实需要除法,考虑用移位代替(除以 2 的幂次)或使用专用 IP 核。
  • 当操作数中有 x(未知值)时,整个运算结果都是 x
// ✅ 推荐:用移位代替除以2的幂次
assign result = data >> 3;    // 等效于 data / 8

// ❌ 避免:直接使用除法
assign result = data / 8;     // 综合结果不可预测

3. 位运算符

位运算符是 FPGA 开发中使用频率最高的运算符——因为数字电路的本质就是对每一位信号做逻辑运算。

运算符功能硬件对应
&按位与AND 门
|按位或OR 门
~按位取反NOT 门(反相器)
^按位异或XOR 门
~^按位同或XNOR 门
wire [3:0] a = 4'b1100;
wire [3:0] b = 4'b1010;

wire [3:0] and_result = a & b;   // 1000
wire [3:0] or_result  = a | b;   // 1110
wire [3:0] xor_result = a ^ b;   // 0110
wire [3:0] not_result = ~a;      // 0011

位宽不等时的处理

当两个操作数位宽不同时,Verilog 会自动将较短的操作数高位补零,然后按位运算。这是一个隐蔽的坑——如果你的信号是有符号数,高位补零可能导致结果错误。

wire [7:0] a = 8'hFF;    // 8位
wire [3:0] b = 4'hF;     // 4位

// b 会被扩展为 8'h0F,然后做按位与
wire [7:0] result = a & b;  // 结果是 8'h0F,而非 8'hFF

4. 归约运算符

归约运算符和位运算符长得一模一样,但含义完全不同。位运算是两个操作数之间逐位运算,归约运算是一个操作数内部所有位之间运算。

运算符功能结果位宽
&所有位做与1 bit
|所有位做或1 bit
^所有位做异或1 bit
wire [3:0] data = 4'b1010;

wire all_one  = &data;   // 1&0&1&0 = 0(是否全1)
wire any_one  = |data;   // 1|0|1|0 = 1(是否有1)
wire parity   = ^data;   // 1^0^1^0 = 0(奇偶校验)

归约运算符在 FPGA 开发中非常实用:

  • &data:判断信号是否全为 1
  • |data:判断信号是否全为 0(取反:~|data
  • ^data:奇偶校验位生成

💬 你可能会问:怎么区分 & 是位运算还是归约运算?

看操作数个数。a & b(两个操作数)是位运算,&a(一个操作数)是归约运算。编译器根据上下文自动判断。


5. 逻辑运算符与关系运算符

逻辑运算符

运算符功能结果
&&逻辑与1 bit(真/假)
||逻辑或1 bit(真/假)
!逻辑非1 bit(真/假)

逻辑运算符将整个操作数视为一个整体——非零为真(1),零为假(0)。

wire [3:0] a = 4'b1010;   // 非零,逻辑上为"真"
wire [3:0] b = 4'b0000;   // 零,逻辑上为"假"

wire result = a && b;      // 1 && 0 = 0

! vs ~ 的区别! 是逻辑非(结果是 1 bit),~ 是按位取反(结果与操作数同位宽)。

wire [3:0] a = 4'b1010;
wire       logic_not = !a;   // 结果:1'b0(a非零,取反为假)
wire [3:0] bit_not   = ~a;   // 结果:4'b0101(逐位取反)

关系运算符

><>=<= 用于比较大小,结果为 1 bit。在 FPGA 中综合为比较器电路


6. 移位运算符

运算符功能补位
<<逻辑左移低位补 0
>>逻辑右移高位补 0
wire [7:0] a = 8'b1100_0011;

wire [7:0] left  = a << 2;  // 0000_1100(左移2位)
wire [7:0] right = a >> 2;  // 0011_0000(右移2位)

移位的硬件本质——零资源消耗的”免费运算”

这是本文最值得记住的知识点:移位操作在硬件中不消耗任何逻辑资源——它只是重新连线。左移 N 位就是把信号线向高位平移 N 位,空出来的低位接地(0)。这也是为什么用移位代替乘/除 2 的幂次是 FPGA 设计的常见优化。

// 这三行综合结果完全相同,但移位的意图更清晰
assign y = a << 3;     // 乘以8
assign y = a * 8;      // 乘以8(综合工具通常也会优化为移位)
assign y = a * 4'd8;   // 乘以8

7. 拼接与复制运算符

拼接运算符 {} 是 Verilog 的”瑞士军刀”——把多个信号首尾相连,组成更宽的信号。

拼接

wire [3:0] high = 4'hA;
wire [3:0] low  = 4'h5;

wire [7:0] byte_data = {high, low};  // 8'hA5

复制

{N{signal}} 将信号复制 N 份后拼接:

wire [7:0] all_one  = {8{1'b1}};       // 8'hFF
wire [7:0] extended = {{4{data[3]}}, data[3:0]};  // 符号扩展

常见应用场景

// 1. 符号扩展:将4位有符号数扩展为8位
wire [3:0] signed_4bit = 4'b1010;  // -6(补码)
wire [7:0] signed_8bit = {{4{signed_4bit[3]}}, signed_4bit};  // 8'b1111_1010

// 2. 清零高位
wire [7:0] masked = {4'b0, data[3:0]};  // 只保留低4位

// 3. 位域拼接
wire [15:0] packet = {start_bit, addr, data, parity};

8. 条件运算符

? : 是 Verilog 中的三目运算符,功能等同于 2 选 1 MUX(多路选择器)。

assign out = sel ? data_1 : data_0;

综合后就是一个 MUX:当 sel=1 时输出 data_1,否则输出 data_0

可以嵌套使用,实现多路选择:

// 4选1 MUX
assign out = (sel == 2'b00) ? d0 :
             (sel == 2'b01) ? d1 :
             (sel == 2'b10) ? d2 :
                              d3 ;

💡 工程师手记:条件运算符嵌套超过 3 层后,可读性会急剧下降。这时候我通常会改用 always @(*) 块配合 case 语句,虽然代码长了一点,但看起来清爽多了。

(建议替换为你自己的真实经历,读者会更有共鸣)


9. 等式运算符

Verilog 有两组等式运算符:

运算符名称对 x/z 的处理
== !=逻辑等/不等含 x 或 z 时结果为 x
=== !==全等/不全等x 和 z 也参与精确比较
wire a = 1'bx;
wire b = 1'bx;

wire eq1 = (a == b);    // 结果:x(不确定)
wire eq2 = (a === b);   // 结果:1(两个x完全相同)

FPGA 开发实践

  • RTL 设计(可综合代码)中只用 ==!=
  • ===!== 只用于仿真和 Testbench,综合工具不支持
  • === 常用于 casex / casez 的行为模拟

10. 总结

运算符类型代表硬件对应FPGA 开发关注点
算术+ - *加法器、乘法器除法/取模尽量避免
位运算& | ^ ~逻辑门使用频率最高
归约&a |a ^a级联逻辑门全1/全0判断、奇偶校验
移位<< >>重新连线零资源消耗,替代乘除
拼接{}重新连线信号拼接、符号扩展
条件?:MUX嵌套不超过3层
等式== ===比较器=== 仅用于仿真

核心思维:每写一个运算符,想一想它对应什么硬件。加法器多大?乘法器要不要用 DSP?移位是不是零开销?这种思维习惯越早养成,你的 RTL 代码质量就越高。


常见问题

Q1:& 到底是位运算还是归约运算?编译器怎么区分?

看操作数个数。a & b(双目)是按位与,&a(单目)是归约与。编译器根据语法上下文自动判断,不会混淆。

Q2:乘法一定会用 DSP Slice 吗?

不一定。如果操作数是常数且为 2 的幂次,综合工具会优化为移位。如果操作数很小(如 3 位乘 3 位),工具可能用 LUT 实现。你可以通过综合报告查看具体的映射结果,也可以用属性 (* use_dsp = "yes" *) 强制使用 DSP。

Q3:Verilog 有没有算术右移(保留符号位)?

Verilog-2001 引入了 >>> 算术右移和 <<< 算术左移。但要注意,信号必须声明为 signed 类型才能生效:

wire signed [7:0] a = -8'd4;
wire signed [7:0] b = a >>> 1;  // 算术右移,高位补符号位

参考资料


系列导航:本文是「FPGA 入门系列」第 8 篇。

如果这篇文章对你有帮助,欢迎点赞、收藏,也欢迎在评论区分享你踩过的运算符相关的坑。

End of file.