4. 基本逻辑门与数据流建模:与门、或门、非门、异或门、assign语句、连续赋值

各位同学,今天我们来聊聊数字电路最基础的东西——基本逻辑门。你可能会觉得,与门、或门、非门,这谁不知道啊?但说实话,我在项目中见过不少老手,写Verilog时把这几个门用错,导致仿真和综合结果对不上。嗯,咱们今天就把这些基础夯实了。

4.1 基本逻辑门:数字世界的原子

逻辑门,说白了就是数字电路里的"原子"。你想想看,再复杂的CPU、GPU,拆到最底层,也就是这些门的组合。我个人习惯把逻辑门分成三类:基本门(与、或、非)、复合门(与非、或非、异或、同或),以及三态门。今天咱们先搞定前两类。

4.1.1 与门(AND)

与门的规则很简单:所有输入为1,输出才为1。我在项目中遇到过一个问题,一个同事把与门当成了"只要有一个1就输出1",结果整个状态机跑飞了。记住,与门是"全1出1"。

aby = a & b
000
010
100
111

4.1.2 或门(OR)

或门是"有1出1"。这个好理解,但要注意一点:在组合逻辑中,如果或门的输入信号有毛刺,输出也会跟着抖。我曾经在调试一个SPI接口时,就是因为或门输入没做同步处理,导致片选信号误触发。

aby = a | b
000
011
101
111

4.1.3 非门(NOT)

非门就是取反,输入0输出1,输入1输出0。这个太简单了?别急,我告诉你一个坑:在Lattice的FPGA中,非门如果单独使用,综合器可能会把它优化掉,合并到前一级逻辑中。所以写代码时,别指望综合后还能看到独立的非门。

ay = ~a
01
10

4.1.4 异或门(XOR)

异或门是"相同出0,不同出1"。这个门在数据校验、加法器里用得特别多。我记得有一次做CRC校验,用异或门级联实现多项式除法,仿真没问题,但上板后时序总是不满足。后来发现是异或门链太长,组合逻辑延迟太大了。所以,异或门虽然好用,但别串太多级。

aby = a ^ b
000
011
101
110

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 知识体系:基本逻辑门与数据流建模

下面这张图,是我自己总结的本章知识体系。你可以把它当作一个思维导图来看。

基本逻辑门与数据流建模 基本逻辑门 与门 (AND) 或门 (OR) 非门 (NOT) 异或门 (XOR) 数据流建模 assign 语句 连续赋值特性 条件运算符 (?:) Lattice FPGA 综合验证 LUT 实现基本门 时序分析与优化

4.4 实战经验:在Lattice FPGA中验证

理论讲完了,咱们聊聊实战。在Lattice的FPGA中验证基本逻辑门,我建议你按以下步骤来:

  1. 写代码:用assign描述好你的逻辑门。
  2. 写testbench:遍历所有输入组合,检查输出是否正确。
  3. 功能仿真:用ModelSim或Active-HDL跑仿真,看波形。
  4. 综合:在Diamond或Radiant中综合,查看RTL视图,确认综合结果。
  5. 布局布线:查看资源利用率,确认没有意外优化。

小技巧:

在Lattice Diamond中,你可以打开"Technology Map Viewer"查看综合后的LUT实现。比如一个2输入与门,综合后就是一个LUT4(4输入查找表)中的一种配置。如果你看到综合结果用了多个LUT,那说明你的代码可能写复杂了。

嗯,基本逻辑门和数据流建模就讲到这里。记住,assign是描述组合逻辑最直接的方式,但要注意位宽、运算符选择,以及多驱动问题。下一章咱们会讲行为级建模,到时候用always块描述组合逻辑,又是另一种思路了。


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