第二讲: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 |
既可以输入也可以输出(初学者先别碰) |
input 和 output 后面要跟数据类型。最常用的是 wire(线网类型)。如果你不写,默认也是 wire。但我建议你每次都写清楚,别偷懒。
为什么会这样?因为Verilog里还有另一种数据类型叫 reg(寄存器类型),它俩用法不同。我们后面会专门讲。现在你记住:模块端口默认用 wire 就行。
2.3 数据类型:wire 和 reg 的初步认识
说白了,wire 就是一根导线。它只能被连续赋值(assign)驱动。而 reg 可以存储值,用在 always 块里。
对于这个与门例子,输入 a、b 和输出 y 都是 wire。因为 assign 语句只能驱动 wire 类型。
「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 避坑指南——初学者最容易踩的坑
- 模块名不要和关键字冲突:比如别叫
module、assign、always。Verilog 会报错。 - 端口列表的逗号别漏:多个端口之间用逗号分隔,最后一个不要逗号。
- endmodule 后面没有分号:这是 Verilog 里少数几个不加分号的地方。
- assign 语句只能用于 wire 类型:别想着 assign 一个 reg 变量,综合工具会给你脸色看。
一个Verilog模块 = 模块声明 + 端口定义 + 功能描述。
与门设计 = assign y = a & b; 就这么简单。
但简单背后,是 wire/reg 的区别、assign 的用法、模块实例化的概念。这些基础打牢了,后面学什么都快。
好了,这一讲就到这里。下一讲我们会聊聊「如何用 always 块实现组合逻辑」,那才是真正开始玩硬件描述语言的时候。
记住:写Verilog不是写软件,你是在描述硬件。每一行代码,最终都会变成芯片上实实在在的门电路。