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

看到没?moduleendmodule 把整个电路包起来。端口就是芯片的引脚,input 是输入,output 是输出。

模块化设计的好处,我总结了几点:

  • 可复用:写好的模块,下次直接拿来用
  • 可调试:出问题了,定位到具体模块,不用翻遍整个代码
  • 可分工:团队里每人负责几个模块,最后拼起来
小技巧:模块名和文件名保持一致。这是行业惯例,别问为什么,照做就行。

3.2 数据类型——wire 和 reg

这是新手最容易搞混的地方。我刚开始学的时候也迷糊了好一阵子。

wire 就是一根导线。它不保存值,只是把某处的信号传递到另一处。你给它赋值,它立马反映出来。

reg 是一个寄存器。它能保存值。注意,这里的“寄存器”不是芯片里的物理寄存器,而是Verilog里的一种变量类型。它可以在always块里被赋值,并且保持住这个值,直到下一次被赋值。

我画个表格,你一看就明白:

特性 wire reg
本质 连线 变量(可存储)
赋值方式 assign 语句 always 块内赋值
默认值 高阻态 z 未知态 x
物理实现 组合逻辑 组合逻辑或时序逻辑
注意:在always块里被赋值的变量,必须声明为reg类型。但reg不一定会被综合成寄存器。如果always块是组合逻辑描述(比如 always @(*)),reg综合出来还是导线。这个坑我踩过,当时仿真对了,综合出来电路不对,查了半天。

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模块内部结构 module (端口列表) input wire output wire assign 连续赋值 (组合逻辑, wire类型) always 过程赋值 (组合/时序, reg类型) 内部 reg 变量 最终综合为:与门、或门、非门、触发器...

嗯,这张图你看懂了,Verilog的骨架就抓住了。模块是容器,assign和always是两种描述方式,wire和reg是数据类型。它们组合起来,就能描述任何数字电路。

我的建议:刚开始学,先别急着写复杂逻辑。把与门、或门、非门、D触发器这几个基本单元写熟。就像学写字,先练好横竖撇捺。

好了,这一章就到这里。下一章咱们继续深入,讲讲如何用Verilog描述更复杂的组合逻辑和时序逻辑。到时候我会分享一些我在项目里踩过的坑,保证让你少走弯路。


专注资料整理