4. 基本逻辑门与数据流建模:与门、或门、非门、异或门、assign语句、连续赋值
各位同学,今天我们来聊聊数字电路最基础的东西——基本逻辑门。你可能会觉得,与门、或门、非门,这谁不知道啊?但说实话,我在项目中见过不少老手,写Verilog时把这几个门用错,导致仿真和综合结果对不上。嗯,咱们今天就把这些基础夯实了。
4.1 基本逻辑门:数字世界的原子
逻辑门,说白了就是数字电路里的"原子"。你想想看,再复杂的CPU、GPU,拆到最底层,也就是这些门的组合。我个人习惯把逻辑门分成三类:基本门(与、或、非)、复合门(与非、或非、异或、同或),以及三态门。今天咱们先搞定前两类。
4.1.1 与门(AND)
与门的规则很简单:所有输入为1,输出才为1。我在项目中遇到过一个问题,一个同事把与门当成了"只要有一个1就输出1",结果整个状态机跑飞了。记住,与门是"全1出1"。
| a | b | y = a & b |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
4.1.2 或门(OR)
或门是"有1出1"。这个好理解,但要注意一点:在组合逻辑中,如果或门的输入信号有毛刺,输出也会跟着抖。我曾经在调试一个SPI接口时,就是因为或门输入没做同步处理,导致片选信号误触发。
| a | b | y = a | b |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
4.1.3 非门(NOT)
非门就是取反,输入0输出1,输入1输出0。这个太简单了?别急,我告诉你一个坑:在Lattice的FPGA中,非门如果单独使用,综合器可能会把它优化掉,合并到前一级逻辑中。所以写代码时,别指望综合后还能看到独立的非门。
| a | y = ~a |
|---|---|
| 0 | 1 |
| 1 | 0 |
4.1.4 异或门(XOR)
异或门是"相同出0,不同出1"。这个门在数据校验、加法器里用得特别多。我记得有一次做CRC校验,用异或门级联实现多项式除法,仿真没问题,但上板后时序总是不满足。后来发现是异或门链太长,组合逻辑延迟太大了。所以,异或门虽然好用,但别串太多级。
| a | b | y = a ^ b |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
4.2 数据流建模:用assign描述组合逻辑
在Verilog中,描述组合逻辑有三种方式:数据流建模(assign)、行为级建模(always)、结构化建模(门级实例化)。今天咱们重点讲数据流建模,也就是用assign语句。
assign语句,说白了就是"连续赋值"。只要等号右边的信号发生变化,左边的信号就会立即更新。这跟实际硬件的行为是一致的——组合逻辑的输出,随输入变化而变化。
核心语法:
assign wire_name = expression;
注意:assign只能赋值给wire类型,不能赋值给reg类型。
4.2.1 用assign实现基本逻辑门
咱们直接上代码,看看怎么用assign描述与、或、非、异或门。
module basic_gates (
input wire a,
input wire b,
output wire y_and,
output wire y_or,
output wire y_not,
output wire y_xor
);
// 与门
assign y_and = a & b;
// 或门
assign y_or = a | b;
// 非门
assign y_not = ~a;
// 异或门
assign y_xor = a ^ b;
endmodule
这段代码很简单,对吧?但我要提醒你一点:在Lattice的Diamond或Radiant软件中,综合器对位宽很敏感。如果你写assign y = a & b;,而a和b是1位,那没问题。但如果a是4位,b是4位,y也是4位,那就是按位与。千万别把按位与(&)和逻辑与(&&)搞混了。逻辑与的结果是1位布尔值,按位与的结果是逐位运算。
避坑指南:
我曾经在写一个地址译码器时,把assign sel = (addr & mask);写成了assign sel = (addr && mask);。结果仿真时地址译码全乱了,查了半天才发现是逻辑与和按位与的区别。各位同学,写代码时一定要看清楚运算符。
4.2.2 连续赋值的特性
连续赋值有几个特性,我建议你记住:
- 持续性:只要输入变化,输出立即重新计算。没有时钟,没有边沿触发。
- 并行性:多个assign语句是并行执行的,跟书写顺序无关。
- 组合性:assign描述的是纯组合逻辑,不包含任何存储元件。
你想想看,如果多个assign语句对同一个wire赋值,会发生什么?嗯,综合器会报错——多驱动冲突。所以每个wire只能被一个assign驱动。
4.2.3 用assign实现更复杂的组合逻辑
基本门学会了,咱们来点实用的。比如实现一个4选1多路选择器:
module mux4to1 (
input wire [1:0] sel,
input wire [3:0] data_in,
output wire mux_out
);
assign mux_out = (sel == 2'b00) ? data_in[0] :
(sel == 2'b01) ? data_in[1] :
(sel == 2'b10) ? data_in[2] :
data_in[3];
endmodule
这里用了条件运算符(?:),它本质上也是一个组合逻辑。综合后,Lattice的FPGA会用LUT(查找表)来实现这个MUX。我个人习惯在写这种选择逻辑时,尽量用case语句(在always块中)而不是嵌套的?:,因为可读性更好。但如果你只想用assign,?:也是可以的。
4.3 知识体系:基本逻辑门与数据流建模
下面这张图,是我自己总结的本章知识体系。你可以把它当作一个思维导图来看。
4.4 实战经验:在Lattice FPGA中验证
理论讲完了,咱们聊聊实战。在Lattice的FPGA中验证基本逻辑门,我建议你按以下步骤来:
- 写代码:用assign描述好你的逻辑门。
- 写testbench:遍历所有输入组合,检查输出是否正确。
- 功能仿真:用ModelSim或Active-HDL跑仿真,看波形。
- 综合:在Diamond或Radiant中综合,查看RTL视图,确认综合结果。
- 布局布线:查看资源利用率,确认没有意外优化。
小技巧:
在Lattice Diamond中,你可以打开"Technology Map Viewer"查看综合后的LUT实现。比如一个2输入与门,综合后就是一个LUT4(4输入查找表)中的一种配置。如果你看到综合结果用了多个LUT,那说明你的代码可能写复杂了。
嗯,基本逻辑门和数据流建模就讲到这里。记住,assign是描述组合逻辑最直接的方式,但要注意位宽、运算符选择,以及多驱动问题。下一章咱们会讲行为级建模,到时候用always块描述组合逻辑,又是另一种思路了。
公众号:蓝海资料掘金营,微信deep3321