Vengineerの戯言

人生は短いけど、長いです。人生を楽しみましょう!

Verilatorの中を調べる(その2)

@Vengineerの戯言 : Twitter
SystemVerilogの世界へようこそすべては、SystemC v0.9公開から始まった 

はじめに

昨日の「Verilatorの中を調べる(その1)」の続き。

Verilatorの最新版 v4.200 では、その前の v4.110 とちょこっと変わったようです。。。それは、後程。

準備

最新版の v4.200 をインストール。あたしは Windows 10 上の WSL2/Ubuntu 20.04 + Visual Studio を使っています。
インストール先は、/usr/local/verilator/v4.200 です。

SystemCを使うので、v2.3.3 をビルドし、/usr/local/systemc/2.3.3 にインストールしています。

とりあえず、-V オプションにて確認。環境変数 PATH に /usr/local/verilator/v4.200/bin を追加し、SYSTEMC_INCLUDE に /usr/local/systemc/2.3.3/inculde を設定しています。
下記のログから、VERILATOR_ROOT に /usr/local/verilator/v4.200/share/verilator、SYSTMEC_INCLUDE に /usr/local/systemc/2.3.3/inculde が設定されているのがわかります。

$ verilator -V
Verilator 4.200 2021-03-12 rev v4.200

Copyright 2003-2021 by Wilson Snyder.  Verilator is free software; you can
redistribute it and/or modify the Verilator internals under the terms of
either the GNU Lesser General Public License Version 3 or the Perl Artistic
License Version 2.0.

See https://verilator.org for documentation

Summary of configuration:
  Compiled in defaults if not in environment:
    SYSTEMC            =
    SYSTEMC_ARCH       =
    SYSTEMC_INCLUDE    =
    SYSTEMC_LIBDIR     =
    VERILATOR_ROOT     = /usr/local/verilator/v4.200/share/verilator
    SystemC system-wide = 1

Environment:
    MAKE               =
    PERL               =
    SYSTEMC            =
    SYSTEMC_ARCH       =
    SYSTEMC_INCLUDE    =  /usr/local/systemc/2.3.3/include
    SYSTEMC_LIBDIR     =
    VERILATOR_ROOT     =
    VERILATOR_BIN      =

Features (based on environment or compiled-in support):
    SystemC found      = 1

examples/make_hello_c

examples/make_hello_c を見てみます。

Verilog HDLコードは、top.v 、テストベンチ側は sim_main.cpp (C++コード)です。あとは、Makefile です。

make コマンドを実行してみましょう。

以下のように、最初に verilator -cc --exe --build -j top.v sim_main.cpp が実行されています。

$ cd examples/make_hello_c
$ ls
Makefile  sim_main.cpp  top.v
$ make
- Verilator hello-world simple example
-- VERILATE & BUILD --------
verilator -cc --exe --build -j top.v sim_main.cpp
make[1]: Entering directory '/mnt/c/Users/haray/home/verilator/verilator/examples/make_hello_c/obj_dir'
g++  -I.  -MMD -I/usr/local/verilator/v4.200/share/verilator/include -I/usr/local/verilator/v4.200/share/verilator/include/vltstd -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -DVM_TRACE_FST=0 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-sign-compare -Wno-uninitialized -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable -Wno-shadow      -std=gnu++14 -Os -c -o sim_main.o ../sim_main.cpp
g++  -I.  -MMD -I/usr/local/verilator/v4.200/share/verilator/include -I/usr/local/verilator/v4.200/share/verilator/include/vltstd -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -DVM_TRACE_FST=0 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-sign-compare -Wno-uninitialized -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable -Wno-shadow      -std=gnu++14 -Os -c -o verilated.o /usr/local/verilator/v4.200/share/verilator/include/verilated.cpp
/usr/bin/perl /usr/local/verilator/v4.200/share/verilator/bin/verilator_includer -DVL_INCLUDE_OPT=include Vtop.cpp Vtop__Slow.cpp Vtop__Syms.cpp > Vtop__ALL.cpp
g++  -I.  -MMD -I/usr/local/verilator/v4.200/share/verilator/include -I/usr/local/verilator/v4.200/share/verilator/include/vltstd -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -DVM_TRACE_FST=0 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-sign-compare -Wno-uninitialized -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable -Wno-shadow      -std=gnu++14 -Os -c -o Vtop__ALL.o Vtop__ALL.cpp
Archive ar -cr Vtop__ALL.a Vtop__ALL.o
g++    sim_main.o verilated.o Vtop__ALL.a      -o Vtop
make[1]: Leaving directory '/mnt/c/Users/haray/home/verilator/verilator/examples/make_hello_c/obj_dir'
-- RUN ---------------------
obj_dir/Vtop
Hello World!
- top.v:11: Verilog $finish
final
-- DONE --------------------
Note: Once this example is understood, see examples/make_tracing_c.
Note: Also see the EXAMPLE section in the verilator manpage/document.

cc オプションは、テストベンチ側に C++ を使う時に指定します。生成されるものが C++ コードになります。

exe オプションは、実行プログラムを生成します。

build オプションは、コードを生成して、実行プログラムを生成します。

ls コマンドにて生成されたコードを確認してみましょう。obj_dir ディレクトリが生成され、その下にいろいろなファイルが生成されています。

$ ls 
Makefile  obj_dir  sim_main.cpp  top.v
$ ls obj_dir
Vtop      Vtop.h   Vtop__ALL.a    Vtop__ALL.d  Vtop__Slow.cpp  Vtop__Syms.h  Vtop__verFiles.dat  sim_main.d  verilated.d
Vtop.cpp  Vtop.mk  Vtop__ALL.cpp  Vtop__ALL.o  Vtop__Syms.cpp  Vtop__ver.d   Vtop_classes.mk     sim_main.o  verilated.o  

obj_dir はデフォルト値で、-Mdir directory_name にて変更できます。

$ make clean
$ verilator --c --exe --build -j top.v sim_main.cpp -Mdir xxx
$ ls
Makefile  xxx  sim_main.cpp  top.v

build オプションを付けないで実行するとどうなるでしょうか?*.d や *.o および、Vtop_ALL.a や Vtop などのオブジェクトは生成されていません。--build オプションをしていすることでこれらオブジェクトを生成していることになります。

$ make clean
Makefile  sim_main.cpp  top.v
$ verilator --cc --exe top.v sim_main.cpp
$ ls obj_dir
Vtop.cpp  Vtop.h  Vtop.mk  Vtop__Slow.cpp  Vtop__Syms.cpp  Vtop__Syms.h  Vtop__ver.d  Vtop__verFiles.dat  Vtop_classes.mk

最初のログファイルを見てみると、obj_dir ディレクトリ内で、g++ コマンドにて生成されたC++コードをコンパイルしています。
コンパイルしているファイルは、../sim_main.cpp 、 /usr/local/verilator/v4.200/share/verilator/include/verilated.cpp、Vtop__ALL.cpp です。ただし、Vtop_ALL.cpp は、Vtop.cpp,
Vtop_Slow.cpp Vtop__Syms.cpp を リダイレクトしたファイルです。Vtop__ALL.o は、ar コマンドに Vtop_ALL.a にして、最後に、sim_main.o verilated.o Vtop_ALL.a から Vtop を生成しています。

make[1]: Entering directory '/mnt/c/Users/haray/home/verilator/verilator/examples/make_hello_c/obj_dir'
g++  -I.  -MMD -I/usr/local/verilator/v4.200/share/verilator/include -I/usr/local/verilator/v4.200/share/verilator/include/vltstd -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -DVM_TRACE_FST=0 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-sign-compare -Wno-uninitialized -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable -Wno-shadow      -std=gnu++14 -Os -c -o sim_main.o ../sim_main.cpp
g++  -I.  -MMD -I/usr/local/verilator/v4.200/share/verilator/include -I/usr/local/verilator/v4.200/share/verilator/include/vltstd -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -DVM_TRACE_FST=0 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-sign-compare -Wno-uninitialized -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable -Wno-shadow      -std=gnu++14 -Os -c -o verilated.o /usr/local/verilator/v4.200/share/verilator/include/verilated.cpp
/usr/bin/perl /usr/local/verilator/v4.200/share/verilator/bin/verilator_includer -DVL_INCLUDE_OPT=include Vtop.cpp Vtop__Slow.cpp Vtop__Syms.cpp > Vtop__ALL.cpp
g++  -I.  -MMD -I/usr/local/verilator/v4.200/share/verilator/include -I/usr/local/verilator/v4.200/share/verilator/include/vltstd -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -DVM_TRACE_FST=0 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-sign-compare -Wno-uninitialized -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable -Wno-shadow      -std=gnu++14 -Os -c -o Vtop__ALL.o Vtop__ALL.cpp
Archive ar -cr Vtop__ALL.a Vtop__ALL.o
g++    sim_main.o verilated.o Vtop__ALL.a      -o Vtop
make[1]: Leaving directory '/mnt/c/Users/haray/home/verilator/verilator/examples/make_hello_c/obj_dir'

生成された Vtop.h を見てみる

まずは、top.v の中を見てみます。initial 文内で、$display システムタスクと $finish システムタスクを実行しているだけです。

module top;
   initial begin
      $display("Hello World!");
      $finish;
   end
endmodule

生成された Vtop.h を見てみましょう。

// Verilated -*- C++ -*-
// DESCRIPTION: Verilator output: Primary design header
//
// This header should be included by all source files instantiating the design.
// The class here is then constructed to instantiate the design.
// See the Verilator manual for examples.

#ifndef VERILATED_VTOP_H_
#define VERILATED_VTOP_H_  // guard

#include "verilated_heavy.h"

//==========

class Vtop__Syms;

//----------

VL_MODULE(Vtop) {
  public:
    
    // INTERNAL VARIABLES
    // Internals; generally not touched by application code
    Vtop__Syms* __VlSymsp;  // Symbol table
    
    // CONSTRUCTORS
  private:
    VL_UNCOPYABLE(Vtop);  ///< Copying not allowed
  public:
    /// Construct the model; called by application code
    /// If contextp is null, then the model will use the default global context
    /// If name is "", then makes a wrapper with a
    /// single model invisible with respect to DPI scope names.
    Vtop(VerilatedContext* contextp, const char* name = "TOP");
    Vtop(const char* name = "TOP")
      : Vtop(nullptr, name) {}
    /// Destroy the model; called (often implicitly) by application code
    ~Vtop();
    
    // API METHODS
    /// Return current simulation context for this model.
    /// Used to get to e.g. simulation time via contextp()->time()
    VerilatedContext* contextp();
    /// Evaluate the model.  Application must call when inputs change.
    void eval() { eval_step(); }
    /// Evaluate when calling multiple units/models per time step.
    void eval_step();
    /// Evaluate at end of a timestep for tracing, when using eval_step().
    /// Application must call after all eval() and before time changes.
    void eval_end_step() {}
    /// Simulation complete, run final blocks.  Application must call on completion.
    void final();
    
    // INTERNAL METHODS
    static void _eval_initial_loop(Vtop__Syms* __restrict vlSymsp);
    void __Vconfigure(Vtop__Syms* symsp, bool first);
  private:
    static QData _change_request(Vtop__Syms* __restrict vlSymsp);
    static QData _change_request_1(Vtop__Syms* __restrict vlSymsp);
    void _ctor_var_reset() VL_ATTR_COLD;
  public:
    static void _eval(Vtop__Syms* __restrict vlSymsp);
  private:
#ifdef VL_DEBUG
    void _eval_debug_assertions();
#endif  // VL_DEBUG
  public:
    static void _eval_initial(Vtop__Syms* __restrict vlSymsp) VL_ATTR_COLD;
    static void _eval_settle(Vtop__Syms* __restrict vlSymsp) VL_ATTR_COLD;
    static void _final_TOP(Vtop__Syms* __restrict vlSymsp) VL_ATTR_COLD;
    static void _initial__TOP__1(Vtop__Syms* __restrict vlSymsp) VL_ATTR_COLD;
} VL_ATTR_ALIGNED(VL_CACHE_LINE_BYTES);

//----------


#endif  // guard

VL_MODULE(Vtop) というマクロを使ったクラス定義になっています。VL_MODULEは、下記のように、include/verilated.h の中でマクロ定義されています。VerilatedModule クラスを継承した Vtop というクラスになります。

include/verilated.h:#define VL_MODULE(modname) class modname VL_NOT_FINAL : public VerilatedModule

テストベンチの sim_main.cpp を見てみる

テストベンチの sim_main.cpp を見てみます。#include にて、 と "Vtop.h" を 取り込んでいます。verilated.h は、上記の Vtop.h の中で VL_MODULE マクロを定義しているファイルです。main の中では、Verilated::randReset(1)、Verilated::goFinish() を使っていますが、これらは verilated.h の中で宣言されています。

Vtop* top = new Vtop; で top.v から生成された Vtop クラスのインスタンスを生成しています。生成したインスタンス top のメソッドである eval と final を呼んでいます。

#include <verilated.h>

#include "Vtop.h"

int main(int argc, char** argv, char** env) {

    if (false && argc && argv && env) {}

    Verilated::randReset(1);

    Vtop* top = new Vtop;

    while (!Verilated::gotFinish()) {
        top->eval();
    }

    top->final();

    delete top;

    return 0;
}

eval および final メソッド の中を見てみる

eval は、Vtop.hおよびVtop.cpp 、final は Vtop_Slow.cpp の中で以下のようにメソッドが定義されています。

eval メソッドの中で、eval_step メソッドが呼ばれています。eval_step メソッドは、Vtop.cpp の中で下記のように定義されています。__Vchange が 1 の間、do - while loop を実行しています。

    void eval() { eval_step(); }
void Vtop::eval_step() {
    VL_DEBUG_IF(VL_DBG_MSGF("+++++TOP Evaluate Vtop::eval\n"); );
    Vtop__Syms* __restrict vlSymsp = this->__VlSymsp;  // Setup global symbol table
    Vtop* const __restrict vlTOPp VL_ATTR_UNUSED = vlSymsp->TOPp;
#ifdef VL_DEBUG
    // Debug assertions
    _eval_debug_assertions();
#endif  // VL_DEBUG
    // Initialize
    if (VL_UNLIKELY(!vlSymsp->__Vm_didInit)) _eval_initial_loop(vlSymsp);
    // Evaluate till stable
    int __VclockLoop = 0;
    QData __Vchange = 1;
    do {
        VL_DEBUG_IF(VL_DBG_MSGF("+ Clock loop\n"););
        _eval(vlSymsp);
        if (VL_UNLIKELY(++__VclockLoop > 100)) {
            // About to fail, so enable debug to see what's not settling.
            // Note you must run make with OPT=-DVL_DEBUG for debug prints.
            int __Vsaved_debug = Verilated::debug();
            Verilated::debug(1);
            __Vchange = _change_request(vlSymsp);
            Verilated::debug(__Vsaved_debug);
            VL_FATAL_MT("top.v", 8, "",
                "Verilated model didn't converge\n"
                "- See DIDNOTCONVERGE in the Verilator manual");
        } else {
            __Vchange = _change_request(vlSymsp);
        }
    } while (VL_UNLIKELY(__Vchange));
}

一方、final メソッドは、Vtop_Slow.cpp で下記のように定義されています。_final_TOP メソッドが呼び出されています。

void Vtop::final() {
    VL_DEBUG_IF(VL_DBG_MSGF("+    Vtop::final\n"); );
    // Variables
    Vtop__Syms* __restrict vlSymsp = this->__VlSymsp;
    Vtop* const __restrict vlTOPp VL_ATTR_UNUSED = vlSymsp->TOPp;
    // Body
    vlTOPp->_final_TOP(vlSymsp);
}

_final_TOP メソッドも Vtop_Slow.cpp の中で次のように定義されています。特に何もしていません。

void Vtop::_final_TOP(Vtop__Syms* __restrict vlSymsp) {
    VL_DEBUG_IF(VL_DBG_MSGF("+    Vtop::_final_TOP\n"); );
    // Variables
    Vtop* const __restrict vlTOPp VL_ATTR_UNUSED = vlSymsp->TOPp;
    // Body
}

Vtop を実行してみる

生成された Vtop を実行してみましょう。Hello World! は、$display システムタスクの出力、- top.v:11:Verilog $finish は、$finish システムタスクの出力になります。

$ obj_dir/Vtop
Hello World!
- top.v:11: Verilog $finish

make コマンドの最後の部分と同じです。

-- RUN ---------------------
obj_dir/Vtop
Hello World!
- top.v:11: Verilog $finish
-- DONE --------------------

Verilog HDL の initial 文はどうなっているのか?

Verilog HDL の initial 文は、生成された Vtop クラスの中でどのように実装されているのでしょうか?

Vtop__Slow.cpp の initial__TOP__1 メソッドが Verilog HDL の initial 文に対応しているようです。下記のように // Body のコメントの後に、VL_WRITE("Hello World!\n"); と VL_FINISH_MT("top.v, 11, ""); が実行されています。これは、obj_dir/Vtop の実行結果に一致します。

void Vtop::_initial__TOP__1(Vtop__Syms* __restrict vlSymsp) {
    VL_DEBUG_IF(VL_DBG_MSGF("+    Vtop::_initial__TOP__1\n"); );
    Vtop* const __restrict vlTOPp VL_ATTR_UNUSED = vlSymsp->TOPp;
    // Body
    VL_WRITEF("Hello World!\n");
    VL_FINISH_MT("top.v", 11, "");
}

initial__TOP__1 メソッドは、以下のように、Vtop__Slow.cpp の eval_initial メソッドから呼ばれています。

void Vtop::_eval_initial(Vtop__Syms* __restrict vlSymsp) {
    VL_DEBUG_IF(VL_DBG_MSGF("+    Vtop::_eval_initial\n"); );
    Vtop* const __restrict vlTOPp VL_ATTR_UNUSED = vlSymsp->TOPp;
    // Body
    vlTOPp->_initial__TOP__1(vlSymsp);
}

_eval_initial メソッドは、Vtop.cpp の eval_initial_loop メソッドの最初に呼ばれています。

void Vtop::_eval_initial_loop(Vtop__Syms* __restrict vlSymsp) {
    vlSymsp->__Vm_didInit = true;
    _eval_initial(vlSymsp);
    // Evaluate till stable
    int __VclockLoop = 0;
    QData __Vchange = 1;

eval_initial_loop メソッドは、Vtop.cpp の eval_step メソッドの最初で呼ばれています。

void Vtop::eval_step() {
    VL_DEBUG_IF(VL_DBG_MSGF("+++++TOP Evaluate Vtop::eval\n"); );
    Vtop__Syms* __restrict vlSymsp = this->__VlSymsp;  // Setup global symbol table
    Vtop* const __restrict vlTOPp VL_ATTR_UNUSED = vlSymsp->TOPp;
#ifdef VL_DEBUG
    // Debug assertions
    _eval_debug_assertions();
#endif  // VL_DEBUG
    // Initialize
    if (VL_UNLIKELY(!vlSymsp->__Vm_didInit)) _eval_initial_loop(vlSymsp);
    // Evaluate till stable
    int __VclockLoop = 0;
    QData __Vchange = 1;

C++モデルの eval と Verilog HDL の initial 文の関係

以上のことからまとめると、

C++モデルの eval => eval_step => _eval_initial_loop => _eval_initial => initial__TOP__1 になり、initial_TOP__1 が Verilog HDL の initial 文に対応することになります。

initial 文を複数書くと?

initial 文を下記のように追加してみました。

module top;
   initial begin
      $display("Hello World!");
      $finish;
   end
   initial begin
      $display("Hello World!-2");
   end 
endmodule

make コマンドでの実行結果は、下記のようになりました。

-- RUN ---------------------
obj_dir/Vtop
Hello World!
- top.v:11: Verilog $finish
Hello World!-2
-- DONE --------------------

書いた順番に、initial 文が実行されているっぽいです。

生成されたコードの initial__TOP__1 メソッドは、下記のようになっています。

void Vtop::_initial__TOP__1(Vtop__Syms* __restrict vlSymsp) {
    VL_DEBUG_IF(VL_DBG_MSGF("+    Vtop::_initial__TOP__1\n"); );
    Vtop* const __restrict vlTOPp VL_ATTR_UNUSED = vlSymsp->TOPp;
    // Body
    VL_WRITEF("Hello World!\n");
    VL_FINISH_MT("top.v", 11, "");
    VL_WRITEF("Hello World!-2\n");
}

initial 文をもう一つ追加しました。今度は、最初の initial 文の前に追加しました。

module top;
   initial begin
      $display("Hello World!-0");
   end   
   initial begin
      $display("Hello World!");
      $finish;
   end
   initial begin
      $display("Hello World!-2");
   end   
endmodule

実行結果は以下の通り。

-- RUN ---------------------
obj_dir/Vtop
Hello World!-0
Hello World!
- top.v:14: Verilog $finish
Hello World!-2
-- DONE --------------------
* おわりに

生成されたコードは、下記のように、最初の initial 文の Hellow World!-0 と 2番目の Hello World! が 1つの LV_WRITEF 文でまとまっていますね。

void Vtop::_initial__TOP__1(Vtop__Syms* __restrict vlSymsp) {
    VL_DEBUG_IF(VL_DBG_MSGF("+    Vtop::_initial__TOP__1\n"); );
    Vtop* const __restrict vlTOPp VL_ATTR_UNUSED = vlSymsp->TOPp;
    // Body
    VL_WRITEF("Hello World!-0\nHello World!\n");
    VL_FINISH_MT("top.v", 14, "");
    VL_WRITEF("Hello World!-2\n");
}

final メソッドは何をする?

Vtop クラスの final メソッドは、何をするのだろうか?と思ったのですが、SystemVerilog には、final 文なるものがあります。どうやら、この final 文が final メソッドになるっぽです。
top.v に final 文を追加してみました。

module top;
   initial begin
      $display("Hello World!");
      $finish;
   end

   final begin
      $display("final");
   end
endmodule

実行結果の最後に、final が表示されますね。

-- RUN ---------------------
obj_dir/Vtop
Hello World!
- top.v:16: Verilog $finish
final
-- DONE --------------------

生成されたコードを見てみると、// Body の後に、VL_WRITEF("final\n"); が追加されています。

void Vtop::_final_TOP(Vtop__Syms* __restrict vlSymsp) {
    VL_DEBUG_IF(VL_DBG_MSGF("+    Vtop::_final_TOP\n"); );
    // Variables
    Vtop* const __restrict vlTOPp VL_ATTR_UNUSED = vlSymsp->TOPp;
    // Body
    VL_WRITEF("final\n");
}

Verilated::gotFinish() メソッドとは?

テストベンチ sim_main.cppの中の Verilated::gotFinish() メソッドって、何だろうか?

    while (!Verilated::gotFinish()) {
        top->eval();
    }

Verilated::gotFinishメソッドは、include/verilated.h に以下のように定義されています。Verilated::threadContexp()->gotFinish() の戻り値を返しています。

    static bool gotFinish() VL_MT_SAFE { return Verilated::threadContextp()->gotFinish(); }

こっちの gotFinish() は、include/verilated.h に次のように定義されています。m_s.m_gotFinish の値を返しているだけですね。

    bool gotFinish() const VL_MT_SAFE { return m_s.m_gotFinish; }

m_s.m_gotFinish は、include/verilated.cpp の中で次のように gotFinish メソッドで設定されています。

void VerilatedContext::gotFinish(bool flag) VL_MT_SAFE {
    const VerilatedLockGuard lock(m_mutex);
    m_s.m_gotFinish = flag;
}

gotFinish(bool flag) メソッドは、include/verilated.cpp の中の vl_finish、vl_stop、vl_fatal メソッドの中で呼ばれています。

#ifndef VL_USER_FINISH  ///< Define this to override this function
void vl_finish(const char* filename, int linenum, const char* hier) VL_MT_UNSAFE {
    if (false && hier) {}
    VL_PRINTF(  // Not VL_PRINTF_MT, already on main thread
        "- %s:%d: Verilog $finish\n", filename, linenum);
    if (Verilated::threadContextp()->gotFinish()) {
        VL_PRINTF(  // Not VL_PRINTF_MT, already on main thread
            "- %s:%d: Second verilog $finish, exiting\n", filename, linenum);
        Verilated::runFlushCallbacks();
        Verilated::runExitCallbacks();
        exit(0);
    }
    Verilated::threadContextp()->gotFinish(true);
}
#endif

#ifndef VL_USER_STOP  ///< Define this to override this function
void vl_stop(const char* filename, int linenum, const char* hier) VL_MT_UNSAFE {
    const char* const msg = "Verilog $stop";
    Verilated::threadContextp()->gotError(true);
    Verilated::threadContextp()->gotFinish(true);
    if (Verilated::threadContextp()->fatalOnError()) {
        vl_fatal(filename, linenum, hier, msg);
    } else {
        if (filename && filename[0]) {
            // Not VL_PRINTF_MT, already on main thread
            VL_PRINTF("%%Error: %s:%d: %s\n", filename, linenum, msg);
        } else {
            VL_PRINTF("%%Error: %s\n", msg);
        }
        Verilated::runFlushCallbacks();
    }
}
#endif

#ifndef VL_USER_FATAL  ///< Define this to override this function
void vl_fatal(const char* filename, int linenum, const char* hier, const char* msg) VL_MT_UNSAFE {
    if (false && hier) {}
    Verilated::threadContextp()->gotError(true);
    Verilated::threadContextp()->gotFinish(true);
    if (filename && filename[0]) {
        // Not VL_PRINTF_MT, already on main thread
        VL_PRINTF("%%Error: %s:%d: %s\n", filename, linenum, msg);
    } else {
        VL_PRINTF("%%Error: %s\n", msg);
    }
    Verilated::runFlushCallbacks();

    VL_PRINTF("Aborting...\n");  // Not VL_PRINTF_MT, already on main thread

    // Second flush in case VL_PRINTF does something needing a flush
    Verilated::runFlushCallbacks();

    // Callbacks prior to termination
    Verilated::runExitCallbacks();
    abort();
}
#endif

vl_finish、vl_stop、vl_fatal メソッドは、Verilog HDLの $finish、$stop、$fatal(これは、SystemVerilogかな?) システムタスクに対応していて、include/verilated.cpp で次のように定義されています。

void VL_FINISH_MT(const char* filename, int linenum, const char* hier) VL_MT_SAFE {
#ifdef VL_THREADED
VerilatedThreadMsgQueue::post(VerilatedMsg([=]() { //
vl_finish(filename, linenum, hier);
}));
#else
vl_finish(filename, linenum, hier);
#endif
}

void VL_STOP_MT(const char* filename, int linenum, const char* hier, bool maybe) VL_MT_SAFE {
#ifdef VL_THREADED
VerilatedThreadMsgQueue::post(VerilatedMsg([=]() { //
vl_stop_maybe(filename, linenum, hier, maybe);
}));
#else
vl_stop_maybe(filename, linenum, hier, maybe);
#endif
}

void VL_FATAL_MT(const char* filename, int linenum, const char* hier, const char* msg) VL_MT_SAFE {
#ifdef VL_THREADED
VerilatedThreadMsgQueue::post(VerilatedMsg([=]() { //
vl_fatal(filename, linenum, hier, msg);
}));
#else
vl_fatal(filename, linenum, hier, msg);
#endif
}

<<

ということは、下記のように、$finish; をコメントアウトすると、シミュレーションは終わらないのか?

module top;
   initial begin
      $display("Hello World!");
      // $finish;
   end
endmodule

はい、終わりませんでした。

$ make
-- RUN ---------------------
obj_dir/Vtop
Hello World!

$stop システムタスクを追加したみたら、どうなるでしょうか?

module top;
   initial begin
      $display("Hello World!");
      $stop();
      // $finish;
   end
endmodule

シミュレーションは終わりますが、%Error および Aborting ... になりますね。

-- RUN ---------------------
obj_dir/Vtop
Hello World!
%Error: top.v:16: Verilog $stop
Aborting...
make: *** [Makefile:40: default] Aborted

$fatal システムタスクにしたみたら、ちょっと違うメッセージですが、%Error および Aborting ... になりますね。

module top;
   initial begin
      $display("Hello World!");
      $fatal();
      // $finish;
   end
endmodule
-- RUN ---------------------
obj_dir/Vtop
Hello World!
[0] %Error: top.v:16: Assertion failed in TOP.top
%Error: top.v:16: Verilog $stop
Aborting...
make: *** [Makefile:40: default] Aborted

$stop システムタスクの場合は、fatalなエラーが発生した場合(fatalOnError()が0出ない時)は、vl_fatal ($fatalシステムタスクを呼んだ場合と同じ)を呼んでいますね。

#ifndef VL_USER_STOP  ///< Define this to override this function
void vl_stop(const char* filename, int linenum, const char* hier) VL_MT_UNSAFE {
    const char* const msg = "Verilog $stop";
    Verilated::threadContextp()->gotError(true);
    Verilated::threadContextp()->gotFinish(true);
    if (Verilated::threadContextp()->fatalOnError()) {
        vl_fatal(filename, linenum, hier, msg);
    } else {
        if (filename && filename[0]) {
            // Not VL_PRINTF_MT, already on main thread
            VL_PRINTF("%%Error: %s:%d: %s\n", filename, linenum, msg);
        } else {
            VL_PRINTF("%%Error: %s\n", msg);
        }
        Verilated::runFlushCallbacks();
    }
}
#endif

$fatal システムタスクでは、vl_fatal メソッドの最後に abort() 関数を呼んでいますね。

#ifndef VL_USER_FATAL  ///< Define this to override this function
void vl_fatal(const char* filename, int linenum, const char* hier, const char* msg) VL_MT_UNSAFE {
    if (false && hier) {}
    Verilated::threadContextp()->gotError(true);
    Verilated::threadContextp()->gotFinish(true);
    if (filename && filename[0]) {
        // Not VL_PRINTF_MT, already on main thread
        VL_PRINTF("%%Error: %s:%d: %s\n", filename, linenum, msg);
    } else {
        VL_PRINTF("%%Error: %s\n", msg);
    }
    Verilated::runFlushCallbacks();

    VL_PRINTF("Aborting...\n");  // Not VL_PRINTF_MT, already on main thread

    // Second flush in case VL_PRINTF does something needing a flush
    Verilated::runFlushCallbacks();

    // Callbacks prior to termination
    Verilated::runExitCallbacks();
    abort();
}
#endif

おわりに

Verilatror の中を調べるということで、例題の examples/make_hello_c で生成される C++ コードとその中で呼ばれる メソッドを調べてみました。
make_hello_c の top.v には、initial 文しかないですが、それだけでも、いろいろなことが行われていることがわかって、凄くためになりました。

次回は、examples/make_tracing_c を見てみます。
Verilatorの最新版 v4.200 では、その前の v4.110 とちょこっと変わった部分の例題が examples/make_trace_c にありましたので。

こうご期待。。。