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里几乎不消耗资源,因为布线就能实现。所以能用移位的地方,尽量别用乘除法。
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不是写C语言,你写的每一行代码,最终都会变成硬件电路。所以,多想想你写的表达式会综合成什么,而不是只关心仿真结果对不对。
下一节,我们会聊赋值语句和块语句,那是真正开始“写逻辑”的地方。到时候见。
公众号:蓝海资料掘金营,微信deep3321