FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

Verilatorのコンパイルフローを観察する (12. VerilatorがC++のヘッダファイルC++ファイルを出力する仕組み)

VerilatorがVerilogの構成自体をAst形式で格納しているのは理解できた。では、これがどのようにC++に置き換えられているのかを観察することにする。

C++のヘッダファイルと実装ファイルが出力されるのはV3EmitC.hV3EmitC.cppが使用されているようだ。

  • verilator/src/V3EmitC.cpp
 //######################################################################
 // Internal EmitC implementation

 class EmitCImp final : EmitCStmts {
     // MEMBERS

このなかで、どうやらヘッダファイルとソースファイルの出力でメソッドが2つに分かれているようだった。

 //######################################################################
 // EmitC class functions

 void V3EmitC::emitc() {
     UINFO(2, __FUNCTION__ << ": " << endl);
     // Process each module in turn
     for (AstNodeModule* nodep = v3Global.rootp()->modulesp(); nodep;
          nodep = VN_CAST(nodep->nextp(), NodeModule)) {
         if (VN_IS(nodep, Class)) continue;  // Imped with ClassPackage
         // clang-format off
         EmitCImp cint; cint.mainInt(nodep);
         cint.mainImp(nodep, true);
         { EmitCImp fast; fast.mainImp(nodep, false); }
         // clang-format on
     }
 }

cint.mainInt()cint.mainImp()に分けられる。mainInt()はヘッダファイル側、mainImp()がそーそファイル側のようだ。なんでInt()がヘッダファイル側なのかはよく分からない。

 void EmitCImp::mainInt(AstNodeModule* modp) {
     AstNodeModule* fileModp = modp;  // Filename constructed using this module
/* ... 中略 ... */
     emitIntTop(modp);
     emitInt(modp);
 void EmitCImp::mainImp(AstNodeModule* modp, bool slow) {
     // Output a module
     AstNodeModule* fileModp = modp;  // Filename constructed using this module
     m_modp = modp;
     m_slow = slow;
     m_fast = !slow;

     UINFO(5, "  Emitting " << prefixNameProtect(modp) << endl);

     m_ofp = newOutCFile(fileModp, !m_fast, true /*source*/);
     emitImpTop(fileModp);
     emitImp(modp);

それぞれ以下のような構成になっている。

// ソースファイル側
     void emitImpTop(AstNodeModule* modp);
     void emitImp(AstNodeModule* modp);
// ヘッダファイル側
     void emitIntTop(AstNodeModule* modp);
     void emitInt(AstNodeModule* modp);

ヘッダファイル側から見ていく。emitIntTop()は本当にヘッダファイルのヘッダ部分を出力するだけのようだ。

 void EmitCImp::emitIntTop(AstNodeModule*) {
     // Always have this first; gcc has short circuiting if #ifdef is first in a file
     ofp()->putsGuard();
     puts("\n");

     ofp()->putsIntTopInclude();
     if (v3Global.needHeavy()) {
         puts("#include \"verilated_heavy.h\"\n");
     } else {
         puts("#include \"verilated.h\"\n");
     }
     if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n");
     if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n");
     if (v3Global.opt.coverage()) {
         puts("#include \"verilated_cov.h\"\n");
         if (v3Global.opt.savable()) v3error("--coverage and --savable not supported together");
     }
     if (v3Global.dpi()) {
         // do this before including our main .h file so that any references to
         // types defined in svdpi.h are available
         puts("#include \"" + topClassName() + "__Dpi.h\"\n");
     }
 }

emitInt()の方が主役を張っているような関数だが、大きくどのように構成されているのだろうか?

 void EmitCImp::emitInt(AstNodeModule* modp) {
     puts("\n//==========\n\n");
/* ... 中略 ... */

まずはクラス宣言の出力。

     // Declare foreign instances up front to make C++ happy
     puts("class " + symClassName() + ";\n");
     emitModCUse(modp, VUseType::INT_FWD_CLASS);

     puts("\n//----------\n\n");
     emitTextSection(AstType::atScHdr);

     if (AstClass* classp = VN_CAST(modp, Class)) {
         puts("class " + prefixNameProtect(modp));
         if (classp->extendsp())
             puts(" : public " + prefixNameProtect(classp->extendsp()->classp()));
         puts(" {\n");
     } else if (optSystemC() && modp->isTop()) {
         puts("SC_MODULE(" + prefixNameProtect(modp) + ") {\n");
     } else {
         puts("VL_MODULE(" + prefixNameProtect(modp) + ") {\n");
     }

プライベート信号と、入出力ポートはここで宣言するようだ。

  • ポートの宣言
     string section;
     section = "\n// PORTS\n";
     if (modp->isTop()) {
         section += ("// The application code writes and reads these signals to\n"
                     "// propagate new values into/out from the Verilated model.\n");
     }
     emitVarList(modp->stmtsp(), EVL_CLASS_IO, "", section /*ref*/);
  • ローカル変数の宣言
     section = "\n// LOCAL SIGNALS\n";
     if (modp->isTop()) section += "// Internals; generally not touched by application code\n";
     emitVarList(modp->stmtsp(), EVL_CLASS_SIG, "", section /*ref*/);

     section = "\n// LOCAL VARIABLES\n";
     if (modp->isTop()) section += "// Internals; generally not touched by application code\n";
     emitVarList(modp->stmtsp(), EVL_CLASS_TEMP, "", section /*ref*/);

     puts("\n// INTERNAL VARIABLES\n");
     if (modp->isTop()) puts("// Internals; generally not touched by application code\n");
     if (!VN_IS(modp, Class)) {  // Avoid clang unused error (& don't want in every object)
         ofp()->putsPrivate(!modp->isTop());  // private: unless top
         puts(symClassName() + "* __VlSymsp;  // Symbol table\n");
     }
  • 内部信号の宣言
     ofp()->putsPrivate(false);  // public:
     if (modp->isTop()) {
         if (v3Global.opt.inhibitSim()) {
             puts("bool __Vm_inhibitSim;  ///< Set true to disable evaluation of module\n");
         }
         if (v3Global.opt.mtasks()) emitMTaskState();
     }
     emitCoverageDecl(modp);  // may flip public/private
  • パラメータ宣言
     section = "\n// PARAMETERS\n";
     if (modp->isTop())
         section += "// Parameters marked /*verilator public*/ for use by application code\n";
     ofp()->putsPrivate(false);  // public:
     emitVarList(modp->stmtsp(), EVL_CLASS_PAR, "",
                 section /*ref*/);  // Only those that are non-CONST
     bool first = true;
     emitParams(modp, false, &first, section /*ref*/);