第二讲:Verilog初体验——模块的基本结构与最简单的与门设计

各位同学,欢迎来到第二讲。今天我们要亲手写出第一段真正的Verilog代码。

说实话,我当年学Verilog时,最困惑的就是「模块」这个概念。明明就是几行代码,为什么叫模块?后来做项目多了才明白——模块就是硬件设计的「积木块」。你搭一个复杂系统,其实就是把这些积木块拼起来。

2.1 模块(module)长什么样?

先看一个最简单的例子。这是一个两输入与门的Verilog描述:

module and_gate (
    input  wire a,    // 输入端口a
    input  wire b,    // 输入端口b
    output wire y     // 输出端口y
);

    assign y = a & b;  // 与运算

endmodule

嗯,就这么几行。但每一行都有讲究。

模块的基本骨架

  • module 关键字 + 模块名(比如 and_gate
  • 端口列表(写在括号里)
  • 功能描述(中间的部分)
  • endmodule 结尾

我个人习惯把模块名写得有意义一点。比如 and_gate 一看就知道是「与门」。千万别写 module m1 这种名字,三个月后你自己都看不懂。

2.2 端口定义:input、output、inout

端口就是模块和外界通信的「接口」。就像芯片的引脚一样。

端口方向 关键字 说明
输入 input 数据从外部流入模块
输出 output 数据从模块流出到外部
双向 inout 既可以输入也可以输出(初学者先别碰)
注意: 端口定义时,inputoutput 后面要跟数据类型。最常用的是 wire(线网类型)。如果你不写,默认也是 wire。但我建议你每次都写清楚,别偷懒。

为什么会这样?因为Verilog里还有另一种数据类型叫 reg(寄存器类型),它俩用法不同。我们后面会专门讲。现在你记住:模块端口默认用 wire 就行。

2.3 数据类型:wire 和 reg 的初步认识

说白了,wire 就是一根导线。它只能被连续赋值(assign)驱动。而 reg 可以存储值,用在 always 块里。

对于这个与门例子,输入 a、b 和输出 y 都是 wire。因为 assign 语句只能驱动 wire 类型。

小技巧: 如果你不确定用 wire 还是 reg,记住这个口诀——
「assign 左边是 wire,always 左边是 reg」。

2.4 最简单的与门设计——逐行拆解

我们再回头看看那段代码:

module and_gate (
    input  wire a,
    input  wire b,
    output wire y
);

    assign y = a & b;

endmodule

第一行module and_gate ( —— 声明模块,名字叫 and_gate。

第二到四行:端口定义。三个端口,都是 wire 类型。

第六行assign y = a & b; —— 这是核心。assign 是连续赋值语句,意思是「y 的值永远等于 a 与 b 的结果」。只要 a 或 b 变了,y 立刻跟着变。

第八行endmodule —— 模块结束。

我在项目中遇到过有人把 & 写成 &&。前者是按位与(硬件用),后者是逻辑与(软件用)。在Verilog里,& 才是你要的与门。写错了仿真能过,但综合出来的电路可能完全不对。

2.5 仿真验证——怎么知道它对不对?

光写代码不行,你得验证。最简单的办法是写一个 testbench(测试平台)。

module tb_and_gate;

    reg  a;
    reg  b;
    wire y;

    // 实例化被测试的模块
    and_gate u_and_gate (
        .a(a),
        .b(b),
        .y(y)
    );

    // 产生测试激励
    initial begin
        a = 0; b = 0; #10;
        a = 0; b = 1; #10;
        a = 1; b = 0; #10;
        a = 1; b = 1; #10;
        $finish;
    end

endmodule

注意看:testbench 里没有端口!因为它是顶层,不需要和外界通信。我们用 reg 来驱动输入,用 wire 来观察输出。

我曾经犯过一个低级错误:在 testbench 里把输入也定义成 wire,结果仿真一直出 X(未知态)。折腾了半天才发现——wire 不能被 initial 块赋值啊!

2.6 避坑指南——初学者最容易踩的坑

  • 模块名不要和关键字冲突:比如别叫 moduleassignalways。Verilog 会报错。
  • 端口列表的逗号别漏:多个端口之间用逗号分隔,最后一个不要逗号。
  • endmodule 后面没有分号:这是 Verilog 里少数几个不加分号的地方。
  • assign 语句只能用于 wire 类型:别想着 assign 一个 reg 变量,综合工具会给你脸色看。
核心总结:
一个Verilog模块 = 模块声明 + 端口定义 + 功能描述。
与门设计 = assign y = a & b; 就这么简单。
但简单背后,是 wire/reg 的区别、assign 的用法、模块实例化的概念。这些基础打牢了,后面学什么都快。

好了,这一讲就到这里。下一讲我们会聊聊「如何用 always 块实现组合逻辑」,那才是真正开始玩硬件描述语言的时候。

记住:写Verilog不是写软件,你是在描述硬件。每一行代码,最终都会变成芯片上实实在在的门电路。