4. Verilog基础语法(二):运算符与表达式

各位同学,欢迎回来。上一节我们聊了Verilog的数据类型和基本结构,算是把“砖块”认全了。今天咱们来聊聊怎么把这些“砖块”砌起来——也就是运算符和表达式。

说白了,运算符就是Verilog里的“动作指令”。你要让加法器干活,就得用+;你要判断两个数谁大谁小,就得用>。这些符号看着简单,但用不好,综合出来的电路可能跟你想象的完全不一样。我刚开始学的时候,就吃过这个亏。

4.1 算术运算符:加减乘除那些事儿

算术运算符大家最熟悉了,就是+-*/%。但FPGA里用它们,跟C语言里写代码,完全是两码事。

运算符 含义 综合结果
+ 加法 加法器(LUT+进位链)
- 减法 减法器(补码加法)
* 乘法 乘法器(DSP或LUT)
/ 除法 除法器(资源消耗极大)
% 取模 取模器(慎用)
注意:除法/和取模%在FPGA里非常“贵”。如果除数是2的幂次,比如除以4,直接用右移>>2代替,资源能省一大半。我曾经在一个项目里,因为用了除法器,导致LUT不够用,最后改成移位才解决问题。

另外,算术运算的结果位宽要小心。两个4位数相加,结果应该是5位,否则会溢出。我习惯这样写:

wire [3:0] a, b;
wire [4:0] sum;
assign sum = {1'b0, a} + {1'b0, b};  // 扩展一位,防止溢出

4.2 逻辑运算符:真真假假

逻辑运算符有&&(与)、||(或)、!(非)。它们的结果只有两种:真(1)或假(0)。

注意,逻辑运算符操作的是整个数。比如a && b,只要a和b都不为0,结果就是1。这在条件判断里很常用。

if (a && b) begin
  // 只有当a和b都不为0时,才执行这里
end

嗯,这里要注意:逻辑运算符和位运算符容易搞混。逻辑运算符是“整体判断”,位运算符是“逐位操作”。我见过不少新手把&&写成&,综合出来电路完全不对。

4.3 位运算符:逐位操作

位运算符包括&(按位与)、|(按位或)、^(按位异或)、~(按位取反)。它们对数据的每一位独立操作。

举个例子:

wire [3:0] a = 4'b1010;
wire [3:0] b = 4'b1100;
wire [3:0] c;
assign c = a & b;  // 结果是 4'b1000

位运算符在寄存器配置、掩码操作里用得特别多。比如你想把某个寄存器的第3位清零,可以这样:

reg [7:0] status_reg;
status_reg <= status_reg & 8'b1111_0111;  // 第3位清零
小技巧:异或^可以用来做奇偶校验。比如^data,就是对data的所有位做异或,结果就是奇偶校验位。我在做SPI通信时经常用这个。

4.4 关系运算符:比大小

关系运算符有><>=<===!=。结果也是0或1。

这里有个坑:=====的区别。==比较时,如果某一位是x或z,结果就是x(未知)。而===会精确比较,包括x和z。在仿真里,===很有用,但综合时一般用==就够了。

if (a == b) begin
  // 相等
end

关系运算符综合出来是比较器。比较器资源不大,但如果比较的位宽很大(比如32位),也会消耗不少LUT。

4.5 移位运算符:左移右移

移位运算符有<<(左移)、>>(右移)。左移相当于乘以2,右移相当于除以2(对于无符号数)。

wire [3:0] a = 4'b0011;  // 3
wire [3:0] b;
assign b = a << 1;       // 结果是 4'b0110,即6

移位在FPGA里几乎不消耗资源,因为布线就能实现。所以能用移位的地方,尽量别用乘除法。

核心要点:移位运算符是FPGA里最“便宜”的运算之一。能用移位实现的,绝对不要用乘法器或除法器。

4.6 条件运算符:三目运算符

条件运算符? :,也叫三目运算符。格式是:条件 ? 表达式1 : 表达式2。如果条件为真,取表达式1的值,否则取表达式2的值。

assign max = (a > b) ? a : b;  // 取a和b中的较大值

条件运算符综合出来是一个多路选择器(MUX)。如果条件嵌套多了,会形成多级MUX,影响时序。我建议嵌套不要超过3层,否则代码可读性差,综合结果也不好。

4.7 运算符优先级:谁先谁后

运算符优先级是个容易忽略的问题。优先级搞错了,综合出来的电路可能跟你想要的不一样。

优先级 运算符
最高 ~!
*/%
+-
<<>>
<><=>=
==!=
&
^
|
&&
最低 ||

我的建议是:不要依赖优先级,多用括号。括号不仅让代码更清晰,也避免了优先级带来的意外。比如:

// 不推荐
assign result = a + b >> 1;

// 推荐
assign result = (a + b) >> 1;  // 先加后移

我曾经在一个项目里,因为少写了一个括号,导致加法器和移位器的顺序搞反了,仿真没问题,但综合出来的结果完全不对。查了两天才找到原因,从那以后,我写表达式必加括号。

4.8 知识体系总览

下面这张图,把今天讲的运算符体系串起来了。你可以把它当作一个速查表。

Verilog 运算符体系 算术运算符 + 加法 - 减法 * 乘法(用DSP) / % 慎用 逻辑运算符 && 逻辑与 || 逻辑或 ! 逻辑非 结果:0或1 位运算符 & 按位与 | 按位或 ^ 按位异或 ~ 按位取反 关系运算符 > < >= <= == != 结果:0或1 移位运算符 << 左移(×2) >> 右移(÷2) 资源消耗极低 条件运算符 ? : 三目运算符 综合为MUX 嵌套不超过3层 核心原则:多用括号明确优先级,能用移位不用乘除,注意位宽扩展

好了,运算符这块就讲到这里。记住一句话:写Verilog不是写C语言,你写的每一行代码,最终都会变成硬件电路。所以,多想想你写的表达式会综合成什么,而不是只关心仿真结果对不对。

下一节,我们会聊赋值语句和块语句,那是真正开始“写逻辑”的地方。到时候见。


公众号:蓝海资料掘金营,微信deep3321