Vengineerの妄想(準備期間)

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

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

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

はじめに

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

今回は、examples/make_hello_c と examples/make_tracing_c では見てこなかった下記の機能についてみていきます。

  • timescale
  • coverage
  • trace

Timescale

Verilog HDL では `timescale directive 、SystemVerilog では timeunit/timeprecision にて timescale を決めることができます。timescale がどのように設定されているのかを examples/make_hello_c で生成されたC++コードをみていきます。

下記のように、Vtopクラスの__Vconfigure メソッドて、Timescale (timeunit と timeprecision)を設定しています。

void Vtop::__Vconfigure(Vtop__Syms* vlSymsp, bool first) {
    if (false && first) {}  // Prevent unused
    this->__VlSymsp = vlSymsp;
    if (false && this->__VlSymsp) {}  // Prevent unused
    vlSymsp->_vm_contextp__->timeunit(-12);
    vlSymsp->_vm_contextp__->timeprecision(-12);
}

下記のように、Verilog HDLコード(top.v) に、`timescale 100ns/1ns を追加してみました。

`timescale 100ns/1ns

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

この時の、__Vconfigure メソッドは次のようになりました。timeuinit は -7 に、timeprecision は -9 になりました。ということは、デフォルトの -12 は、1ps ということになります。

void Vtop::__Vconfigure(Vtop__Syms* vlSymsp, bool first) {
    if (false && first) {}  // Prevent unused
    this->__VlSymsp = vlSymsp;
    if (false && this->__VlSymsp) {}  // Prevent unused
    vlSymsp->_vm_contextp__->timeunit(-7);
    vlSymsp->_vm_contextp__->timeprecision(-9);
}

module 内で、timeunit と timeprecision にて再設定すると、__Vconfigure メソッドの設定値も、-8 と -10 に変わりました。

`timescale 100ns/1ns

module top;
  timeunit 10ns;
  timeprecision100ps;
   initial begin
      $display("Hello World!");
      $finish;
   end
endmodule
void Vtop::__Vconfigure(Vtop__Syms* vlSymsp, bool first) {
    if (false && first) {}  // Prevent unused
    this->__VlSymsp = vlSymsp;
    if (false && this->__VlSymsp) {}  // Prevent unused
    vlSymsp->_vm_contextp__->timeunit(-8);
    vlSymsp->_vm_contextp__->timeprecision(-10);
}

coverage

verilator コマンドで、--coverage オプションを指定した時には、Vtop_Syms クラスの内容が変わるようです。examples/make_hello_c の例題で、verilator コマンドに --coverage オプションを付けたときの __Vconfigure メソッドの部分です。_configura_coverage(vlSymsp, first); が追加されました。

void Vtop::__Vconfigure(Vtop__Syms* vlSymsp, bool first) {
    if (false && first) {}  // Prevent unused
    this->__VlSymsp = vlSymsp;
    if (false && this->__VlSymsp) {}  // Prevent unused
    _configure_coverage(vlSymsp, first);
    vlSymsp->_vm_contextp__->timeunit(-12);
    vlSymsp->_vm_contextp__->timeprecision(-12);
}

_configure_coverage メソッドは、次のようになっています。examples/make_hello_c の場合は // Body 以降に何もありません。

void Vtop::_configure_coverage(Vtop__Syms* __restrict vlSymsp, bool first) {
    VL_DEBUG_IF(VL_DBG_MSGF("+    Vtop::_configure_coverage\n"); );
    // Body
    if (false && vlSymsp && first) {}  // Prevent unused
}

exampls/make_tracing_c の場合はどうなるでしょうか?

Vtop__Symsの constructor が次のようになっていて、下記のメンバー変数が追加されています。

    , __Vm_dumping(false)
    , __Vm_dumperp(nullptr)
    , __Vm_activity(false)
    , __Vm_baseCode(0)
Vtop__Syms::Vtop__Syms(VerilatedContext* contextp, Vtop* topp, const char* namep)
    // Setup locals
    : VerilatedSyms{contextp}
    , __Vm_namep(namep)
    , __Vm_dumping(false)
    , __Vm_dumperp(nullptr)
    , __Vm_activity(false)
    , __Vm_baseCode(0)
    , __Vm_didInit(false)
    // Setup submodule names
{
    // Pointer to top level
    TOPp = topp;
    // Setup each module's pointers to their submodules
    // Setup each module's pointer back to symbol table (for public functions)
    TOPp->__Vconfigure(this, true);
    // Setup scopes
    __Vscope_top__sub.configure(this, name(), "top.sub", "sub", -12, VerilatedScope::SCOPE_OTHER);
    __Vscope_top__sub__AssertionExample.configure(this, name(), "top.sub.AssertionExample", "AssertionExample", -12, VerilatedScope::SCOPE_OTHER);
}

また、Vtopクラスの_configura_coverage メソッドは下記のように、// Body のしあに、__vlCoverInsert がいっぱい追加されています。この__vlCoverInsert メソッドがカバレッジのポイントになっているっぽいです。vlSymsp->__Vcoverage[X] に、ファイル名(top.v) の行数およびラベルを設定しています。vlSymsp->__Vcoverage[X]がカバレッジポイントになるようですね。

void Vtop::_configure_coverage(Vtop__Syms* __restrict vlSymsp, bool first) {
    VL_DEBUG_IF(VL_DBG_MSGF("+    Vtop::_configure_coverage\n"); );
    // Body
    if (false && vlSymsp && first) {}  // Prevent unused
    __vlCoverInsert(&(vlSymsp->__Vcoverage[0]), first, "top.v", 14, 23, ".top", "v_toggle/top", "clk", "");
    __vlCoverInsert(&(vlSymsp->__Vcoverage[1]), first, "top.v", 15, 23, ".top", "v_toggle/top", "reset_l", "");
    __vlCoverInsert(&(vlSymsp->__Vcoverage[2]), first, "top.v", 17, 23, ".top", "v_toggle/top", "out_small[0]", "");
    __vlCoverInsert(&(vlSymsp->__Vcoverage[3]), first, "top.v", 17, 23, ".top", "v_toggle/top", "out_small[1]", "");

__vlCoverInsertメソッドは次のようになっています。

// Coverage
void Vtop::__vlCoverInsert(uint32_t* countp, bool enable, const char* filenamep, int lineno, int column,
    const char* hierp, const char* pagep, const char* commentp, const char* linescovp) {
    uint32_t* count32p = countp;
    static uint32_t fake_zero_count = 0;
    if (!enable) count32p = &fake_zero_count;
    *count32p = 0;
    VL_COVER_INSERT(__VlSymsp->_vm_contextp__->coveragep(), count32p,  "filename",filenamep,  "lineno",lineno,  "column",column,
        "hier",std::string(name())+hierp,  "page",pagep,  "comment",commentp,  (linescovp[0] ? "linescov" : ""), linescovp);
}

trace

波形ダンプ関連も coverage と同様に、--trace オプションを指定した時のみ生成されるようです。examples/make_tracing_c の Vtop.h の中を見ると、下記のような traceXXX というメソッドが追加されています。

    void _traceDump();
    void _traceDumpOpen();
    void _traceDumpClose();

  private:
    static void traceChgSub0(void* userp, VerilatedVcd* tracep);
    static void traceChgTop0(void* userp, VerilatedVcd* tracep);
    static void traceCleanup(void* userp, VerilatedVcd* /*unused*/);
    static void traceFullSub0(void* userp, VerilatedVcd* tracep) VL_ATTR_COLD;
    static void traceFullTop0(void* userp, VerilatedVcd* tracep) VL_ATTR_COLD;
    static void traceInitSub0(void* userp, VerilatedVcd* tracep) VL_ATTR_COLD;
    static void traceInitTop(void* userp, VerilatedVcd* tracep) VL_ATTR_COLD;
    void traceRegister(VerilatedVcd* tracep) VL_ATTR_COLD;
    static void traceInit(void* userp, VerilatedVcd* tracep, uint32_t code) VL_ATTR_COLD;

Verilog HDL コード (top.v) の initial 文の中で、$dumpfile システムタスク と $dumpvars システムタスクを呼んでいます。

   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

生成されたC++コードにおいて、 initial 文の中で、$dumpfile システムタスク と $dumpvars システムタスクに対応する部分が_initial__TOP__1 メソッドの中にあります。
if (VL_UNLIKELY*1;; になります。__Vtemp1には、”logs/vlt_dump.vcd” が設定されています。$dumpvars();がvlSymsp->Topp->_traceDumpOpen(); に対応しているのでしょう!

ちなみに、++(vlSymsp->__Vcoverage[226]);のようなコードは、coverage のところで説明したカバレッジポイントでこの行を実行すると実行数をインクリメントする感じになっています。

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;
    // Variables
    WData/*159:0*/ __Vtemp1[5];
    // Body
    if (VL_UNLIKELY((0U != VL_TESTPLUSARGS_I("trace")))) {
        __Vtemp1[0U] = 0x2e766364U;
        __Vtemp1[1U] = 0x64756d70U;
        __Vtemp1[2U] = 0x766c745fU;
        __Vtemp1[3U] = 0x6f67732fU;
        __Vtemp1[4U] = 0x6cU;
        vlSymsp->_vm_contextp__->dumpfile(VL_CVT_PACK_STR_NW(5, __Vtemp1));
        vlSymsp->TOPp->_traceDumpOpen();
        ++(vlSymsp->__Vcoverage[226]);
        VL_WRITEF("[%0t] Tracing to logs/vlt_dump.vcd...\n\n",
                  64,VL_TIME_UNITED_Q(1));
    } else {
        ++(vlSymsp->__Vcoverage[227]);
    }
    VL_WRITEF("[%0t] Model running...\n\n",64,VL_TIME_UNITED_Q(1));
    ++(vlSymsp->__Vcoverage[228]);
}

_traceDumpOpen メソッドは、Vtop__Trace__Slow.cpp ファイル内で定義されています。この中で VCDファイルをオープンしています。

void Vtop::_traceDumpOpen() {
    const VerilatedLockGuard lock(__VlSymsp->__Vm_dumperMutex);
    if (VL_UNLIKELY(!__VlSymsp->__Vm_dumperp)) {
        __VlSymsp->__Vm_dumperp = new VerilatedVcdC();
        trace(__VlSymsp->__Vm_dumperp, 0, 0);
        std::string dumpfile = __VlSymsp->_vm_contextp__->dumpfile();
        __VlSymsp->__Vm_dumperp->open(dumpfile.c_str());
        __VlSymsp->__Vm_dumping = true;
    }
}

おわりに

今回は、

  • timescle
  • coverage
  • trace

について、見てみました。

coverage と trace については、--coverage オプション、--trace オプション を付けて、verilator コマンドを実行しないと、対応するコードが生成されないんですね。
そうすることでシミュレーション速度の低下を防いでいるんですね。。。

*1:0U != VL_TESTPLUSARGS_I("trace")))) {の部分が、 if ($test$plusargs("trace") != 0) begin に対応しています。$dumpfile("logs/vlt_dump.vcd");に対応するのがvlSymsp->_vm_contexp__->dumpfile(VL_CVT_PACK_STR_NW(5,__Vtemp1

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++ コードでまだ見ていないメソッドを覗いてみます。

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 にありましたので。

こうご期待。。。

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

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

みんな?は、Verilatorの何を知りたいのか?

いろいろと調べたものをどうするか?ということで、下記のようなツイートをして、アンケートを取りました。投票数は、109票。それなりの需要はありそうですね。

とはいっても、Verilatorは Verilog HDL/SystemVerilog のシミュレータです。ただし、以下のような制約があります。

  • #xxx のような遅延は無視される
  • @(posged clk); のような、イベント待ちができない
  • Verilog HDL/SystemVerilog コードはデザイン側のみで、テストベンチ側には使えない
  • テストベンチ側は、C++ あるいは SystemC++ で記述する必要がある

最初の 遅延と、2番目の イベント待ちができないということは、SystemVerilog の DPI-C の export task で BFM(Bus Functional Model)でのサイクルを発生するような記述ができないということです。これ、結構辛いです。。。今まで、SystemVerilog DPI-C の export task を使って、Software Driven Verifiaction やりましょう!とか、言っていたので、それができないのです。

Verilator の中を調べる

では、どんなことをすれば、Software Driven Verification ができるようになるのか? そのために、Verilator の中を調べる必要があります。
Verilator は、Verilog HDL/SystemVerilog のコードを C++コードに変換します。その生成された C++コードの中を見ることで何かわかるかな?と思い、
example にあるコードを 使って、Verilator の 中を調べることにしました。

おわりに

明日から、Verilator の中を調べた結果をブログにアップすることにします。
どのまで深く書くかは、わかりませんが、

こうご期待を。。。

Cloud用AIチップベンチャーへの投資額

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

はじめに

記録のために、

昨年までにチップ、ボード、システムを公開したCloud用AIチップベンチャーへの投資額を調べてみました。

Cloud用AIチップベンチャーへの投資額、下記のツイートのように、4社で$2527Mです。凄いです。

4社はすべて、チップ、ボード、システムを公開しています。

Simple Machie Inc. に関しては、チップとボードのみ公開しています。

おわりに

勉強会で最先端プロセスでチップ作るには、100憶円無いとね。とか言っていたんですが、100憶円は開発費で、商品を出すには+数百憶円必要なんですよね。
おまけに、次のチップも開発しなくてはいけないので。。。ということで、半導体開発でビジネスをするということは、一声、1000憶円、必要ということですかね。。

GTC 2021 の Keynote を眺めてみて、思ったこと

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

はじめに

昨年のGTC 2020からリモート開催になり、Jensen キッチンが舞台となった GTC 2021 の Keynote を眺めて見ました。

GTC 2021 Keynote with NVIDIA CEO Jensen Huang

www.youtube.com

とりあえず、ツイートしたもの

GRACEの部分をYoutubeから引用します

GPU が 8,000 GB/sec, CPU が 500 GB/sec, PCIE Gen4 が 16 GB/sec に対して、
NVLink が 500 GB/sec, Mem-to-GPU を 2,000 GB/sec を目指すと。となると、NVLink の 500 GB/sec => Mem-to-GPU の 2,000 GB/sec で4倍か。
あれ、各GRACEにLPDDR5xが繋がるので、4つのGRACEなので、NVLink 500 * 4 = 2,000 GB/sec じゃん。NVLink だよー。。。

f:id:Vengineer:20210413111916p:plain

下記の記事に、Grace + GPU のボードの写真が載っていますね。

NVIDIAがサーバーなどでGPUGPUを接続するインターコネクトとして導入しているNVLinkの次世代版が搭載されており、キャッシュコヒーレントに対応したNVLinkを利用した場合、CPUとGPU間の帯域幅は900GB/秒、キャッシュコヒーレントを使わない場合には600GB/秒の帯域を実現する。

とあるので、NVLink を使うというのは、Bingo ですね。

cloud.watch.impress.co.jp

おわりに

このツイートにも書きましたが、マルチコア、GPGPUFPGAに関しては、IntelAMDが全部やることになってしまいましたが、

NVIDIAは、GPGPU、Network、マルチコア、を1つのチップ。。。NVIDIAのATLAN、に入れちゃうみたいですね。

とはいえ、出てくるのは 2022、2023、2024 なので、その間に何があるかはわかりません。

参考ブログ:Orin は 2018のGTCに発表があり、出てくるのが2022とか、超長いのね。ATLANも2024なので、3年後。
vengineer.hatenablog.com

vfio (Virtual Funtion I/O) と SystemC を繋げる (その2)

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

はじめに

前回は、QEMU 上で起動した Ubuntu cloud image に SystemC/Verilatorをインストールし、SystemC側のモデルをビルド後、vfio-pciバイスを登録しました。
今回は、SystemC側のモデル、test-pcie-ep-master-vfio.cc の中を見ていきます。

test-pcie-ep-master-vfio.cc の内容

test-pcie-ep-master-vfio.cc のブロック図を下記に示します。

f:id:Vengineer:20210404133913p:plain

図の右側にある vfio_dev が vfio と接続することになります。

vfio_dev

vfio_dev は、ここ で定義されています。

vfio_dev の constructor は、下記のようになっていて、/dev/vfio/vfio および /dev/vfio/3 (I/O MMUのグループ、3)を open して、いろいろとやっています。vfio_pciバイスにして何をしなければいけないのかは、こちら に説明があるので、確認してみてください。

vfio_dev::vfio_dev(const char *devname, int iommu_group)
{
  ..... 途中略

	container = open("/dev/vfio/vfio", O_RDWR);
	if (container < 0) {
		printf("Failed to open /dev/vfio/vfio, %d (%s)\n",
				container, strerror(errno));
		print_vfio_iommu_err();
		goto error;
	}

	snprintf(path, sizeof(path), "/dev/vfio/%d", iommu_group);
	group = open(path, O_RDWR);
	if (group < 0) {
		printf("Failed to open %s, %d (%s)\n",
				path, group, strerror(errno));
		print_vfio_device_id_err();
		print_vfio_iommu_err();
		goto error;
	} 
  .....
};

tlm2vfio_bridge

vfio_dev は、SystemCのモジュールではありません。。vfio_dev は、下記のように tlm2vfio_bridge の constructor にて bindされています。

tlm2vfio_bridge::tlm2vfio_bridge(sc_module_name name,
		int nr_sockets,
		class vfio_dev& dev,
		int region, uint64_t offset,
		bool handle_irq) :
	sc_module(name),
	tgt_socket("tgt-socket", nr_sockets),
	irq("irq"),
	dev(dev),
	offset(offset),
	region(region),
	event("ev"),
	irq_val(false),
	irq_dummy("irq-dummy"),
	handle_irq(handle_irq)

vfio_dev が使われるのは、

  • b_transport
  • get_direct_mem_ptr
  • irq_ack
  • irq_poll

です。b_transport と get_direct_mem_ptr は tlm2vfio が target デバイスとして動作する場合に使われます。また、irq_ackは、b_transport 内で使われています。
一方、irq_poll は、poll_trampoline という static な関数内で使われています。

poll_trampoline 関数は、tlm2vfio_bridge の constractor 内で thread として起動しています。引数は自分自身(this) です。

	if (handle_irq) {
		SC_THREAD(irq_proxy);
		pthread_create(&thread, NULL, poll_trampoline, this);
	}

irq_proxy は、下記のようになっていて、割り込み信号 irq の出力を event によって制御しています。

void tlm2vfio_bridge::irq_proxy(void)
{
	while (true) {
		irq.write(irq_val);
		wait(event);
	}
}

ここまでに見てきたことから、vfio_dev は tlm2vfio_bridge 経由で割り込み付の target device の働きをするようです。

test-pcie-ep-master-vfio

test-pcie-ep-master-vfio コマンドによって、SystemC側のモデルを起動します。この時に、デバイス、I/O MMU のグループ等を引数に渡しています。実行すると以下のようなメッセージが表示されます。
sudo が必要なのは、/dev/vfio/ ファイルを test-pcie-ep-master-vfio の中でアクセスしているためです。sudo コマンドを使わないでもいいようにするには、/dev/vfio/ を chown にて所有者を変えればいいですが、それはちょっとね。

$ sudo ./test-pcie-ep-master-vfio 0000:01:00.0 3 0

        SystemC 2.3.2-Accellera --- Jan  8 2021 14:33:28
        Copyright (c) 1996-2017 by all Contributors,
        ALL RIGHTS RESERVED
Device supports 9 regions, 5 irqs
mapped 0 at 0x7fb2352b3000

Info: (I702) default timescale unit used for tracing: 1 ps (./test-pcie-ep-master-vfio.vcd)
Bridge ID c3a89fe1
Position 0
version=100
type=12 pcie-axi-master
Bridge version 1.0
Bridge data-width 128
Bridge nr-descriptors: 16
--------------------------------------------------------------------------------
[15 us]

Write : 0x0, length = 16384, streaming_width = 16384

data = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0....

上記のログの内容:
Bridge ID から Bridge nr-descriptors: までは、tlm_hw_bridge_base の bridge_probe メソッドにて出力されているようです。tlm2axi_hw_bridge の親クラスが tlm_hw_bridge_base なので、tlm2axi_hw_bridge のインスタンスの tlm_hw_bridge が出力しているっぽいですね。

Write: 0x0, length = ... の部分は、tg-tlm.h の debugWrite メソッドからのようです。

最初のブロック図を再度見てみましょう。左側のTLMTrafficGeneratorが tg-tlm.h の debugWriteメソッドを実行しています。

f:id:Vengineer:20210404133913p:plain

TLMTrafiicGererator に送られてくる traffic は、TLMTrafficGenerator の上の RandomTraffic によって生成されます。下記のようなコードで RandonTraffic のインスタンス rand_xfers を TLMTrafficGeneratorのインスタンス tgに addTransfers で割り当てています。

		rand_xfers = new RandomTraffic(0, ram_size, UINT64_MAX, 1,
						ram_size, ram_size, 40000);

		// Wire up the clock and reset signals.
		tlm_hw_bridge.rst(rst);

		rand_xfers->setInitMemory(true);
		rand_xfers->setMaxStreamingWidthLen(ram_size);

		tg.enableDebug();
		tg.addTransfers(*rand_xfers, 0, DoneCallback);
		tg.setStartDelay(sc_time(15, SC_US));

生成された Trans は、splitter によって、memory あるいは、tlm2axi_hw_bridge に渡されます。tlm2axi_hw_bridge にて適当さサイズの trans に分割され、tlm2vfio_bridge に送られ、vfio_dev に bind された vfio_pci から mmap された PCIe の BAR[x] のメモリ空間にアクセスしています。

おわりに

今回紹介したSystemC側のモデルから vfio (vfio_pci) 経由にて実際のPCIe デバイスにアクセスできます。SystemCから実際のデバイスにアクセスできて何が嬉しいって?

まー、そこは 何かの?目的があったからだからでしょうね。