FPGA開発日記

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

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

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.cppvisit()に戻ってくるわけだ。

    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();