3、第一个仿真程序:编写一个简单的Verilog门电路

好,咱们正式开始动手了。

前面两章聊了工具链和基本概念,说实话,光看不练容易犯困。这一章咱们直接写代码、编译、跑仿真、看波形——走完一个完整的流程。你想想看,当你在GTKWave里看到自己写的门电路波形跳起来的那一刻,那种感觉还是挺爽的。

3.1 我们要做什么?

这一章的目标很简单:

  • 写一个Verilog描述的与门(AND gate)
  • 写一个测试平台(testbench)来驱动它
  • 用iverilog编译这两个文件
  • 生成VCD波形文件
  • 用GTKWave打开波形,验证功能

说白了,就是让一个与门从代码变成看得见的波形。我当年第一次跑通这个流程时,盯着波形看了好几分钟——原来数字电路真的是这样工作的。

核心知识点:Verilog模块结构、testbench写法、iverilog编译命令、VCD文件生成、GTKWave基本操作。

3.2 编写与门模块

先写一个最简单的与门。新建一个文件,命名为 and_gate.v:

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

    assign y = a & b;

endmodule

嗯,这里要注意:assign 是连续赋值语句,表示 y 的值随着 a 和 b 的变化实时更新。在项目中我见过有人把 & 写成 &&,前者是按位与,后者是逻辑与——对于单bit信号结果一样,但多bit时就完全不是一回事了。

3.3 编写testbench

光有设计模块还不够,得有个东西给它输入信号、观察输出。这就是testbench的作用。

新建 tb_and_gate.v:

`timescale 1ns / 1ps

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

    // 生成VCD波形文件
    initial begin
        $dumpfile("wave.vcd");
        $dumpvars(0, tb_and_gate);
    end

endmodule

这里有几个关键点:

  • `timescale 1ns / 1ps:定义时间单位是1ns,精度是1ps。我习惯这么写,精度够用又不浪费仿真时间。
  • reg 和 wire:testbench里,驱动输入用reg,观察输出用wire。这是Verilog的规则,别搞反了。
  • #10:延迟10个时间单位,也就是10ns。
  • $dumpfile 和 $dumpvars:这是生成VCD文件的关键。我曾经忘记加这两行,折腾了半天才发现没波形——嗯,这种低级错误犯过一次就不会再犯了。

小技巧:$dumpvars(0, tb_and_gate) 中的0表示dump所有层次的信号。如果只想看某个模块的信号,可以写成 $dumpvars(1, tb_and_gate.u_and_gate),只dump一层。

3.4 编译与仿真

打开终端,进入文件所在目录,执行:

iverilog -o tb_and_gate.vvp and_gate.v tb_and_gate.v
vvp tb_and_gate.vvp

第一行命令用iverilog编译两个.v文件,输出一个名为 tb_and_gate.vvp 的可执行文件。第二行用vvp运行这个文件,生成 wave.vcd。

如果一切顺利,你会看到终端没有任何报错,目录下多了一个 wave.vcd 文件。这就是我们要的波形文件。

注意:编译时文件顺序有讲究。建议把testbench放在最后,因为iverilog是按顺序解析的,testbench里引用了and_gate模块,所以and_gate要先被编译。

3.5 用GTKWave查看波形

执行:

gtkwave wave.vcd

GTKWave的界面会打开。左侧是信号列表,右侧是波形显示区。操作步骤:

  1. 在左侧找到 tb_and_gate,展开它
  2. 找到 u_and_gate,再展开
  3. 选中 a、b、y 三个信号
  4. 点击左上角的 Insert 按钮,或者直接拖拽到右侧波形区

你会看到这样的波形:

  • 0ns-10ns:a=0, b=0, y=0
  • 10ns-20ns:a=0, b=1, y=0
  • 20ns-30ns:a=1, b=0, y=0
  • 30ns-40ns:a=1, b=1, y=1

与门的真值表验证通过。y只有在a和b同时为1时才为1,其他情况都是0。

验证结论:与门功能正确,仿真环境搭建成功。

3.6 本章知识体系

下面这张图展示了我们这一章的核心逻辑:

第一个仿真程序流程 编写设计模块 and_gate.v 编写testbench tb_and_gate.v iverilog编译 生成 .vvp 文件 vvp仿真运行 生成 wave.vcd GTKWave查看波形 验证功能正确性 验证通过 与门功能正确 输入文件 → 编译 → 仿真 → 波形查看 → 验证 核心命令:iverilog → vvp → gtkwave

3.7 避坑指南

这一章虽然简单,但有几个坑我当年都踩过:

  • 文件找不到:确保终端当前目录和.v文件在同一目录下,或者用路径。
  • VCD文件没生成:检查testbench里有没有写 $dumpfile 和 $dumpvars。我曾经漏写过,仿真跑完了才发现没波形。
  • 波形显示不对:检查信号名是否写错,比如把 y 写成 Y。Verilog是大小写敏感的。
  • GTKWave打不开:确认VCD文件确实生成了,用 ls -l wave.vcd 看看文件大小,如果为0说明仿真没跑成功。

我的习惯:每次写完testbench,先检查 $dumpfile 和 $dumpvars 这两行有没有写对。这是生成波形的关键,少了它们等于白跑。

3.8 小结

这一章我们走完了从代码到波形的完整流程。你亲手写了一个与门,写了testbench,用iverilog编译,用vvp仿真,最后在GTKWave里看到了波形。说实话,这个流程在后面的29章里会反复出现,所以现在打好基础很重要。

下一章我们会聊更复杂的组合逻辑电路,但核心流程是一样的。嗯,先把这个与门跑通再说。


可可精品资料库