3、硬件描述语言Verilog入门(上):模块化设计、数据类型(wire、reg)、always块与assign语句、基础门电路建模
好,咱们正式开始接触Verilog。说实话,很多新手一上来就被各种语法吓住了。其实没那么复杂。你想想看,Verilog说白了就是描述硬件的一种语言。它不是C语言,不是软件,是描述“电线”和“触发器”的。
我个人习惯,学Verilog先忘掉“编程”这两个字。你是在画电路,不是在写程序。这个观念转过来,后面就顺了。
3.1 模块化设计——搭积木的思路
一个复杂的CPU,不可能在一个文件里写完。谁那么干谁就是给自己挖坑。我见过一个同事,把所有代码塞进一个模块,两万行。后来他自己都看不懂了。
正确的做法是:模块化。每个模块只干一件事,干好。
一个Verilog模块长这样:
module and_gate (
input wire a, // 输入端口
input wire b, // 输入端口
output wire y // 输出端口
);
assign y = a & b;
endmodule
看到没?module 和 endmodule 把整个电路包起来。端口就是芯片的引脚,input 是输入,output 是输出。
模块化设计的好处,我总结了几点:
- 可复用:写好的模块,下次直接拿来用
- 可调试:出问题了,定位到具体模块,不用翻遍整个代码
- 可分工:团队里每人负责几个模块,最后拼起来
3.2 数据类型——wire 和 reg
这是新手最容易搞混的地方。我刚开始学的时候也迷糊了好一阵子。
wire 就是一根导线。它不保存值,只是把某处的信号传递到另一处。你给它赋值,它立马反映出来。
reg 是一个寄存器。它能保存值。注意,这里的“寄存器”不是芯片里的物理寄存器,而是Verilog里的一种变量类型。它可以在always块里被赋值,并且保持住这个值,直到下一次被赋值。
我画个表格,你一看就明白:
| 特性 | wire | reg |
|---|---|---|
| 本质 | 连线 | 变量(可存储) |
| 赋值方式 | assign 语句 | always 块内赋值 |
| 默认值 | 高阻态 z | 未知态 x |
| 物理实现 | 组合逻辑 | 组合逻辑或时序逻辑 |
3.3 always块与assign语句
这两个是Verilog里最核心的两种赋值方式。我分别说说。
3.3.1 assign——连续赋值
assign 用于描述组合逻辑。它就像一根实实在在的导线,左边是输出,右边是输入表达式。只要右边变了,左边立刻跟着变。
assign y = a & b;
这行代码描述了一个与门。a和b变了,y马上变。没有延迟,没有时钟。
3.3.2 always——过程赋值
always 块更灵活。它可以描述组合逻辑,也可以描述时序逻辑。区别在于敏感列表。
// 组合逻辑:敏感列表里放所有输入
always @(*) begin
y = a & b;
end
// 时序逻辑:敏感列表里放时钟沿
always @(posedge clk) begin
q <= d;
end
注意看,组合逻辑我用 =(阻塞赋值),时序逻辑我用 <=(非阻塞赋值)。这个区别很重要。我曾经在项目里混用过,结果仿真波形一塌糊涂。后来养成了习惯:组合逻辑用 =,时序逻辑用 <=,绝不混用。
核心原则:
- assign 语句:描述组合逻辑,用 wire 类型
- always @(*) :描述组合逻辑,用 reg 类型,用 = 赋值
- always @(posedge clk) :描述时序逻辑,用 reg 类型,用 <= 赋值
3.4 基础门电路建模
咱们用Verilog把最基本的门电路实现一遍。这些是数字电路的砖瓦。
3.4.1 与门、或门、非门
module basic_gates (
input wire a, b,
output wire y_and,
output wire y_or,
output wire y_not
);
assign y_and = a & b; // 与门
assign y_or = a | b; // 或门
assign y_not = ~a; // 非门
endmodule
就这么简单。& 是与,| 是或,~ 是非。记住这三个符号,后面所有复杂逻辑都是它们的组合。
3.4.2 异或门
assign y_xor = a ^ b; // 异或门
异或用 ^ 表示。这个门在加法器里很常用。
3.4.3 多输入门
Verilog支持多输入。比如三输入与门:
assign y = a & b & c;
或者用位拼接:
assign y = &{a, b, c}; // 归约与
我个人更喜欢第一种写法,可读性更好。
3.5 知识体系总览
下面这张图,把本章的核心逻辑串起来了。你仔细看看,模块是外壳,里面是assign和always,它们操作wire和reg,最终实现门电路。
嗯,这张图你看懂了,Verilog的骨架就抓住了。模块是容器,assign和always是两种描述方式,wire和reg是数据类型。它们组合起来,就能描述任何数字电路。
好了,这一章就到这里。下一章咱们继续深入,讲讲如何用Verilog描述更复杂的组合逻辑和时序逻辑。到时候我会分享一些我在项目里踩过的坑,保证让你少走弯路。