GDBを当てて動作を観察してみた。Verilatorはデバッグ版とリリース版がビルドされるので、デバッグ版を使用する。
$ gdb ./bin/verilator_bin_dbg
EmitCImp::visit()
にブレークポイントを張って動作を観察する。
b EmitCImp::visit run --cc --debug ../tiny_assign/simple_assign.sv bt
以下のようなトレースが出力された。
#0 EmitCImp::visit (this=0x7ffffffed060, nodep=0x8d39030) at ../V3EmitC.cpp:1512 #1 0x000000000826e1aa in AstCFunc::accept (this=0x8d39030, v=...) at ../V3AstNodes.h:8882 #2 0x0000000008221b9c in AstNVisitor::iterate (this=0x7ffffffed060, nodep=0x8d39030) at ../V3Ast.h:2939 #3 0x00000000083a5c4d in EmitCImp::mainDoFunc (this=0x7ffffffed060, nodep=0x8d39030) at ../V3EmitC.cpp:1872 #4 0x0000000008388ce1 in EmitCImp::emitImp (this=0x7ffffffed060, fileModp=0x8d25ef0, modp=0x8d25ef0) at ../V3EmitC.cpp:3379 #5 0x000000000838924a in EmitCImp::mainImp (this=0x7ffffffed060, modp=0x8d25ef0, slow=true) at ../V3EmitC.cpp:3431 #6 0x00000000083895a9 in V3EmitC::emitc () at ../V3EmitC.cpp:3928 #7 0x00000000082105f6 in process () at ../Verilator.cpp:515 #8 0x0000000008210ddc in verilate (argString=...) at ../Verilator.cpp:586
V3EmitC::emitc()
から始めて見る。次にmainImp()
を呼び出す。これは前回すでにトレースした。
verilator/src/V3EmitC.cpp
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.mainImp()
を見てみる。前回emitImp()
の部分は解析した。
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(fileModp, modp); // 次にemitImp()を解析する
emitImp()
を見てみる。mainDoFunc()
を見てみる。
void EmitCImp::emitImp(AstNodeModule* fileModp, AstNodeModule* modp) { puts("\n//==========\n"); if (m_slow) { string section; emitVarList(modp->stmtsp(), EVL_CLASS_ALL, prefixNameProtect(modp), section /*ref*/); /* ... 中略 ... */ // Blocks for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) { if (AstCFunc* funcp = VN_CAST(nodep, CFunc)) { maybeSplit(fileModp); mainDoFunc(funcp); // mainDFuncを解析する } } }
mainDoFunc()
はすべてのノードに対してイタレーションを行う。
void mainDoFunc(AstCFunc* nodep) { iterate(nodep); }
staticに解析したときにはあまり自信が無かったのだが、iterate()
によってnodep
がそれぞれ解析されaccept()
が呼び出されるようだ。
verilator/src/V3Ast.h
inline void AstNVisitor::iterate(AstNode* nodep) { nodep->accept(*this); }
accept()
はV3AstNodes.h
で定義されているようだった。
#define ASTNODE_NODE_FUNCS_NO_DTOR(name) \ virtual void accept(AstNVisitor& v) override { v.visit(this); } \ virtual AstNode* clone() override { return new Ast##name(*this); } \ static Ast##name* cloneTreeNull(Ast##name* nodep, bool cloneNextLink) { \ return nodep ? nodep->cloneTree(cloneNextLink) : nullptr; \ } \ Ast##name* cloneTree(bool cloneNext) { \ return static_cast<Ast##name*>(AstNode::cloneTree(cloneNext)); \ } \ Ast##name* clonep() const { return static_cast<Ast##name*>(AstNode::clonep()); }
これでV3EmitC.cpp
のvisit()
に戻ってくるわけだ。
virtual void visit(AstCFunc* nodep) override { // TRACE_* and DPI handled elsewhere if (nodep->funcType().isTrace()) return; if (nodep->dpiImport()) return; /* ... 中略 ... */ if (nodep->stmtsp()) putsDecoration("// Body\n"); iterateAndNextNull(nodep->stmtsp()); if (!m_blkChangeDetVec.empty()) emitChangeDet(); /* ... 中略 ... */
iterateAndNextNull()
でステートメントに対して処理を適用する。
verilator/src/V3Ast.h
inline void AstNVisitor::iterateAndNextNull(AstNode* nodep) { if (VL_LIKELY(nodep)) nodep->iterateAndNext(*this); }
verilator/src/V3Ast.cpp
void AstNode::iterateAndNext(AstNVisitor& v) { // This is a very hot function // IMPORTANT: If you replace a node that's the target of this iterator, // then the NEW node will be iterated on next, it isn't skipped! // Future versions of this function may require the node to have a back to be iterated; // there's no lower level reason yet though the back must exist. AstNode* nodep = this; #ifdef VL_DEBUG // Otherwise too hot of a function for debug UASSERT_OBJ(!(nodep && !nodep->m_backp), nodep, "iterateAndNext node has no back"); #endif if (nodep) ASTNODE_PREFETCH(nodep->m_nextp); while (nodep) { // effectively: if (!this) return; // Callers rely on this if (nodep->m_nextp) ASTNODE_PREFETCH(nodep->m_nextp->m_nextp); AstNode* niterp = nodep; // Pointer may get stomped via m_iterpp if the node is edited // Desirable check, but many places where multiple iterations are OK
例えば、assign out = in_a & in_b;
の場合は以下のコードが実行されていることが分かった。再びBodyが呼び出されている。
verilator/src/V3EmitC.cpp
//--------------------------------------- // VISITORS using EmitCStmts::visit; // Suppress hidden overloaded virtual function warning virtual void visit(AstCFunc* nodep) override { // TRACE_* and DPI handled elsewhere if (nodep->funcType().isTrace()) return; /* ... 中略 ... */ puts("VL_DEBUG_IF(VL_DBG_MSGF(\"+ "); for (int i = 0; i < m_modp->level(); ++i) { puts(" "); } puts(prefixNameProtect(m_modp) + "::" + nodep->nameProtect() + "\\n\"); );\n"); /* ... 中略 ... */ if (nodep->stmtsp()) putsDecoration("// Body\n"); iterateAndNextNull(nodep->stmtsp()); if (!m_blkChangeDetVec.empty()) emitChangeDet();