FPGA開発日記

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

Verilatorのコンパイルフローを観察する (6. 007_JumpLinkのソースコードを読む)

whileブロックの内部解析続き。break文がどのように分解されているのかを観察する。以下のようなVerilogファイルを題材に観察している。

module while_jump;

int i;
initial begin
  while(i < 100) begin
    if (i == 10) break;
    i++;
  end
end
endmodule

これらがAstNodeに変換される処理はここでは置いておくとして、breakをどのように変換するのかを確かめている。これにはV3LinkJump.cppを観察するのが良さそうだ。

まず、visit()が大量に定義されている。これはAstNode()の種類に応じて処理を切り替えるために定義されているらしい。

119:    // VISITORS
120:    virtual void visit(AstNodeModule* nodep) override {
130:    virtual void visit(AstNodeFTask* nodep) override {
135:    virtual void visit(AstNodeBlock* nodep) override {
145:    virtual void visit(AstRepeat* nodep) override {
173:    virtual void visit(AstWait* nodep) override {
184:    virtual void visit(AstWhile* nodep) override {
198:    virtual void visit(AstReturn* nodep) override {
226:    virtual void visit(AstBreak* nodep) override {
238:    virtual void visit(AstContinue* nodep) override {
251:    virtual void visit(AstDisable* nodep) override {
276:    virtual void visit(AstVarRef* nodep) override {
279:    virtual void visit(AstConst*) override {}
280:    virtual void visit(AstNode* nodep) override { iterateChildren(nodep); }

まず見てみるのは、visit(AstWhile* nodep)だ。ここではmlooppに自分自身のブロックを設定し(つまりこれからwhile文の内部に入るということを意味する。それからwhile文の各ブロックの解析に入る。

     virtual void visit(AstWhile* nodep) override {
         // Don't need to track AstRepeat/AstFor as they have already been converted
         VL_RESTORER(m_loopp);
         VL_RESTORER(m_loopInc);
         {
             m_loopp = nodep;
             m_loopInc = false;
             iterateAndNextNull(nodep->precondsp());
             iterateAndNextNull(nodep->condp());
             iterateAndNextNull(nodep->bodysp());
             m_loopInc = true;
             iterateAndNextNull(nodep->incsp());
         }
     }

そして内部でbreak文に遭遇した場合、visit(AstBreak *nodep)が呼び出される。

     virtual void visit(AstBreak* nodep) override {
         iterateChildren(nodep);
         if (!m_loopp) {
             nodep->v3error("break isn't underneath a loop");
         } else {
             // Jump to the end of the loop
             AstJumpLabel* labelp = findAddLabel(m_loopp, false);
             nodep->addNextHere(new AstJumpGo(nodep->fileline(), labelp));
         }
         nodep->unlinkFrBack();
         VL_DO_DANGLING(pushDeletep(nodep), nodep);
     }

まずm_looppがNULL以外でなければならない。これはループの外部でbreakされることを禁止する。そしてfindAddLabel()を呼び出す。これはwhileループ(m_looppブロック)の直後にラベルを作成する。次のAstJumpGo()でそのラベルにジャンプする文を生成する。

     AstJumpLabel* findAddLabel(AstNode* nodep, bool endOfIter) {
         // Put label under given node, and if WHILE optionally at end of iteration
         UINFO(4, "Create label for " << nodep << endl);
         if (VN_IS(nodep, JumpLabel)) return VN_CAST(nodep, JumpLabel);  // Done

         AstNode* underp = nullptr;
         bool under_and_next = true;
         if (VN_IS(nodep, NodeBlock)) {
             underp = VN_CAST(nodep, NodeBlock)->stmtsp();
         } else if (VN_IS(nodep, NodeFTask)) {
/* ... 中略 ... */
         if (VN_IS(underp, JumpLabel)) {
             return VN_CAST(underp, JumpLabel);
         } else {  // Move underp stuff to be under a new label
             AstJumpBlock* blockp = new AstJumpBlock(nodep->fileline(), nullptr);
             AstJumpLabel* labelp = new AstJumpLabel(nodep->fileline(), blockp);
             blockp->labelp(labelp);

             AstNRelinker repHandle;
             if (under_and_next) {
                 underp->unlinkFrBackWithNext(&repHandle);
             } else {
                 underp->unlinkFrBack(&repHandle);
             }
             repHandle.relink(blockp);

             blockp->addStmtsp(underp);
             // Keep any AstVars under the function not under the new JumpLabel
             for (AstNode *nextp, *varp = underp; varp; varp = nextp) {
                 nextp = varp->nextp();
                 if (VN_IS(varp, Var)) blockp->addPrev(varp->unlinkFrBack());
             }
             // Label goes last
             blockp->addEndStmtsp(labelp);
             return labelp;
         }
     }

最後のaddEndStmtsp()がミソだと思う。while文の最後にラベルを追加し、それを関数の戻り値として返す。それを上記のAstJumpGo()でジャンプするという流れになる。従って生成されるAstは以下のようになる。

    1:2:2:1:1: WHILE 0x7fffecd65e10 <e75#> {c5ad}
    1:2:2:1:1:2: LT 0x7fffecd64740 <e47> {c5al} @dt=0x7fffecd64800@(G/nw1)
    1:2:2:1:1:2:1: VARREF 0x7fffecd5f950 <e55#> {c5aj} @dt=0@  i [RV] <- VAR 0x7fffecd63d30 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
    1:2:2:1:1:2:2: CONST 0x7fffecd64450 <e16> {c5an} @dt=0x7fffecd645c0@(G/swu32/7)  ?32?sh64
    1:2:2:1:1:3: BEGIN 0x7fffecd64be0 <e21> {c5as}
    1:2:2:1:1:3:1: IF 0x7fffecd656f0 <e33> {c6af}
    1:2:2:1:1:3:1:1: EQ 0x7fffecd65430 <e34> {c6al} @dt=0x7fffecd64800@(G/nw1)
    1:2:2:1:1:3:1:1:1: VARREF 0x7fffecd600a0 <e58#> {c6aj} @dt=0@  i [RV] <- VAR 0x7fffecd63d30 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
    1:2:2:1:1:3:1:1:2: CONST 0x7fffecd65100 <e30> {c6ao} @dt=0x7fffecd652b0@(G/swu32/4)  ?32?sha
    1:2:2:1:1:3:1:2: JUMPGO 0x7fffecd60f60 <e78#> {c6as} -> JUMPLABEL 0x7fffecd60e90 <e74#> {c5ad} -> JUMPBLOCK 0x7fffecd60dc0 <e76#> {c5ad}
    1:2:2:1:1:3:1: POSTADD 0x7fffecd65c60 <e45> {c7ag} @dt=0@
    1:2:2:1:1:3:1:1: CONST 0x7fffecd658a0 <e42> {c7ag} @dt=0x7fffecd65a50@(G/wu32/1)  ?32?h1
    1:2:2:1:1:3:1:2: VARREF 0x7fffecd601e0 <e61#> {c7af} @dt=0@  i [RV] <- VAR 0x7fffecd63d30 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
    1:2:2:1:1:3:1:3: VARREF 0x7fffecd60320 <e64#> {c7af} @dt=0@  i [LV] => VAR 0x7fffecd63d30 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
    1:2:2:1:2: JUMPLABEL 0x7fffecd60e90 <e74#> {c5ad} -> JUMPBLOCK 0x7fffecd60dc0 <e76#> {c5ad}

これよく見たら昨日書いたブロック間違っているな。WHILEブロックは分解されるわけじゃないから、JumpGoは2方向への条件分岐ジャンプを定義しているわけではなく、条件が成立すればWHILEの外へ、そうでなければすぐ下のブロックに進むような仕組みなっている。

f:id:msyksphinz:20210406233522p:plain