第三章:组合逻辑设计——assign语句、always块、逻辑门、加法器、多路选择器
组合逻辑,说白了就是电路里最直来直去的那部分。输入一变,输出立马跟着变,不带半点延迟(当然,实际走线是有延时的,但那是后话)。我刚开始学FPGA时,总觉得组合逻辑太简单,不就是几个与或非门嘛。后来做项目才发现,真正把组合逻辑用好、写清楚,其实挺考验功力的。
3.1 assign语句——最直接的连线方式
assign语句,你可以把它理解成一根导线。左边是输出,右边是输入表达式。只要右边变了,左边立刻更新。
assign led = button; // 按键按下,灯就亮
assign sum = a + b; // 加法器
assign flag = (cnt > 100) ? 1'b1 : 1'b0; // 条件判断
我个人习惯,简单的组合逻辑都用assign。它写起来快,读起来也清楚。但要注意,assign的左边必须是wire类型,不能是reg。这个坑我见过不少新手踩过。
3.2 always块——组合逻辑的另一种写法
很多人以为always块只能写时序逻辑,其实不然。只要把敏感列表写成@(*),它就是组合逻辑。
always @(*) begin
if (sel == 1'b0)
out = a;
else
out = b;
end
这段代码实现了一个2选1多路选择器。敏感列表里的*表示“所有输入信号”,也就是sel、a、b。只要它们中任何一个变了,always块就会重新执行一次。
3.3 基本逻辑门——与或非的Verilog写法
FPGA底层其实就是查找表(LUT),而LUT本质上就是一堆逻辑门。Verilog里写逻辑门很简单:
| 逻辑功能 | Verilog写法 | 说明 |
|---|---|---|
| 与门 | assign y = a & b; |
两个输入都为1时输出1 |
| 或门 | assign y = a | b; |
任一输入为1时输出1 |
| 非门 | assign y = ~a; |
取反 |
| 异或门 | assign y = a ^ b; |
两个输入不同时输出1 |
| 与非门 | assign y = ~(a & b); |
与门取反 |
你想想看,这些基本门电路组合起来,就能实现任意复杂的逻辑。FPGA的魅力就在于此——用有限的资源,搭出无限的可能。
3.4 加法器——从半加器到全加器
加法器是算术运算的基础。我们先从最简单的半加器说起。
半加器
半加器只考虑两个输入相加,不考虑进位输入。它的真值表很简单:
| a | b | sum | cout |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |
// 半加器
assign sum = a ^ b; // 和 = 异或
assign cout = a & b; // 进位 = 与
全加器
全加器多了一个进位输入cin,这样就能级联起来做多位加法了。
// 全加器
assign sum = a ^ b ^ cin;
assign cout = (a & b) | (a & cin) | (b & cin);
嗯,这里要注意。实际项目中,我们很少自己写全加器。直接用+运算符,综合工具会自动帮你优化成最优的加法器结构。但理解底层原理很重要——遇到高位宽加法时,你就知道为什么时序会紧张,该怎么加流水线了。
3.5 多路选择器——数据选择的艺术
多路选择器(MUX)在FPGA里无处不在。从寄存器堆的读端口,到状态机的跳转条件,到处都有它的身影。
实现MUX的方式有好几种:
- assign + 三目运算符:适合2选1
- always + case语句:适合多选1
- generate + 循环:适合参数化的MUX阵列
// 方式1:三目运算符(2选1)
assign out = sel ? b : a;
// 方式2:case语句(4选1)
always @(*) begin
case(sel)
2'b00: out = a;
2'b01: out = b;
2'b10: out = c;
2'b11: out = d;
default: out = 1'b0;
endcase
end
// 方式3:generate循环(参数化MUX)
generate
genvar i;
for(i = 0; i < WIDTH; i = i + 1) begin
assign out[i] = sel ? b[i] : a[i];
end
endgenerate
我个人偏爱用case语句写MUX,因为可读性好,而且综合出来的面积通常更小。但要注意,case语句必须写default分支,否则又会生成latch。这个坑我踩过不止一次。
3.6 知识体系总览
下面这张图,把本章的核心知识点串起来了。你可以把它当作一个快速索引:
这张图里,assign、always、逻辑门是三种基本描述方式。加法器和多路选择器是典型的组合逻辑应用。把它们吃透了,大部分组合逻辑设计你都能搞定。
3.7 避坑指南
最后,分享几个我实战中遇到的坑:
- latch的生成:我曾经在
always块里漏写了一个else分支,综合后多出十几个latch。排查时看RTL视图才发现。从那以后,我写组合逻辑always块一定会检查每个输出是否在所有分支都被赋值。 - 敏感列表不完整:早期我用
always @(a or b)这种写法,后来加了信号c却忘了更新敏感列表,导致仿真和综合结果不一致。现在一律用@(*),省心。 - 位宽不匹配:做加法时,如果
a和b是8位,sum是8位,进位就丢了。要么把sum定义成9位,要么单独引出进位信号。
嗯,组合逻辑就讲这么多。说白了,它就是一堆门电路搭起来的、没有记忆功能的逻辑。写代码时注意避免latch,敏感列表写完整,基本就不会出大问题。
可可精品资料库