使用 Icarus Verilog 开发

在学校课程中,电子钟、CPU 等数字系统的设计需要使用 Verilog 编写。Quartus II 虽然是一款功能强大的 Verilog IDE,但是不像 VS Code 这样可以接入各种插件,对于像笔者这样的初学者使用不是很友好。(个人感觉 Quartus II 完全就不是现代开发工具……)

然后发现,计算机组成原理课设的资料里给了 Icarus Verilog 这个工具,看起来挺好用的。

Icarus Verilog(通常简称为iverilog)是一款基于 C++ 开发的轻量免费的 Verilog 编译器。可以将 Verilog 编译成多种目标语言,并且可以配合 GTKWave 实现波形仿真。使用方法和命令格式与 gcc 很像,需要输入命令行来使用该工具。有了 iverilog,我们就可以在 VS Code 中编写 Verilog,这样就可以接入其他各种便捷的辅助插件了!

基础使用方法

该部分讲解如何编译源代码以及调用 GTKWave 查看波形。

Icarus Verilog 共包含两个工具:iverilogvvp. 在其安装文件中还包含一个波形查看器: gtkwave.

iverilog 命令可以编译 .v/.sv 等源文件,生成输出文件。testbench 等包含顶层激励的仿真设计经 iverilog 编译出的输出文件可以作为仿真的输入。vvp 命令用来执行编译后的仿真程序,通过在 testbench 里编写 $dumpfile $dumpvars 转储语句可以生成 .vcd 波形文件。gtkwave 命令用来查看 .vcd 文件存储的波形。

大概逻辑是这样:

.v 文件 —— iverilog ——> 输出文件 —— vvp ——> .vcd 文件 —— gtkwave ——> 查看仿真波形

编译源代码

编译源代码分为两种情况:1、编译的文件没有引用其他文件编写的模块,即单文件编译;2、编写的文件引用了其他文件编写的模块,即多文件编译。接下来分别说明两种情况的编译方法。

编译使用的是 iverilog 命令,该命令结构如下:

iverilog <参数列表> <文件名>

参数列表里最常用的是 -o,用来指定输出文件的名称。

单文件编译

如果一个 Verilog 文件内部已经包含了完整的模块定义,没有调用其他文件中的模块,那么直接编译这一个文件即可。

下面给出一个最简单的示例,其中模块定义和测试代码都放在同一个文件中。

文件夹

module and_gate(
    input a,
    input b,
    output y
);
    assign y = a & b;
endmodule

module and_gate_tb;
    reg a;
    reg b;
    wire y;

    and_gate uut(
        .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

假设上述代码保存在 and_gate_tb.v 中,那么可以使用下面的命令进行编译:

iverilog -o and_gate_tb.out and_gate_tb.v

终端

其中 -o 用于指定输出文件名,and_gate_tb.out 是编译后生成的仿真文件。

文件夹

多文件编译

当一个文件中使用了其他文件定义的模块时,编译时不能只传入当前文件,而应将所有相关源文件同时加入命令。

例如,将与门模块和测试代码拆分成两个文件。

文件夹

and_gate.v

module and_gate(
    input a,
    input b,
    output y
);
    assign y = a & b;
endmodule

and_gate_tb.v

module and_gate_tb;
    reg a;
    reg b;
    wire y;

    and_gate uut(
        .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

由于 and_gate_tb.v 中实例化了 and_gate 模块,因此编译时需要把两个文件都写上:

iverilog -o and_gate_tb.out and_gate_tb.v and_gate.v

终端

文件夹

当工程包含多个源文件时,也可以继续按照这种方式把相关文件依次追加到命令后面。若遗漏了某个被引用模块所在的文件,编译时通常会报出模块未定义之类的错误。

执行仿真

编译完成后,就可以使用 vvp 来执行刚刚生成的仿真文件:

vvp and_gate_tb.out

这条命令会按 testbench 中定义的激励顺序运行整个仿真流程。若 testbench 中包含 $display 等输出语句,终端里会直接打印对应结果。

例如我在 and_gate_tb.v 里添加了一些 $display 语句:

module and_gate_tb;
    reg a;
    reg b;
    wire y;

    and_gate uut(
        .a(a),
        .b(b),
        .y(y)
    );

    initial begin
        $display("start sim...");               // 提示仿真开始
        a = 0; b = 0;
        #10 a = 0; b = 1;
        $display("a=%d, b=%d, y=%d", a, b, y);  // 打印 a b y
        #10 a = 1; b = 0;
        $display("a=%d, b=%d, y=%d", a, b, y);  // 打印 a b y
        #10 a = 1; b = 1;
        $display("a=%d, b=%d, y=%d", a, b, y);  // 打印 a b y
        #10 $display("finish sim...");          // 提示仿真结束
        $finish;
    end
endmodule

如果你不知道 $display 怎么用,可以把 $display 跟 C 语言的 printf 函数类比一下,它们的参数结构都是一样的。$display(<format>, <var1>, <var2>, ...) 这样的形式。

输出结果:

终端

如果希望进一步查看波形,还需要在 testbench 中加入波形转储语句。例如:

initial begin
    $dumpfile("and_gate_tb.vcd");
    $dumpvars(0, and_gate_tb);
end

其中,$dumpfile 用于指定生成的波形文件名,$dumpvars 用于指定需要记录的信号范围。加入这段代码后,再次执行:

vvp and_gate_tb.out

文件夹

仿真结束后,当前目录下会生成 and_gate_tb.vcd 文件。接着可以使用 GTKWave 打开它:

gtkwave and_gate_tb.vcd

这样就可以在图形界面中查看各个信号随时间变化的波形。若执行 vvp 后没有生成 .vcd 文件,通常需要优先检查 testbench 中是否正确写入了 $dumpfile$dumpvars

查看波形

执行 gtkwave 后会进入如下界面,依次点击模块、选择波形、添加波形,即可在右侧查看到选择的波形。

查看波形

至此 iverilog 的基础操作就完成了。学会这些操作就足够应付作业了。

$monitor 的用法

$display$monitor 都是用来打印日志的系统任务。$monitor 顾名思义,在调用后监视其绑定的信号,一旦这些信号发生变化,$monitor 就会打印一次日志。这比 $display 要方便很多。

p.s. 直到数字系统设计期末备考的时候我才知道有 $monitor 这个功能,于是然后就把这段加上了

简单写个使用示例:(Gemini 生成)

module tb_monitor;
    reg clk;
    reg [3:0] counter;

    initial begin
        clk = 0;
        counter = 0;
        
        // 启动监视器。只要 clk 或 counter 发生变化,这行就会自动打印
        $monitor("Time = %0t | clk = %b | counter = %d", $time, clk, counter);
        
        // 仿真运行一段时间后停止
        #50 $finish;
    end

    // 产生时钟
    always #5 clk = ~clk;

    // 计数器逻辑
    always @(posedge clk) begin
        counter <= counter + 1;
    end
endmodule

全局只有一个 $monitor 生效,如果代码里定义了多个,只有最后一个 $monitor 会生效。

Icarus Verilog 下载方式

打开iVerilog官网下载:http://bleyer.org/icarus/

下载界面

选择自己需要的版本下载。

启动安装程序,一直点“下一步”即可。注意勾上环境变量。

勾选环境变量

最后输入以下命令测试安装是否成功。

where iverilog
where vvp
where gtkwave

测试安装

若打印出向上面那样的路径名则说明安装成功。

附录:格式控制符

  1. 格式控制符 在使用 $display 和 $monitor 时,常使用以下格式符来规定输出的进制或类型:
    • %b%B:二进制 (Binary)
    • %d%D:十进制 (Decimal)
    • %h%H:十六进制 (Hexadecimal)
    • %o%O:八进制 (Octal)
    • %t%T:仿真时间 (Time)
    • %s%S:字符串 (String)
    • %c%C:ASCII字符 (Character)