Vengineerの戯言

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

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

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

はじめに

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

今回は、examples/make_tracing_c を見ていきます。examples/make_tracing_c では、波形ダンプと、カバレッジを行っています。波形ダンプは VCD ファイルになります。VCD ファイルは、OSSの gtkwave にて波形を見ることができます。

Verilog HDLコード

Verilog HDL コードでは、入力および出力信号、下位モジュール( sub.v )、それと、波形ダンプ($test$plusargs("trace"))があります。

module top
  (
   // Declare some signals so we can see how I/O works
   input              clk,
   input              reset_l,

   output wire [1:0]  out_small,
   output wire [39:0] out_quad,
   output wire [69:0] out_wide,
   input [1:0]        in_small,
   input [39:0]       in_quad,
   input [69:0]       in_wide
   );

   // Connect up the outputs, using some trivial logic
   assign out_small = ~reset_l ? '0 : (in_small + 2'b1);
   assign out_quad  = ~reset_l ? '0 : (in_quad + 40'b1);
   assign out_wide  = ~reset_l ? '0 : (in_wide + 70'b1);

   // And an example sub module. The submodule will print stuff.
   sub sub (/*AUTOINST*/
            // Inputs
            .clk                        (clk),
            .reset_l                    (reset_l));

   // Print some stuff as an example
   initial begin
      if ($test$plusargs("trace") != 0) begin
         $display("[%0t] Tracing to logs/vlt_dump.vcd...\n", $time);
         $dumpfile("logs/vlt_dump.vcd");
         $dumpvars();
      end
      $display("[%0t] Model running...\n", $time);
   end

endmodule

テストベンチコード

テストベンチコードは、前回と同じ C++ コードです。Vtop がインスタンスされる前のコードを下記に示します。

// For std::unique_ptr
#include <memory>

// Include common routines
#include <verilated.h>

// Include model header, generated from Verilating "top.v"
#include "Vtop.h"

int main(int argc, char** argv, char** env) {
    // This is a more complicated example, please also see the simpler examples/make_hello_c.

    // Prevent unused variable warnings
    if (false && argc && argv && env) {}

    // Create logs/ directory in case we have traces to put under it
    Verilated::mkdir("logs");

    // Construct a VerilatedContext to hold simulation time, etc.
    // Multiple modules (made later below with Vtop) may share the same
    // context to share time, or modules may have different contexts if
    // they should be independent from each other.

    // Using unique_ptr is similar to
    // "VerilatedContext* contextp = new VerilatedContext" then deleting at end.
    const std::unique_ptr<VerilatedContext> contextp{new VerilatedContext};

    // Set debug level, 0 is off, 9 is highest presently used
    // May be overridden by commandArgs argument parsing
    contextp->debug(0);

    // Randomization reset policy
    // May be overridden by commandArgs argument parsing
    contextp->randReset(2);

    // Verilator must compute traced signals
    contextp->traceEverOn(true);

    // Pass arguments so Verilated code can see them, e.g. $value$plusargs
    // This needs to be called before you create any model
    contextp->commandArgs(argc, argv);

    Verilated::randReset(0);

examples/make_hello_c では、Verilated::randReset(0) だけでした。examples/make_tracing_c では、verilated::randReset(0) 以外に、

  • Verilated::mkdir("logs");
  • const std::unique_ptr contextp{new VerilatedContext};
  • contextp->debug(0);
  • contextp->randReset(2);
  • contextp->traceEverOn(true);
  • contextp->commandArgs(argc, argv);

が追加されています。Verilated::mkdir("logs"); は、波形ダンプファイルを生成するためのディレクトリを作っているだけですが、それ以降の VerilatedContextクラスのcontextp インスタンスに対するメソッドは何を行っているのでしょうかね?

VerilatedContext クラス

VerilatedContext クラス、実は、v4.110 までは、include/verilated.h には存在しなかったです。v4.200 から登場しました。v4.110 までは、Verilatedクラスの中で同じ機能を実現していたっぽいです。v4.200 では、Verilated クラスの中で次のように VerilatedContextクラスのインスタンスを static メンバー変数としてもっているようです。

    static VerilatedContext* s_lastContextp;  ///< Last context constructed/attached

VerilatedContex クラスの宣言の説明のコメントが下記のようになっています。

/// Verilator simulation context
///
/// The VerilatedContext contains the information common across all models
/// that are interconnected, for example this contains the simulation time
/// and if $finish was executed.
///
/// VerilatedContexts maybe created by the user wrapper code and passed
/// when a model is created.  If this is not done, then Verilator will use
/// the Verilated::defaultContextp()'s global context.

VerilatedContext クラスの 各メソッドを見ていきます。

  • contextp->debug(0);
inline void VerilatedContext::debug(int val) VL_MT_SAFE { Verilated::debug(val); }

Verilatedクラスのdebugメソッドを呼んでいるだけです。Verilatedクラスのdebugメソッドは、include/verilated.cpp に次のようで定義されています。s_debug メンバー変数に設定値を変えているだけですね。

void Verilated::debug(int level) VL_MT_SAFE {
    s_debug = level;
    if (level) {
#ifdef VL_DEBUG
        VL_DEBUG_IF(VL_DBG_MSGF("- Verilated::debug is on."
                                " Message prefix indicates {<thread>,<sequence_number>}.\n"););
#else
        VL_PRINTF_MT("- Verilated::debug attempted,"
                     " but compiled without VL_DEBUG, so messages suppressed.\n"
                     "- Suggest remake using 'make ... CPPFLAGS=-DVL_DEBUG'\n");
#endif
    }
}
  • contextp->randReset(2);

randReset メソッドは、下記のように m_s.m_randReset に設定値を変えているだけです。

void VerilatedContext::randReset(int val) VL_MT_SAFE {
    const VerilatedLockGuard lock(m_mutex);
    m_s.m_randReset = val;
}

この m_randReset への値は、以下のように、リセット後の初期値の設定で、0 を設定すると初期値は オール0、1を設定すると初期値はオール1、2を設定するとランダムになるようです。
examples/make_tracing_c では、2を設定しているので初期値はランダムになります。

        int m_randReset = 0;  // Random reset: 0=all 0s, 1=all 1s, 2=random
  • contextp->traceEverOn(true);

traceEverOnメソッドは、次のように include/verilated.h で定義されていて、すべてのポイントの trace (波形ダンプ) を有効にしています。

    /// Allow traces to at some point be enabled (disables some optimizations)
    void traceEverOn(bool flag) VL_MT_SAFE {
        if (flag) calcUnusedSigs(true);
    }
  • contextp->commandArgs(argc, argv);

commandArgs メソッドは、下記のように定義されていて、impp()->commandArgsAddGuts メソッドが呼び出されています。

    /// Record command-line arguments, for retrieval by $test$plusargs/$value$plusargs,
    /// and for parsing +verilator+ run-time arguments.
    /// This should be called before the first model is created.
    void commandArgs(int argc, const char** argv) VL_MT_SAFE_EXCLUDES(m_argMutex);
    void commandArgs(int argc, char** argv) VL_MT_SAFE {
        commandArgs(argc, const_cast<const char**>(argv));
    }
void VerilatedContext::commandArgs(int argc, const char** argv) VL_MT_SAFE_EXCLUDES(m_argMutex) {
    const VerilatedLockGuard lock(m_argMutex);
    m_args.m_argVec.clear();  // Empty first, then add
    impp()->commandArgsAddGuts(argc, argv);
}
void VerilatedContext::commandArgsAdd(int argc, const char** argv)
    VL_MT_SAFE_EXCLUDES(m_argMutex) {
    const VerilatedLockGuard lock(m_argMutex);
    impp()->commandArgsAddGuts(argc, argv);
}

impp()->commandArgsAddGuts メソッド は、include/verilated.cpp の中で、VerilatedContextImpクラスで次のように定義されています。

void VerilatedContextImp::commandArgsAddGuts(int argc, const char** argv) VL_REQUIRES(m_argMutex) {
    if (!m_args.m_argVecLoaded) m_args.m_argVec.clear();
    for (int i = 0; i < argc; ++i) {
        m_args.m_argVec.push_back(argv[i]);
        commandArgVl(argv[i]);
    }
    m_args.m_argVecLoaded = true;  // Can't just test later for empty vector, no arguments is ok
}
|<<

最終的には、下記のcommandArgVl メソッドが呼ばれます。この commandArgVl メソッドの中でプログラムを実行する時の引数で、+verilator+から始まるものの解析を行っています。

>|cpp|
void VerilatedContextImp::commandArgVl(const std::string& arg) {
    if (0 == strncmp(arg.c_str(), "+verilator+", strlen("+verilator+"))) {
        std::string value;
        if (arg == "+verilator+debug") {
            Verilated::debug(4);
        } else if (commandArgVlValue(arg, "+verilator+debugi+", value /*ref*/)) {
            Verilated::debug(atoi(value.c_str()));
        } else if (commandArgVlValue(arg, "+verilator+error+limit+", value /*ref*/)) {
            errorLimit(atoi(value.c_str()));
        } else if (arg == "+verilator+help") {
            VerilatedImp::versionDump();
            VL_PRINTF_MT("For help, please see 'verilator --help'\n");
            VL_FATAL_MT("COMMAND_LINE", 0, "",
                        "Exiting due to command line argument (not an error)");
        } else if (commandArgVlValue(arg, "+verilator+prof+threads+start+", value /*ref*/)) {
            profThreadsStart(atoll(value.c_str()));
        } else if (commandArgVlValue(arg, "+verilator+prof+threads+window+", value /*ref*/)) {
            profThreadsWindow(atol(value.c_str()));
        } else if (commandArgVlValue(arg, "+verilator+prof+threads+file+", value /*ref*/)) {
            profThreadsFilename(value);
        } else if (commandArgVlValue(arg, "+verilator+rand+reset+", value /*ref*/)) {
            randReset(atoi(value.c_str()));
        } else if (commandArgVlValue(arg, "+verilator+seed+", value /*ref*/)) {
            randSeed(atoi(value.c_str()));
        } else if (arg == "+verilator+noassert") {
            assertOn(false);
        } else if (arg == "+verilator+V") {
            VerilatedImp::versionDump();  // Someday more info too
            VL_FATAL_MT("COMMAND_LINE", 0, "",
                        "Exiting due to command line argument (not an error)");
        } else if (arg == "+verilator+version") {
            VerilatedImp::versionDump();
            VL_FATAL_MT("COMMAND_LINE", 0, "",
                        "Exiting due to command line argument (not an error)");
        } else {
            VL_PRINTF_MT("%%Warning: Unknown +verilator runtime argument: '%s'\n", arg.c_str());
        }
    }
}

この +verilator+ オプションは、SImulation Runtime Arguments ということで、このドキュメントに説明があります。なお、randReset メソッドと同じことは、+verilator+rand+reset+にても設定できるようです。

テストベンチの続きを見ていきましょう!Vtopのインスタンスを生成の時で、examples/make_hello_c との違いは、Vtopクラスのコンストラクタの第一引数にcontextp.get()を指定しているところです。また、std::unique_ptr を使って、delete top を呼ばないようにしています。std::unique_ptr を使う時は、#include が必要です。

    // Construct the Verilated model, from Vtop.h generated from Verilating "top.v".
    // Using unique_ptr is similar to "Vtop* top = new Vtop" then deleting at end.
    // "TOP" will be the hierarchical name of the module.
    const std::unique_ptr<Vtop> top{new Vtop{contextp.get(), "TOP"}};

テストベンチでの信号ドライブ

やっと、テストベンチでの信号ドライブです。top.v の入力信号となるものに対して、テストベンチで信号をドライブしています。

    // Set Vtop's input signals
    top->reset_l = !0;
    top->clk = 0;
    top->in_small = 1;
    top->in_quad = 0x1234;
    top->in_wide[0] = 0x11111111;
    top->in_wide[1] = 0x22222222;
    top->in_wide[2] = 0x3;

生成されたC++コード(Vtop.h)を見てみると次のようになっています。入力信号は VL_INx、出力信号は VL_OUTy になっています。8ビット以下の信号は 8 、64ビット以上の信号は W、33ビットから64ビットまでは 64 っぽいですね。Wの場合は、32ビット単位の信号に分割されるっぽいですね。in_wide/out_wideは70ビットなので3つの32ビットの信号として表現していますね。テストベンチ側では、in_wide[0], in_wide[1], in_wide[2] にそれぞれ信号を設定していますね。

VL_MODULE(Vtop) {
  public:
    
    // PORTS
    // The application code writes and reads these signals to
    // propagate new values into/out from the Verilated model.
    VL_IN8(clk,0,0);
    VL_IN8(reset_l,0,0);
    VL_OUT8(out_small,1,0);
    VL_IN8(in_small,1,0);
    VL_OUTW(out_wide,69,0,3);
    VL_INW(in_wide,69,0,3);
    VL_OUT64(out_quad,39,0);
    VL_IN64(in_quad,39,0);

Vtopの出力信号の初期値

Vtopの出力信号の初期値は、生成されたC++コードの constructor にて設定されます。下記のコードが Vtop__Slow.cpp の Vtopの constructor です。// Reset structure values の後に、_ctor_var_reset() メソッドを呼んでいます。

Vtop::Vtop(VerilatedContext* _vcontextp__, const char* _vcname__)
    : VerilatedModule{_vcname__}
 {
    Vtop__Syms* __restrict vlSymsp = __VlSymsp = new Vtop__Syms(_vcontextp__, this, name());
    Vtop* const __restrict vlTOPp VL_ATTR_UNUSED = vlSymsp->TOPp;
    // Reset internal values
    
    // Reset structure values
    _ctor_var_reset();
}

ctor_var_reset メソッドは次のようになっています。各出力信号は、VL_RAND_RESET_x(n) にて初期化しています。この VL_RAND_RESET_x(n) で設定される値は、contextp->randReset(2) で設定された random になるわけです。

void Vtop::_ctor_var_reset() {
    VL_DEBUG_IF(VL_DBG_MSGF("+    Vtop::_ctor_var_reset\n"); );
    // Body
    clk = VL_RAND_RESET_I(1);
    reset_l = VL_RAND_RESET_I(1);
    out_small = VL_RAND_RESET_I(2);
    out_quad = VL_RAND_RESET_Q(40);
    VL_RAND_RESET_W(70, out_wide);
    in_small = VL_RAND_RESET_I(2);
    in_quad = VL_RAND_RESET_Q(40);
    VL_RAND_RESET_W(70, in_wide);
    top__DOT____Vtogcov__clk = VL_RAND_RESET_I(1);
    top__DOT____Vtogcov__reset_l = VL_RAND_RESET_I(1);
    top__DOT____Vtogcov__out_small = VL_RAND_RESET_I(2);
    top__DOT____Vtogcov__out_quad = VL_RAND_RESET_Q(40);
    VL_RAND_RESET_W(70, top__DOT____Vtogcov__out_wide);
    top__DOT____Vtogcov__in_small = VL_RAND_RESET_I(2);
    top__DOT____Vtogcov__in_quad = VL_RAND_RESET_Q(40);
    VL_RAND_RESET_W(70, top__DOT____Vtogcov__in_wide);
    top__DOT__sub__DOT__count_c = VL_RAND_RESET_I(32);
    top__DOT__sub__DOT____Vtogcov__count_c = VL_RAND_RESET_I(32);
    for (int __Vi0=0; __Vi0<1; ++__Vi0) {
        __Vm_traceActivity[__Vi0] = VL_RAND_RESET_I(1);
    }
}

シミュレーションループ

シミュレーションループは、examples/make_hello_c と基本的には同じですが、Verilated::gotFinish() ではなく、contexp->goFinish() になっています。
コメントにあるように、v4.200 では、Verilated::gotFinish() を使っていたが、contextp->gotFinish() に変わるって。。。そんでもって、contextp-> XXX のほとんどは、Verilated:: を呼ぶ感じなんだって。。。Verilated::を使う時は 1つの context の時だけだって、その時は contextp-> を使うより速いって。。。

    // Simulate until $finish
    while (!contextp->gotFinish()) {
        // Historical note, before Verilator 4.200 Verilated::gotFinish()
        // was used above in place of contextp->gotFinish().
        // Most of the contextp-> calls can use Verilated:: calls instead;
        // the Verilated:: versions simply assume there's a single context
        // being used (per thread).  It's faster and clearer to use the
        // newer contextp-> versions.

次の contextp->timeInc(1) は、1 timeprecision period 先に進む。つまり、単位時間進む。v4.200 より前は、sc_time_stamp() を使っていたが、v4.200 以降では sc_time_stamp() は使わないってよ。

        contextp->timeInc(1);  // 1 timeprecision period passes...
        // Historical note, before Verilator 4.200 a sc_time_stamp()
        // function was required instead of using timeInc.  Once timeInc()
        // is called (with non-zero), the Verilated libraries assume the
        // new API, and sc_time_stamp() will no longer work.

クロックを反転するとのは、top->clk = !top->clk; にする。そして、クロックが 0 (立下りエッジ)の時に、Vtop への入力信号を変化させる。

contextp->time() にてシミュレーション時間を獲得し、1 から 10 までは、リセット信号 (top->reset_l) を !1 (つまり、0)にし、それ以外の時は !0 (つまり、1)にしている。
top->in_quad は、毎クロック +0x12 している。

        // Toggle a fast (time/2 period) clock
        top->clk = !top->clk;

        // Toggle control signals on an edge that doesn't correspond
        // to where the controls are sampled; in this example we do
        // this only on a negedge of clk, because we know
        // reset is not sampled there.
        if (!top->clk) {
            if (contextp->time() > 1 && contextp->time() < 10) {
                top->reset_l = !1;  // Assert reset
            } else {
                top->reset_l = !0;  // Deassert reset
            }
            // Assign some other inputs
            top->in_quad += 0x12;
        }

テストベンチからの信号のドライブが終わったら、top->eval() で top内の評価を行し、評価が終わったら、top からの出力信号が変わっているので、VL_PRINTF (Verilog HDLでの $displayシステムタスク)にて信号を表示する。while loop を抜けたら、top->final() を実行するのはお決まり。

        // Evaluate model
        // (If you have multiple models being simulated in the same
        // timestep then instead of eval(), call eval_step() on each, then
        // eval_end_step() on each. See the manual.)
        top->eval();

        // Read outputs
        VL_PRINTF("[%" VL_PRI64 "d] clk=%x rstl=%x iquad=%" VL_PRI64 "x"
                  " -> oquad=%" VL_PRI64 "x owide=%x_%08x_%08x\n",
                  contextp->time(), top->clk, top->reset_l, top->in_quad, top->out_quad,
                  top->out_wide[2], top->out_wide[1], top->out_wide[0]);
    }

    // Final model cleanup
    top->final();

VM_COVERAGE マクロが 1 の時は、カバレッジデータを logs ディレクトリの coverage.dat というファイルに出力する。

    // Coverage analysis (calling write only after the test is known to pass)
#if VM_COVERAGE
    Verilated::mkdir("logs");
    contextp->coveragep()->write("logs/coverage.dat");
#endif

    // Return good completion status
    // Don't use exit() or destructor won't get called
    return 0;
}

verilator のオプション

make コマンドにて、ビルドすると、verilator へのオプションが次のようになっている。-Os は C++コンパイラへの最適化のオプション、-x-assign は不定 x は 0 をアサイン、--trace はダンプファイルの出力、--assert は assert を有効、--coverage は カバレッジファイルの出力、-f input.vc は Verilog HDL シミュレータのオプションの -f と同じで次の引数のファイルの中身がコマンド引数になる。

-- VERILATE ----------------
verilator  -cc --exe -Os -x-assign 0 -Wall --trace --assert --coverage -f input.vc top.v sim_main.cpp

下記のログファイルから、-Os が g++ の引数として引き渡されているのが分かる。--trace は、-VM_TRACE=1、--coverage は、-DVM_COVERAGE=1になっている。

-- BUILD -------------------
make -j -C obj_dir -f ../Makefile_obj
make[1]: Entering directory '/mnt/c/Users/haray/home/verilator/verilator/examples/make_tracing_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=1 -DVM_SC=0 -DVM_TRACE=1 -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 -MMD -MP -DVL_DEBUG=1 -Os -fstrict-aliasing -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=1 -DVM_SC=0 -DVM_TRACE=1 -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 -MMD -MP -DVL_DEBUG=1 -Os -c -o verilated.o /usr/local/verilator/v4.200/share/verilator/include/verilated.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=1 -DVM_SC=0 -DVM_TRACE=1 -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 -MMD -MP -DVL_DEBUG=1 -Os -c -o verilated_cov.o /usr/local/verilator/v4.200/share/verilator/include/verilated_cov.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=1 -DVM_SC=0 -DVM_TRACE=1 -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 -MMD -MP -DVL_DEBUG=1 -Os -c -o verilated_vcd_c.o /usr/local/verilator/v4.200/share/verilator/include/verilated_vcd_c.cpp   
/usr/bin/perl /usr/local/verilator/v4.200/share/verilator/bin/verilator_includer -DVL_INCLUDE_OPT=include Vtop.cpp Vtop__Trace.cpp Vtop__Slow.cpp Vtop__Syms.cpp Vtop__Trace__Slow.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=1 -DVM_SC=0 -DVM_TRACE=1 -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 -MMD -MP -DVL_DEBUG=1 -Os -fstrict-aliasing -c -o Vtop__ALL.o Vtop__ALL.cpp
Archive ar -cr Vtop__ALL.a Vtop__ALL.o
g++    sim_main.o verilated.o verilated_cov.o verilated_vcd_c.o Vtop__ALL.a      -o Vtop

プログラムの実行

下記は、make コマンドにて、プログラムを実行した部分。

-- RUN ---------------------
obj_dir/Vtop +trace
[1] Tracing to logs/vlt_dump.vcd...

[1] Model running...

[1] clk=1 rstl=1 iquad=1234 -> oquad=1235 owide=3_22222222_11111112
[2] clk=0 rstl=0 iquad=1246 -> oquad=0 owide=0_00000000_00000000
[3] clk=1 rstl=0 iquad=1246 -> oquad=0 owide=0_00000000_00000000
[4] clk=0 rstl=0 iquad=1258 -> oquad=0 owide=0_00000000_00000000
[5] clk=1 rstl=0 iquad=1258 -> oquad=0 owide=0_00000000_00000000
[6] clk=0 rstl=0 iquad=126a -> oquad=0 owide=0_00000000_00000000
[7] clk=1 rstl=0 iquad=126a -> oquad=0 owide=0_00000000_00000000
[8] clk=0 rstl=0 iquad=127c -> oquad=0 owide=0_00000000_00000000
[9] clk=1 rstl=0 iquad=127c -> oquad=0 owide=0_00000000_00000000
[10] clk=0 rstl=1 iquad=128e -> oquad=128f owide=3_22222222_11111112
[11] clk=1 rstl=1 iquad=128e -> oquad=128f owide=3_22222222_11111112
[12] clk=0 rstl=1 iquad=12a0 -> oquad=12a1 owide=3_22222222_11111112
[13] clk=1 rstl=1 iquad=12a0 -> oquad=12a1 owide=3_22222222_11111112
[14] clk=0 rstl=1 iquad=12b2 -> oquad=12b3 owide=3_22222222_11111112
[15] clk=1 rstl=1 iquad=12b2 -> oquad=12b3 owide=3_22222222_11111112
[16] clk=0 rstl=1 iquad=12c4 -> oquad=12c5 owide=3_22222222_11111112
*-* All Finished *-*
- sub.v:29: Verilog $finish
[17] clk=1 rstl=1 iquad=12c4 -> oquad=12c5 owide=3_22222222_11111112

-- COVERAGE ----------------
verilator_coverage --annotate logs/annotated logs/coverage.dat
Total coverage (2/31) 6.00%
See lines with '%00' in logs/annotated

-- DONE --------------------
To see waveforms, open vlt_dump.vcd in a waveform viewer
|<<

最初に、[1] Tracing to logs/vlt_dump.vcd... と波形ダンプを行っている。この部分は、top.v の 下記の initial 文で $display システムタスクからの出力である。その後に、[1] MOdel running... と表示している。

>|verilog|
   initial begin
      if ($test$plusargs("trace") != 0) begin
         $display("[%0t] Tracing to logs/vlt_dump.vcd...\n", $time);
         $dumpfile("logs/vlt_dump.vcd");
         $dumpvars();
      end
      $display("[%0t] Model running...\n", $time);
   end

その後、[16] まで信号が表示され、All Finished が表示される。

*-* All Finished *-*
- sub.v:29: Verilog $finish
[17] clk=1 rstl=1 iquad=12c4 -> oquad=12c5 owide=3_22222222_11111112

これは、sub.v の下記の部分が実行されたである。

         if (count_c >= 3) begin
            // This write is a magic value the Makefile uses to make sure the
            // test completes successfully.
            $write("*-* All Finished *-*\n");
            $finish;
         end

All Finished が表示された後に、[17] ... と表示されているので、$write("[%0t] : *-* All Finished *-*\n", $time); に変更して再度実行してみると、下記のように、[17] と表示されたので同じ時間に表示されている。

[16] clk=0 rstl=1 iquad=12c4 -> oquad=12c5 owide=3_22222222_11111112
[17] : *-* All Finished *-*
- sub.v:29: Verilog $finish
[17] clk=1 rstl=1 iquad=12c4 -> oquad=12c5 owide=3_22222222_11111112

coverage

make コマンドの出力の下記の部分は、出力されたカバレッジデータからカバレッジ状況をレポートしている部分である。verilator_coverage コマンドにて 2/31 で全体の 6% のカバレッジになっているのが分かる。

-- COVERAGE ----------------
verilator_coverage --annotate logs/annotated logs/coverage.dat
Total coverage (2/31) 6.00%
See lines with '%00' in logs/annotated

logs ディレクトリを見てみると、annotated ディレクトリ、coverage.dat (カバレッジデータ)、vlt_dump.vcd (波形データ:VCDフォーマット)が生成されたことが分かる。annotated ディレクトリには、sub.v top.v というファイルが生成されている。

$ ls logs/
annotated  coverage.dat  vlt_dump.vcd
$ ls logs/annotated/
sub.v  top.v

logs/annotated/top.v を見てみると、次のようになっていました。各行に対して、実行カバレッジの数値が annotated されています。

	module top
	  (
	   // Declare some signals so we can see how I/O works
 000017	   input              clk,
%000003	   input              reset_l,
	
%000003	   output wire [1:0]  out_small,
%000030	   output wire [39:0] out_quad,
%000054	   output wire [69:0] out_wide,
%000001	   input [1:0]        in_small,
%000035	   input [39:0]       in_quad,
%000018	   input [69:0]       in_wide

おわりに

examples/make_tracing_c の中と、生成されたC++コードを見てみました。また、verilator の引数で 波形ダンプやカバレッジを有効にする方法も確認できました。
次回は、example/make_tracing_c の生成された C++ コードでまだ見ていないメソッドを覗いてみます。