FPGA開発日記

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

Verilatorのコンパイルフローを観察する (4. Astのダンプについて)

VerilatorがSystem Verilogファイルを字句解析、構文解析した後は、V3AstNodeというクラスに構造的に組み上げる。これをダンプしているのが以下のログファイルに相当する。

Verilator Tree Dump (format 0x3900) from <e0> to <e94>
     NETLIST 0x7fffe4eb4f40 <e1#> {a0aa}  $root [1ps/1ps]
    1: MODULE 0x7fffe4eca660 <e94#> {c1ai}  simple_assign  L0 [1ps]
    1:2: PORT 0x7fffe4ecb3d0 <e19#> {c3az}  in1
    1:2: VAR 0x7fffe4ecb8d0 <e22#> {c3az} @dt=0@  in1 INPUT PORT
    1:2:1: BASICDTYPE 0x7fffe4ecb4b0 <e21#> {c3ak} @dt=this@(nw1)  logic kwd=logic
    1:2:1:1: RANGE 0x7fffe4ec4f50 <e18#> {c3ar}
    1:2:1:1:2: CONST 0x7fffe4ecb590 <e16#> {c3at} @dt=0x7fffe4ecad20@(G/swu32/3)  ?32?sh4
    1:2:1:1:3: CONST 0x7fffe4ecb720 <e17#> {c3aw} @dt=0x7fffe4ecb190@(G/swu32/1)  ?32?sh0
    1:2: PORT 0x7fffe4ecc5e0 <e50#> {c4az}  in2
    1:2: VAR 0x7fffe4eccdf0 <e49#> {c4az} @dt=0@  in2 INPUT PORT
    1:2:1: BASICDTYPE 0x7fffe4ecc6c0 <e48#> {c4ak} @dt=this@(nw1)  logic kwd=logic
    1:2:1:1: RANGE 0x7fffe4ecca50 <e45#> {c4ar}
    1:2:1:1:2: CONST 0x7fffe4eccb10 <e43#> {c4at} @dt=0x7fffe4ecad20@(G/swu32/3)  ?32?sh4
    1:2:1:1:3: CONST 0x7fffe4eccc80 <e44#> {c4aw} @dt=0x7fffe4ecb190@(G/swu32/1)  ?32?sh0
    1:2: PORT 0x7fffe4ecdb00 <e78#> {c5az}  out
    1:2: VAR 0x7fffe4ece0c0 <e77#> {c5az} @dt=0@  out OUTPUT PORT
    1:2:1: BASICDTYPE 0x7fffe4ecdbe0 <e76#> {c5al} @dt=this@(nw1)  logic kwd=logic
    1:2:1:1: RANGE 0x7fffe4ecdcc0 <e73#> {c5ar}
    1:2:1:1:2: CONST 0x7fffe4ecdd80 <e71#> {c5at} @dt=0x7fffe4ecad20@(G/swu32/3)  ?32?sh4
    1:2:1:1:3: CONST 0x7fffe4ecdf10 <e72#> {c5aw} @dt=0x7fffe4ecb190@(G/swu32/1)  ?32?sh0
    1:2: ASSIGNW 0x7fffe4ece860 <e93#> {c8am} @dt=0@
    1:2:1: AND 0x7fffe4ece7a0 <e91#> {c8as} @dt=0@
    1:2:1:1: PARSEREF 0x7fffe4ece540 <e88#> {c8ao}  in1 [TEXT]
    1:2:1:2: PARSEREF 0x7fffe4ece6c0 <e89#> {c8au}  in2 [TEXT]
    1:2:2: PARSEREF 0x7fffe4ece3c0 <e92#> {c8ai}  out [TEXT]
    3: TYPETABLE 0x7fffe4eb55e0 <e2#> {a0aa}
                detailed  ->  BASICDTYPE 0x7fffe4ecb190 <e13#> {c3aw} @dt=this@(G/swu32/1)  logic [GENERIC] kwd=logic range=[31:0]
                detailed  ->  BASICDTYPE 0x7fffe4ecad20 <e8#> {c3at} @dt=this@(G/swu32/3)  logic [GENERIC] kwd=logic range=[31:0]
    3:1: BASICDTYPE 0x7fffe4ecad20 <e8#> {c3at} @dt=this@(G/swu32/3)  logic [GENERIC] kwd=logic range=[31:0]
    3:1: BASICDTYPE 0x7fffe4ecb190 <e13#> {c3aw} @dt=this@(G/swu32/1)  logic [GENERIC] kwd=logic range=[31:0]

まずは手始めにこれがどのように実現されているのかを観察してみる。

V3AstNodeクラスの実体はV3Ast.hに定義されている。

  • src/V3Ast.h
class AstNode VL_NOT_FINAL {
    // v ASTNODE_PREFETCH depends on below ordering of members
    AstNode* m_nextp;  // Next peer in the parent's list
    AstNode* m_backp;  // Node that points to this one (via next/op1/op2/...)
    AstNode* m_op1p;  // Generic pointer 1
...

このクラスをダンプするメソッドもAstNode自体に定義されている。

void AstNode::dumpTree(std::ostream& os, const string& indent, int maxDepth) const {
    static int s_debugFileline = v3Global.opt.debugSrcLevel("fileline");  // --debugi-fileline 9
    os << indent << " " << this << '\n';
    if (debug() > 8) {
        os << indent << "     ";
        dumpPtrs(os);
    }
    if (s_debugFileline >= 9) os << fileline()->warnContextSecondary();
    if (maxDepth == 1) {
        if (op1p() || op2p() || op3p() || op4p()) os << indent << "1: ...(maxDepth)\n";
    } else {
        for (const AstNode* nodep = op1p(); nodep; nodep = nodep->nextp()) {
            nodep->dumpTree(os, indent + "1:", maxDepth - 1);
        }
        for (const AstNode* nodep = op2p(); nodep; nodep = nodep->nextp()) {
            nodep->dumpTree(os, indent + "2:", maxDepth - 1);
        }
        for (const AstNode* nodep = op3p(); nodep; nodep = nodep->nextp()) {
            nodep->dumpTree(os, indent + "3:", maxDepth - 1);
        }
        for (const AstNode* nodep = op4p(); nodep; nodep = nodep->nextp()) {
            nodep->dumpTree(os, indent + "4:", maxDepth - 1);
        }
    }
}

dumpTree()再帰的に見ているのは見ての通りなのだが、カギになるのはos << indent << " " << this << '\n';の部分で、クラスの内部構造をダンプできるように<<をオーバライドしている。なるほど、こうやって使うのか。

  • src/V3Ast.h
inline std::ostream& operator<<(std::ostream& os, const AstNode* rhs) {
    if (!rhs) {
        os << "nullptr";
    } else {
        rhs->dump(os);
    }
    return os;
}
  • src/V3AstNodes.cpp
void AstNode::dump(std::ostream& str) const {
    str << typeName() << " "
        << nodeAddr(this)
        //<< " " << nodeAddr(m_backp)
        << " <e" << std::dec << editCount() << ((editCount() >= editCountLast()) ? "#>" : ">")
        << " {" << fileline()->filenameLetters() << std::dec << fileline()->lastLineno()
        << fileline()->firstColumnLetters() << "}";
    if (user1p()) str << " u1=" << nodeAddr(user1p());
    if (user2p()) str << " u2=" << nodeAddr(user2p());
    if (user3p()) str << " u3=" << nodeAddr(user3p());
    if (user4p()) str << " u4=" << nodeAddr(user4p());
    if (user5p()) str << " u5=" << nodeAddr(user5p());
    if (hasDType()) {
        // Final @ so less likely to by accident read it as a nodep
        if (dtypep() == this) {
            str << " @dt=this@";
        } else {
            str << " @dt=" << nodeAddr(dtypep()) << "@";
        }
        if (AstNodeDType* dtp = dtypep()) dtp->dumpSmall(str);
    } else {  // V3Broken will throw an error
        if (dtypep()) str << " %Error-dtype-exp=null,got=" << nodeAddr(dtypep());
    }
    if (name() != "") {
        if (VN_IS(this, Const)) {
            str << "  " << name();  // Already quoted
        } else {
            str << "  " << V3OutFormatter::quoteNameControls(name());
        }
    }

この全体になって先ほどのダンプファイルを読んでみると以下のように解釈すればいいことが分かる。

f:id:msyksphinz:20210403001139p:plain