少し気になってVerilatorの中味を調査している。VerilatorはSystem Verilogのファイルを読み込んでそれをC++に変換しシミュレーションするためのツールだが、VerilogのファイルがどのようなC++ファイルに変換されているのか気になったので見てみることにした。
このモチベーションは、割と巨大なプロジェクトをVerilatorで読み込むと変換すらできなくなってしまうくらいにVerilatorが重たくなってしまうことだ。これの原因が何なのか、原因が分かればラッキーだし、Verilatorのシミュレーションの仕組みについても見てみたい。
例として、以下のような非常にシンプルなデザインを作成した。クロックは存在せず、単純にin
からout
へ接続されているだけだ。
module simple_assign ( input logic [ 4: 0] in, output logic [ 4: 0] out ); assign out = in; endmodule // simple_assign
これにWrapperを付けて、Verilatorでコンパイルする。すると、obj_dir
に生成C++ファイルとオブジェクトファイルが生成される。
make verilator --cc --exe simple_assign.sv -exe tb_simple_assign.cpp make -C obj_dir -f Vsimple_assign.mk
これの中味を見てみよう。まずはobj_dir/Vsimple_assign.h
から。これにはC++クラスが定義されている。
// VL_MODULEはverilated.hに定義されている: // /// Declare a module, ala SC_MODULE // #define VL_MODULE(modname) class modname : public VerilatedModule VL_MODULE(Vsimple_assign) { public: // VL_IN8とVL_OUT8はverilated.hに定義されている: // typedef vluint8_t CData; ///< Verilated pack data, 1-8 bits // #define VL_IN8(name, msb, lsb) CData name ///< Declare input signal, 1-8 bits // #define VL_OUT8(name, msb, lsb) CData name ///< Declare output signal, 1-8 bits VL_IN8(in,4,0); VL_OUT8(out,4,0); // INTERNAL VARIABLES // Internals; generally not touched by application code Vsimple_assign__Syms* __VlSymsp; // Symbol table // CONSTRUCTORS private: VL_UNCOPYABLE(Vsimple_assign); ///< Copying not allowed
eval()
メソッドはpublicとして公開されており、実体はeval_step()
を呼び出している。eval_step()
は論理が落ち着くまで(100回程度)回路の評価を繰り返している。
評価の中味はおそらく_eval()
の中なので、これを覗いてみよう。
void Vsimple_assign::eval_step() { /* ... 中略 ... */ 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("simple_assign.sv", 1, "", "Verilated model didn't converge\n" "- See DIDNOTCONVERGE in the Verilator manual"); } else { __Vchange = _change_request(vlSymsp); } } while (VL_UNLIKELY(__Vchange));
_eval()
の中では、組み合わせ回路を動かすための記述_combo__TOP__1()
が呼び出されている。
void Vsimple_assign::_eval(Vsimple_assign__Syms* __restrict vlSymsp) { VL_DEBUG_IF(VL_DBG_MSGF("+ Vsimple_assign::_eval\n"); ); Vsimple_assign* const __restrict vlTOPp VL_ATTR_UNUSED = vlSymsp->TOPp; // Body vlTOPp->_combo__TOP__1(vlSymsp); }
内部では、out
変数のポインタに対してin
変数のポインタの実体をコピーしている論理が見えた。これがassign)
の記述となっている。
VL_INLINE_OPT void Vsimple_assign::_combo__TOP__1(Vsimple_assign__Syms* __restrict vlSymsp) { VL_DEBUG_IF(VL_DBG_MSGF("+ Vsimple_assign::_combo__TOP__1\n"); ); Vsimple_assign* const __restrict vlTOPp VL_ATTR_UNUSED = vlSymsp->TOPp; // Body vlTOPp->out = vlTOPp->in; }
ではこれをout = in1 & in2
のように変更するとどうなるか。生成されるC++ファイルは以下のように変化する。
VL_INLINE_OPT void Vsimple_assign::_combo__TOP__1(Vsimple_assign__Syms* __restrict vlSymsp) { VL_DEBUG_IF(VL_DBG_MSGF("+ Vsimple_assign::_combo__TOP__1\n"); ); Vsimple_assign* const __restrict vlTOPp VL_ATTR_UNUSED = vlSymsp->TOPp; // Body vlTOPp->out = ((IData)(vlTOPp->in1) & (IData)(vlTOPp->in2)); }