FPGA開発日記

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

Verilatorのコンパイルフローを観察する (8. 010_paramLinkについて)

010_paramLinkはSystemVerilogにおけるパラメータの決定を行う。例えば以下のようなデザインをサンプルとして作ってみた。

  • test.sv
module test;

localparam p = 4;

logic [p-1: 0] in;

test2
  #(.param(p * 2),
    .plus_p(9))
u_test2
  (
   .in({in, in}),
   .out()
   );

endmodule

module test2
  #(
    parameter param = 2,
    parameter plus_p = 1
    )
(
 input logic [param-1: 0]    in,
 output logic [param*2-1: 0] out
 );


assign out = {in, in} + plus_p;

endmodule // test2

test2モジュールは2つのパラメータを持っている。paramplus_pだ。これらをparamLinkのステージで決定する。

paramLinkの前までのステージ008_linkIncまでは以下のようなツリーでtestモジュールが構成されている。

    1: MODULE 0x7fffb8b146d0 <e226> {c1ai}  test  L2 [1ps]
    1:2: VAR 0x7fffb8b14da0 <e10> {c3am} @dt=0@  p [VSTATIC]  LPARAM
    1:2:1: BASICDTYPE 0x7fffb8b14cc0 <e11> {c3am} @dt=this@(nw0)  LOGIC_IMPLICIT kwd=LOGIC_IMPLICIT
    1:2:3: CONST 0x7fffb8b0dc60 <e12> {c3aq} @dt=0x7fffb8b14b40@(G/swu32/3)  ?32?sh4
    1:2: VAR 0x7fffb8b16080 <e36> {c5aq} @dt=0@  in [VSTATIC]  VAR
    1:2:1: BASICDTYPE 0x7fffb8b15a60 <e35> {c5ab} @dt=this@(nw1)  logic kwd=logic
    1:2:1:1: RANGE 0x7fffb8b15b40 <e33> {c5ah}
    1:2:1:1:2: SUB 0x7fffb8b15c00 <e31> {c5aj} @dt=0@
    1:2:1:1:2:1: VARREF 0x7fffb8b104f0 <e255#> {c5ai} @dt=0@  p [RV] <- VAR 0x7fffb8b14da0 <e10> {c3am} @dt=0@  p [VSTATIC]  LPARAM
    1:2:1:1:2:2: CONST 0x7fffb8b15da0 <e23> {c5ak} @dt=0x7fffb8b15320@(G/swu32/1)  ?32?sh1
    1:2:1:1:3: CONST 0x7fffb8b15f10 <e32> {c5an} @dt=0x7fffb8b15320@(G/swu32/1)  ?32?sh0
    1:2: CELL 0x7fffb8b18970 <e92> {c10ab}  u_test2 -> MODULE 0x7fffb8b19270 <e228#> {c18ai}  test2  L3 [1ps]
    1:2:1: PIN 0x7fffb8b17e60 <e80> {c12af}  in -> VAR 0x7fffb8b1b410 <e143> {c24be} @dt=0@  in INPUT [VSTATIC]  PORT
    1:2:1:1: REPLICATE 0x7fffb8b17990 <e81> {c12ai} @dt=0x7fffb8b17d30@(G/w0)
    1:2:1:1:1: CONCAT 0x7fffb8b17880 <e74> {c12al} @dt=0@
    1:2:1:1:1:1: VARREF 0x7fffb8b10630 <e258#> {c12aj} @dt=0@  in [RV] <- VAR 0x7fffb8b16080 <e36> {c5aq} @dt=0@  in [VSTATIC]  VAR
    1:2:1:1:1:2: VARREF 0x7fffb8b10770 <e261#> {c12an} @dt=0@  in [RV] <- VAR 0x7fffb8b16080 <e36> {c5aq} @dt=0@  in [VSTATIC]  VAR
    1:2:1:1:2: CONST 0x7fffb8b17a50 <e75> {c12ai} @dt=0x7fffb8b17c00@(G/w32)  32'h1
    1:2:1: PIN 0x7fffb8b18120 <e83> {c13af}  out -> VAR 0x7fffb8b1d270 <e185> {c25be} @dt=0@  out OUTPUT [VSTATIC]  PORT
    1:2:2: PIN 0x7fffb8b18270 <e46> {c8ag}  param -> VAR 0x7fffb8b19810 <e103> {c20ap} @dt=0@  param [VSTATIC]  GPARAM
    1:2:2:1: MUL 0x7fffb8b18370 <e47> {c8ao} @dt=0@
    1:2:2:1:1: VARREF 0x7fffb8b10940 <e264#> {c8am} @dt=0@  p [RV] <- VAR 0x7fffb8b14da0 <e10> {c3am} @dt=0@  p [VSTATIC]  LPARAM
    1:2:2:1:2: CONST 0x7fffb8b18510 <e45> {c8aq} @dt=0x7fffb8b16c40@(G/swu32/2)  ?32?sh2
    1:2:2: PIN 0x7fffb8b186c0 <e55> {c9ag}  plus_p -> VAR 0x7fffb8b19d00 <e117> {c21ap} @dt=0@  plus_p [VSTATIC]  GPARAM
    1:2:2:1: CONST 0x7fffb8b187c0 <e54> {c9an} @dt=0x7fffb8b170e0@(G/swu32/4)  ?32?sh9

よく見なければならいのは、CELLのPINが2つポートとして宣言されている。

f:id:msyksphinz:20210409002501p:plain

その次の010_paramLinkではこれらのポートが静的な値として変更されている。インスタンスの名前がtest_2_P8_PB9と変更されている。

f:id:msyksphinz:20210409002534p:plain

ログには以下のように表示されていた。

- V3Param.cpp:346:    Name: test2->test2__P8_PB9->test2__P8_PB9
- V3Param.cpp:546:         De-parameterize to new: MODULE 0x7fffd93c4ae0 <e332#> {c19ai}  test2__P8_PB9  L3 [1ps]
- V3Param.cpp:579:           set param VAR 0x7fffd93c4c20 <e103> {c21ap} u4=0x50 @dt=0@  param [VSTATIC]  GPARAM = CONST 0x7fffd93c7d50 <e327#> {c8ao} @dt=0x7fffd93c8af0@(G/swu32/3)  32'h8
- V3Param.cpp:579:           set param VAR 0x7fffd93c4ff0 <e117> {c22ap} u4=0x150 @dt=0@  plus_p [VSTATIC]  GPARAM = CONST 0x7fffd93cc790 <e54> {c9an} @dt=0x7fffd93cb0b0@(G/swu32/4)  ?32?sh9
- V3Param.cpp:787:         Done with MODULE 0x7fffd93c4ae0 <e332#> {c19ai}  test2__P8_PB9  L3 [1ps]
- V3Param.cpp:794:         Done with CELL 0x7fffd93cc940 <e92> {c11ab}  u_test2 -> MODULE 0x7fffd93c4ae0 <e332#> {c19ai}  test2__P8_PB9  L3 [1ps]
- V3Param.cpp:871:     MOD-done
- V3Param.cpp:849:     MOD   MODULE 0x7fffd93c4ae0 <e332#> {c19ai} u5=0x1  test2__P8_PB9  L3 [1ps]
- V3Param.cpp:871:     MOD-done
- V3Param.cpp:912:     MOD-dead?  MODULE 0x7fffd93cd240 <e228> {c19ai}  test2  L3 [1ps]
- V3Param.cpp:909:     MOD-done   MODULE 0x7fffd93c4ae0 <e332#> {c19ai} u5=0x1  test2__P8_PB9  L3 [1ps]
     // Make sure all parameters are constantified
     virtual void visit(AstVar* nodep) override {
         if (nodep->user5SetOnce()) return;  // Process once
         iterateChildren(nodep);
         if (nodep->isParam()) {
             if (!nodep->valuep()) {
                 nodep->v3error("Parameter without initial value is never given value"
                                << " (IEEE 1800-2017 6.20.1): " << nodep->prettyNameQ());
             } else {
                 V3Const::constifyParamsEdit(nodep);  // The variable, not just the var->init()
                 if (!VN_IS(nodep->valuep(), Const)
                     && !VN_IS(nodep->valuep(), Unbounded)) {  // Complex init, like an array
                     // Make a new INITIAL to set the value.
                     // This allows the normal array/struct handling code to properly
/* ... 中略 ... */

おそらくV3Const::costifyParamEdit(nodep)おそらくパラメータの定数への変換を行っていると思われる。この辺について深堀していきたい。

Verilatorのコンパイルフローを観察する (7. 008_LinkIncのソースコードを読む)

008_LinkIncVerilogファイル中の++--演算を処理するための関数のようだ。ヘッダファイルの説明書きを見てみよう。

  • src/V3LinkInc.cpp
 // V3LinkInc's Transformations:
 //
 //      prepost_stmt_visit
 //        PREADD/PRESUB
 //          Create a temporary __VIncrementX variable, assign the value of
 //          the current variable value to it, substitute the current
 //          variable with the temporary one in the statement.
 //          Increment/decrement the original variable with by the given
 //          value.
 //        POSTADD/POSTSUB
 //          Increment/decrement the current variable by the given value.
 //          Create a temporary __VIncrementX variable, assign the value of
 //          of the current variable (after the operation) to it. Substitute
 //          The original variable with the temporary one in the statement.
 //      prepost_non_stmt_visit
 //        PREADD/PRESUB/POSTADD/POSTSUB
 //          Increment/decrement the current variable by the given value.
 //          The order (pre/post) doesn't matter outside statements thus
 //          the pre/post operations are treated equally and there is no
 //          need for a temporary variable.

つまり、以下のような操作となる。

j = i++;  // は以下のように変換される。
VIncrementX = i;
i = i + 1;
j = VIncrementX;
i++; // は以下のように変換される
i = i + 1;

つまり、ステートメントの中では一度中間変数に値を格納してから変数のアップデート処理を行う必要があるが、単純な演算だとそのままインクリメントを行えばよい。これを制御しているのがsrc/V3LinkInc.cppのようだ。

  • src/V3LinkInc.cpp
     virtual void visit(AstPreAdd* nodep) override { prepost_visit(nodep); }
     virtual void visit(AstPostAdd* nodep) override { prepost_visit(nodep); }
     virtual void visit(AstPreSub* nodep) override { prepost_visit(nodep); }
     virtual void visit(AstPostSub* nodep) override { prepost_visit(nodep); }

ここではAstPreAddAstPostAddに焦点を当てて観察する。

prepost_visit()では上記のようにステートメントの中か、そうでないかで処理を切り替える。

     void prepost_visit(AstNodeTriop* nodep) {
         // Check if we are underneath a statement
         if (!m_insStmtp) {
             prepost_non_stmt_visit(nodep);
         } else {
             prepost_stmt_visit(nodep);
         }
     }

ステートメント内ではない場合、これは単純だ。インクリメントするためのAstAssign(AstAdd())ノードを生成する。

     void prepost_non_stmt_visit(AstNodeTriop* nodep) {
         /* 中略 */
         AstAssign* assignp;
         if (VN_IS(nodep, PreSub) || VN_IS(nodep, PostSub)) {
             assignp = new AstAssign(nodep->fileline(), storetop,
                                     new AstSub(nodep->fileline(), valuep, newconstp));
         } else {
             assignp = new AstAssign(nodep->fileline(), storetop,
                                     new AstAdd(nodep->fileline(), valuep, newconstp));
         }

そうではない場合、ステートメント中の場合は、中間変数を作成する。

     void prepost_stmt_visit(AstNodeTriop* nodep) {
         /* 中略 */
         string name = string("__Vincrement") + cvtToStr(++m_modIncrementsNum);
         AstVar* varp = new AstVar(fl, AstVarType::BLOCKTEMP, name, VFlagChildDType(),
                                   varrefp->varp()->subDTypep()->cloneTree(true));

         // Declare the variable
         insertBeforeStmt(nodep, varp);

         // Define what operation will we be doing
         AstNode* operp;
         if (VN_IS(nodep, PostSub) || VN_IS(nodep, PreSub)) {
             operp = new AstSub(fl, new AstVarRef(fl, varrefp->varp(), VAccess::READ), newconstp);
         } else {
             operp = new AstAdd(fl, new AstVarRef(fl, varrefp->varp(), VAccess::READ), newconstp);
         }
         if (VN_IS(nodep, PreAdd) || VN_IS(nodep, PreSub)) {
             // PreAdd/PreSub operations
             // Immediately after declaration - increment it by one
             m_insStmtp->addHereThisAsNext(
                 new AstAssign(fl, new AstVarRef(fl, varp, VAccess::WRITE), operp));
             // Immediately after incrementing - assign it to the original variable
             m_insStmtp->addHereThisAsNext(
                 new AstAssign(fl, new AstVarRef(fl, varrefp->varp(), VAccess::WRITE),
                               new AstVarRef(fl, varp, VAccess::READ)));
         } else {
             // PostAdd/PostSub operations
             // assign the original variable to the temporary one
             m_insStmtp->addHereThisAsNext(
                 new AstAssign(fl, new AstVarRef(fl, varp, VAccess::WRITE),
                               new AstVarRef(fl, varrefp->varp(), VAccess::READ)));
             // Increment the original variable by one
             m_insStmtp->addHereThisAsNext(
                 new AstAssign(fl, new AstVarRef(fl, varrefp->varp(), VAccess::WRITE), operp));
         }

まず、++ii++のどちらにも関わらず、i + 1の演算を生成する(operp = new AstAdd(fl, new AstVarRef(fl, varrefp->varp(), VAccess::READ), newconstp);がそれに当たる)。

そして、++iの場合は「代入処理の前に」作成した演算を挿入する(次の2つのAstAssignがそれに相当していると思われる。)

i++の場合は、まずはオリジナルの値を代入する処理を置き、その次にインクリメントの処理を置く。つまり上記の逆になる。

このようにして、結果的に以下のようなAstが生成される。

  • test.sv
 module test;

 int i;
 int j;
 initial begin
   j = i++;
   j = ++i;
 end
 endmodule
  • 変換前
 Verilator Tree Dump (format 0x3900) from <e49> to <e79>
      NETLIST 0x7fffed7bef40 <e1> {a0aa}  $root [1ps/1ps]
     1: MODULE 0x7fffed7d3570 <e49#> {c1ai}  test  L0 [1ps]
     1:2: VAR 0x7fffed7d3ad0 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
     1:2:1: BASICDTYPE 0x7fffed7d39f0 <e7> {c3ab} @dt=this@(sw32)  int kwd=int range=[31:0]
     1:2: VAR 0x7fffed7d3f30 <e14> {c4af} @dt=0@  j [VSTATIC]  VAR
     1:2:1: BASICDTYPE 0x7fffed7d3e50 <e13> {c4ab} @dt=this@(sw32)  int kwd=int range=[31:0]
     1:2: INITIAL 0x7fffed7d5720 <e46> {c5ab}
     1:2:2: BEGIN 0x7fffed7d4220 <e15> {c5aj}
     1:2:2:1: ASSIGN 0x7fffed7d4d60 <e26> {c6af} @dt=0@
     1:2:2:1:1: POSTADD 0x7fffed7d4a40 <e27> {c6ai} @dt=0@
     1:2:2:1:1:1: CONST 0x7fffed7d46c0 <e23> {c6ai} @dt=0x7fffed7d4830@(G/wu32/1)  ?32?h1
     1:2:2:1:1:2: VARREF 0x7fffed7cfee0 <e52#> {c6ah} @dt=0@  i [RV] <- VAR 0x7fffed7d3ad0 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
     1:2:2:1:1:3: VARREF 0x7fffed7d0020 <e55#> {c6ah} @dt=0@  i [LV] => VAR 0x7fffed7d3ad0 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
     1:2:2:1:2: VARREF 0x7fffed7d0160 <e58#> {c6ad} @dt=0@  j [LV] => VAR 0x7fffed7d3f30 <e14> {c4af} @dt=0@  j [VSTATIC]  VAR
     1:2:2:1: ASSIGN 0x7fffed7d55c0 <e44> {c7af} @dt=0@
     1:2:2:1:1: PREADD 0x7fffed7d5500 <e42> {c7ah} @dt=0@
     1:2:2:1:1:1: CONST 0x7fffed7d51d0 <e38> {c7ah} @dt=0x7fffed7d4830@(G/wu32/1)  ?32?h1
     1:2:2:1:1:2: VARREF 0x7fffed7d02a0 <e61#> {c7aj} @dt=0@  i [RV] <- VAR 0x7fffed7d3ad0 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
     1:2:2:1:1:3: VARREF 0x7fffed7d03e0 <e64#> {c7aj} @dt=0@  i [LV] => VAR 0x7fffed7d3ad0 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
     1:2:2:1:2: VARREF 0x7fffed7d0570 <e67#> {c7ad} @dt=0@  j [LV] => VAR 0x7fffed7d3f30 <e14> {c4af} @dt=0@  j [VSTATIC]  VAR
     3: TYPETABLE 0x7fffed7bf5e0 <e2> {a0aa}
         detailed  ->  BASICDTYPE 0x7fffed7d4830 <e20> {c6ai} @dt=this@(G/wu32/1)  logic [GENERIC] kwd=logic range=[31:0]
     3:1: BASICDTYPE 0x7fffed7d4830 <e20> {c6ai} @dt=this@(G/wu32/1)  logic [GENERIC] kwd=logic range=[31:0]
  • 変換後
 Verilator Tree Dump (format 0x3900) from <e79> to <e147>
      NETLIST 0x7fffed7bef40 <e1> {a0aa}  $root [1ps/1ps]
     1: MODULE 0x7fffed7d3570 <e49> {c1ai}  test  L0 [1ps]
     1:2: VAR 0x7fffed7d3ad0 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
     1:2:1: BASICDTYPE 0x7fffed7d39f0 <e7> {c3ab} @dt=this@(sw32)  int kwd=int range=[31:0]
     1:2: VAR 0x7fffed7d3f30 <e14> {c4af} @dt=0@  j [VSTATIC]  VAR
     1:2:1: BASICDTYPE 0x7fffed7d3e50 <e13> {c4ab} @dt=this@(sw32)  int kwd=int range=[31:0]
     1:2: INITIAL 0x7fffed7d5720 <e46> {c5ab}
     1:2:2: BEGIN 0x7fffed7d4220 <e15> {c5aj}
     1:2:2:1: ASSIGN 0x7fffed7d1600 <e96#> {c6af} @dt=0@
     1:2:2:1:1: VARREF 0x7fffed7d14c0 <e92#> {c6af} @dt=0@  i [RV] <- VAR 0x7fffed7d3ad0 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
     1:2:2:1:2: VARREF 0x7fffed7d1380 <e93#> {c6af} @dt=0@  __Vincrement1 [LV] => VAR 0x7fffed7d10c0 <e102#> {c6af} @dt=0@  __Vincrement1 BLOCKTEMP
     1:2:2:1: ASSIGN 0x7fffed7d1800 <e104#> {c6af} @dt=0@
     1:2:2:1:1: ADD 0x7fffed7cf530 <e99#> {c6af} @dt=0@
     1:2:2:1:1:1: VARREF 0x7fffed7d1240 <e87#> {c6af} @dt=0@  i [RV] <- VAR 0x7fffed7d3ad0 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
     1:2:2:1:1:2: CONST 0x7fffed7cf9c0 <e88#> {c6ai} @dt=0x7fffed7d4830@(G/wu32/1)  ?32?h1
     1:2:2:1:2: VARREF 0x7fffed7d16c0 <e100#> {c6af} @dt=0@  i [LV] => VAR 0x7fffed7d3ad0 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
     1:2:2:1: VAR 0x7fffed7d10c0 <e102#> {c6af} @dt=0@  __Vincrement1 BLOCKTEMP
     1:2:2:1:1: BASICDTYPE 0x7fffed7d0fe0 <e81#> {c3ab} @dt=this@(sw32)  int kwd=int range=[31:0]
     1:2:2:1: ASSIGN 0x7fffed7d4d60 <e83#> {c6af} @dt=0@
     1:2:2:1:1: VARREF 0x7fffed7d18c0 <e107#> {c6ah} @dt=0@  __Vincrement1 [LV] => VAR 0x7fffed7d10c0 <e102#> {c6af} @dt=0@  __Vincrement1 BLOCKTEMP
     1:2:2:1:2: VARREF 0x7fffed7d0160 <e58> {c6ad} @dt=0@  j [LV] => VAR 0x7fffed7d3f30 <e14> {c4af} @dt=0@  j [VSTATIC]  VAR
     1:2:2:1: ASSIGN 0x7fffed7d2130 <e130#> {c7af} @dt=0@
     1:2:2:1:1: ADD 0x7fffed7d1f30 <e125#> {c7af} @dt=0@
     1:2:2:1:1:1: VARREF 0x7fffed7d1df0 <e121#> {c7af} @dt=0@  i [RV] <- VAR 0x7fffed7d3ad0 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
     1:2:2:1:1:2: CONST 0x7fffed7d1a00 <e122#> {c7ah} @dt=0x7fffed7d4830@(G/wu32/1)  ?32?h1
     1:2:2:1:2: VARREF 0x7fffed7d1ff0 <e126#> {c7af} @dt=0@  __Vincrement2 [LV] => VAR 0x7fffed7d1c70 <e137#> {c7af} @dt=0@  __Vincrement2 BLOCKTEMP
     1:2:2:1: ASSIGN 0x7fffed7d2470 <e139#> {c7af} @dt=0@
     1:2:2:1:1: VARREF 0x7fffed7d2330 <e134#> {c7af} @dt=0@  __Vincrement2 [RV] <- VAR 0x7fffed7d1c70 <e137#> {c7af} @dt=0@  __Vincrement2 BLOCKTEMP
     1:2:2:1:2: VARREF 0x7fffed7d21f0 <e135#> {c7af} @dt=0@  i [LV] => VAR 0x7fffed7d3ad0 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
     1:2:2:1: VAR 0x7fffed7d1c70 <e137#> {c7af} @dt=0@  __Vincrement2 BLOCKTEMP
     1:2:2:1:1: BASICDTYPE 0x7fffed7d1b90 <e114#> {c3ab} @dt=this@(sw32)  int kwd=int range=[31:0]
     1:2:2:1: ASSIGN 0x7fffed7d55c0 <e116#> {c7af} @dt=0@
     1:2:2:1:1: VARREF 0x7fffed7d2530 <e142#> {c7aj} @dt=0@  __Vincrement2 [LV] => VAR 0x7fffed7d1c70 <e137#> {c7af} @dt=0@  __Vincrement2 BLOCKTEMP
     1:2:2:1:2: VARREF 0x7fffed7d0570 <e67> {c7ad} @dt=0@  j [LV] => VAR 0x7fffed7d3f30 <e14> {c4af} @dt=0@  j [VSTATIC]  VAR
     3: TYPETABLE 0x7fffed7bf5e0 <e2> {a0aa}
         detailed  ->  BASICDTYPE 0x7fffed7d4830 <e20> {c6ai} @dt=this@(G/wu32/1)  logic [GENERIC] kwd=logic range=[31:0]
     3:1: BASICDTYPE 0x7fffed7d4830 <e20> {c6ai} @dt=this@(G/wu32/1)  logic [GENERIC] kwd=logic range=[31:0]
f:id:msyksphinz:20210408000239p:plain
f:id:msyksphinz:20210408000300p:plain

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

Verilatorのコンパイルフローを観察する (5. 007_JumpLinkについて)

Verilatorの内部構造勉強続き。次は007_JumpLinkについて見てみた。READMEを見てみると以下のように書いてある。

  • src/V3LinkJump.cpp
 // V3LinkJump's Transformations:
 //
 // Each module:
 //   Look for BEGINs
 //      BEGIN(VAR...) -> VAR ... {renamed}
 //   FOR -> WHILEs
 //
 //   Add JumpLabel which branches to after statements within JumpLabel
 //      RETURN -> JUMPBLOCK(statements with RETURN changed to JUMPGO, ..., JUMPLABEL)
 //      WHILE(... BREAK) -> JUMPBLOCK(WHILE(... statements with BREAK changed to JUMPGO),
 //                                    ... JUMPLABEL)
 //      WHILE(... CONTINUE) -> WHILE(JUMPBLOCK(... statements with CONTINUE changed to JUMPGO,
 //                                    ... JUMPPABEL))

どうもこれはロジック的な構文ではなく、while文に関する構文のようだ。ラベルを定義してそっちにジャンプするための構文を作り上げる。While文を削除しすべてJump文に変換するという訳だ。試しに以下のようなコードを作ってみた。以下は全くデジタル的なコードではなく、initial beginに埋め込まれているコードとなる。

 module while_jump;

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

002_cells007_linkの構文ツリーを見比べてみる。

  • Vwhile_jump_002_cells.tree
 Verilator Tree Dump (format 0x3900) from <e0> to <e41>
      NETLIST 0x7fffd46f3f40 <e1#> {a0aa}  $root [1ps/1ps]
     1: MODULE 0x7fffd4708420 <e41#> {c1ai}  while_jump  L0 [1ps]
     1:2: VAR 0x7fffd4708950 <e6#> {c3af} @dt=0@  i VAR
     1:2:1: BASICDTYPE 0x7fffd4708870 <e7#> {c3ab} @dt=this@(sw32)  int kwd=int range=[31:0]
     1:2: INITIAL 0x7fffd470a4f0 <e38#> {c4ab}
     1:2:2: BEGIN 0x7fffd4708bc0 <e8#> {c4aj}
     1:2:2:1: WHILE 0x7fffd470a3e0 <e35#> {c5ad}
     1:2:2:1:2: LT 0x7fffd4709360 <e36#> {c5al} @dt=0x7fffd4709420@(G/nw1)
     1:2:2:1:2:1: PARSEREF 0x7fffd4708e90 <e15#> {c5aj}  i [TEXT]
     1:2:2:1:2:2: CONST 0x7fffd4709070 <e16#> {c5an} @dt=0x7fffd47091e0@(G/swu32/7)  ?32?sh64
     1:2:2:1:3: BEGIN 0x7fffd4709800 <e21#> {c5as}
     1:2:2:1:3:1: IF 0x7fffd470a2c0 <e33#> {c6af}
     1:2:2:1:3:1:1: EQ 0x7fffd470a050 <e34#> {c6al} @dt=0x7fffd4709420@(G/nw1)
     1:2:2:1:3:1:1:1: PARSEREF 0x7fffd4709b20 <e29#> {c6aj}  i [TEXT]
     1:2:2:1:3:1:1:2: CONST 0x7fffd4709d20 <e30#> {c6ao} @dt=0x7fffd4709ed0@(G/swu32/4)  ?32?sha
     1:2:2:1:3:1:2: BREAK 0x7fffd470a1b0 <e32#> {c6as}
     3: TYPETABLE 0x7fffd46f45e0 <e2#> {a0aa}
            logic  -> BASICDTYPE 0x7fffd4709420 <e19#> {c5al} @dt=this@(G/nw1)  logic [GENERIC] kwd=logic
         detailed  ->  BASICDTYPE 0x7fffd4709420 <e19#> {c5al} @dt=this@(G/nw1)  logic [GENERIC] kwd=logic
         detailed  ->  BASICDTYPE 0x7fffd4709ed0 <e26#> {c6ao} @dt=this@(G/swu32/4)  logic [GENERIC] kwd=logic range=[31:0]
         detailed  ->  BASICDTYPE 0x7fffd47091e0 <e12#> {c5an} @dt=this@(G/swu32/7)  logic [GENERIC] kwd=logic range=[31:0]
     3:1: BASICDTYPE 0x7fffd47091e0 <e12#> {c5an} @dt=this@(G/swu32/7)  logic [GENERIC] kwd=logic range=[31:0]
     3:1: BASICDTYPE 0x7fffd4709420 <e19#> {c5al} @dt=this@(G/nw1)  logic [GENERIC] kwd=logic
     3:1: BASICDTYPE 0x7fffd4709ed0 <e26#> {c6ao} @dt=this@(G/swu32/4)  logic [GENERIC] kwd=logic range=[31:0]
  • Vwhile_jump_007_link.tree
 Verilator Tree Dump (format 0x3900) from <e41> to <e60>
      NETLIST 0x7fffd46f3f40 <e1> {a0aa}  $root [1ps/1ps]
     1: MODULE 0x7fffd4708420 <e41#> {c1ai}  while_jump  L0 [1ps]
     1:2: VAR 0x7fffd4708950 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
     1:2:1: BASICDTYPE 0x7fffd4708870 <e7> {c3ab} @dt=this@(sw32)  int kwd=int range=[31:0]
     1:2: INITIAL 0x7fffd470a4f0 <e38> {c4ab}
     1:2:2: BEGIN 0x7fffd4708bc0 <e8> {c4aj}
     1:2:2:1: JUMPBLOCK 0x7fffd4705470 <e55#> {c5ad}
     1:2:2:1:1: WHILE 0x7fffd470a3e0 <e54#> {c5ad}
     1:2:2:1:1:2: LT 0x7fffd4709360 <e36> {c5al} @dt=0x7fffd4709420@(G/nw1)
     1:2:2:1:1:2:1: VARREF 0x7fffd47048f0 <e44#> {c5aj} @dt=0@  i [RV] <- VAR 0x7fffd4708950 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
     1:2:2:1:1:2:2: CONST 0x7fffd4709070 <e16> {c5an} @dt=0x7fffd47091e0@(G/swu32/7)  ?32?sh64
     1:2:2:1:1:3: BEGIN 0x7fffd4709800 <e21> {c5as}
     1:2:2:1:1:3:1: IF 0x7fffd470a2c0 <e33> {c6af}
     1:2:2:1:1:3:1:1: EQ 0x7fffd470a050 <e34> {c6al} @dt=0x7fffd4709420@(G/nw1)
     1:2:2:1:1:3:1:1:1: VARREF 0x7fffd4704a30 <e47#> {c6aj} @dt=0@  i [RV] <- VAR 0x7fffd4708950 <e6> {c3af} @dt=0@  i [VSTATIC]  VAR
     1:2:2:1:1:3:1:1:2: CONST 0x7fffd4709d20 <e30> {c6ao} @dt=0x7fffd4709ed0@(G/swu32/4)  ?32?sha
     1:2:2:1:1:3:1:2: JUMPGO 0x7fffd4705610 <e57#> {c6as} -> JUMPLABEL 0x7fffd4705540 <e53#> {c5ad} -> JUMPBLOCK 0x7fffd4705470 <e55#> {c5ad}
     1:2:2:1:2: JUMPLABEL 0x7fffd4705540 <e53#> {c5ad} -> JUMPBLOCK 0x7fffd4705470 <e55#> {c5ad}
     3: TYPETABLE 0x7fffd46f45e0 <e2> {a0aa}
            logic  -> BASICDTYPE 0x7fffd4709420 <e19> {c5al} @dt=this@(G/nw1)  logic [GENERIC] kwd=logic
         detailed  ->  BASICDTYPE 0x7fffd4709420 <e19> {c5al} @dt=this@(G/nw1)  logic [GENERIC] kwd=logic
         detailed  ->  BASICDTYPE 0x7fffd4709ed0 <e26> {c6ao} @dt=this@(G/swu32/4)  logic [GENERIC] kwd=logic range=[31:0]
         detailed  ->  BASICDTYPE 0x7fffd47091e0 <e12> {c5an} @dt=this@(G/swu32/7)  logic [GENERIC] kwd=logic range=[31:0]
     3:1: BASICDTYPE 0x7fffd47091e0 <e12> {c5an} @dt=this@(G/swu32/7)  logic [GENERIC] kwd=logic range=[31:0]
     3:1: BASICDTYPE 0x7fffd4709420 <e19> {c5al} @dt=this@(G/nw1)  logic [GENERIC] kwd=logic
     3:1: BASICDTYPE 0x7fffd4709ed0 <e26> {c6ao} @dt=this@(G/swu32/4)  logic [GENERIC] kwd=logic range=[31:0]

明確に見て取れるのは、WHILEブロック内のbreak文が無くなって、JUMPGOという演算子が追加されている。これはおそらく前節のEQが成立するとJUMPLABELに飛び、そうでないときはJUMPBLOCKに飛ぶということだろう。

f:id:msyksphinz:20210405235159p:plain

Vivado Simulatorを使ってUVMに入門する (2. driver / agent / monitor / sequencer などなど)

Vivado Simulatorを使ってUVMに入門している。とりあえず良い資料が無いので以下のサイトを見ながら我流でいろいろ試している。

sites.google.com

UVM Driver / Agent / monitor / Sequencer を以下のように配置した。

.
|-- Makefile
|-- model
|   |-- sample.svh
|   |-- sample_agent.sv
|   |-- sample_driver.sv
|   |-- sample_env.sv
|   |-- sample_model.svh
|   |-- sample_monitor.sv
|   `-- sample_sequencer.sv
|-- sample_test.sv
`-- tb_top.sv

それぞれのファイルの内容は上記のウェブサイトに書いてあるので、Vivado Simulatorを動かすためのMakefileだけ掲載しておく。

.PHONY: all

FILELIST =
FILELIST += tb_top.sv

all:
        xvlog -sv $(FILELIST) -L uvm --include ./model/
        xelab tb_top -timescale 1ns/100ps -L uvm
        xsim tb_top -R --testplusarg "UVM_TESTNAME=sample_test"

clean:
        rm -rf *.log *.jou *.dir *.pb

実行結果は以下のようになった。

UVM_INFO /proj/xbuilds/SWIP/2020.2_0711_1805/installs/lin64/Vivado/2020.2/data/system_verilog/uvm_1.2/xlnx_uvm_package.sv(18648) @ 0: reporter [NO_DPI_TSTNAME] UVM_NO_DPI defined--getting UVM_TESTNAME directly, without DPI
UVM_INFO @ 0: reporter [RNTST] Running test sample_test...
UVM_INFO /proj/xbuilds/SWIP/2020.2_0711_1805/installs/lin64/Vivado/2020.2/data/system_verilog/uvm_1.2/xlnx_uvm_package.sv(20867) @ 0: reporter [UVM/COMP/NAMECHECK] This implementation of the component name checks requires DPI to be enabled
UVM_INFO @ 0: uvm_test_top [TEST] Hello World
UVM_INFO @ 0: uvm_test_top.env [ENV] Hello ENV
UVM_INFO @ 0: uvm_test_top.env.agent [AGENT] Hi
UVM_INFO @ 0: uvm_test_top.env.agent.sequencer [SEQR] Hi
UVM_INFO @ 0: uvm_test_top.env.agent.monitor [MONITOR] Hi
UVM_INFO @ 0: uvm_test_top.env.agent.driver [DRIVER] Hi
UVM_INFO /proj/xbuilds/SWIP/2020.2_0711_1805/installs/lin64/Vivado/2020.2/data/system_verilog/uvm_1.2/xlnx_uvm_package.sv(13673) @ 0: reporter [UVM/REPORT/SERVER] [UVM/RELNOTES]     1

RISC-Vがリンクレジスタを2つ用意しているのは何故なのか

最近は朝の体操としてRISC-Vの仕様書を一から全部読みなおしている。ジャンプアンドリンク命令の部分を読んでいて、自分でも良く知らないところが出てきたので調べてみた。

RISC-VのABIでは通常ジャンプアンドリンクのリンクレジスタとしてraレジスタ(x1)が使用される。 しかし実はAlternateリンクレジスタとしてt0レジスタも使用可能となっている。これはどういう意味なのか。

f:id:msyksphinz:20210403154032p:plain

そのまま仕様書を読むと、「ミリコード(millicode)」のためにAlternate Registerを定義したと書いてあるがミリコードとは何なのか。 よくよく調べていくと、答えはAndrew Waterman(RISC-V ISAの生みの親)の博士論文に書いてあった。

www2.eecs.berkeley.edu

ミリコードの議論のモチベーションはRVC命令から来ていて、RVC命令に複数レジスタのロードストア命令を含めるかどうか、という所から来ている。 複数レジスタのロードストア命令の利点は静的な(コンパイル時)の命令数を削減することができる点ではあるが、結局のアイデアは見送りになっている。 これには様々な要因があるが、例えばアウトオブオーダ実行のCPUでは複数の操作をまとめた一つの命令を実行するよりも分解していた方が命令のスケジューリングを柔軟にできる。このようなRISC的思想から複数レジスタのロードストア命令は見送られた。

ただ、静的に命令数を削減したいというのならば、関数のプロローグとエピローグを共通化した「ミリコード」が有効だとしている。 関数のプロローグとエピローグは退避するレジスタに応じて共通化できるため、関数間で共通化することができる。例えば以下のような階乗計算プログラムに使用できる。

uint64_t factorial(uint64_t x) {
  if (x > 0)
    return factorial(x - 1) * x;
  return 1;
}

通常ならば、以下のようにしてコンパイルされる。プロローグとエピローグは埋め込まれたままだ。

00: cd11     c.beqz a0, 1c         
02: 1141     c.addi sp, sp, -16    
04: e406     c.sdsp ra, 8(sp)      
06: e022     c.sdsp s0, 0(sp)      
08: 842a     c.mv s0, a0           
0a: 157d     c.addi a0, -1         
0c: ff5ff0ef jal ra, factorial 
10: 02850533 mul a0, a0, s0    
14: 60a2     c.ldsp ra, 8(sp)      
16: 6402     c.ldsp s0, 0(sp)
18: 0141     c.addi sp, 16 
1a: 8082     c.jr ra
1c: 4505     c.li a0, 1
1e: 8082     c.jr ra

一方でミリコードを使うと以下のようになる。prologue_2epilogue_2がミリコードであり、関数間で共通化されたルーチンとなる。 prologue_2はt0を使ってジャンプ&リンクしているが、epilogue_2は末尾再帰を使用してリンクを省略していることに注意。

00: c919     c.beqz a0, 16
02: 016002ef jal t0, prologue_2
06: 842a     c.mv s0, a0
08: 157d     c.addi a0, -1
0a: ff7ff0ef jal ra, factorial
0e: 02850533 mul a0, a0, s0
12: 0100006f jal x0, epilogue_2
16: 4505     c.li a0, 1
18: 8082     c.jr ra

prologue_2:               
00: 1141 c.addi sp, -16   
02: e406 c.sdsp ra, 8(sp) 
04: e022 c.sdsp s0, 0(sp) 
06: 8282 c.jr t0          

epilogue_2:
00: 60a2 c.ldsp ra, 8(sp)
02: 6402 c.ldsp s0, 0(sp)
04: 0141 c.addi sp, 16
06: 8082 c.jr ra

この時にミリコードを呼び出すためのリンクレジスタとして"Alternate Link Register"であるt0が使用されているという訳だ。つまり標準的なABIとしてリンクレジスタではなく、特殊な用途で呼び出すためのリンクレジスタ、ということだ。

なぜこのような技法が使えるかというと、RISC-VのJAL命令はリンクレジスタとして任意のレジスタが使用可能だからだ。MIPSなどではリンクレジスタが固定されているが、RISC-Vではリンクレジスタが固定されていないため、このような技法が適用可能となる。

このミリコードをGCCに実装して評価した結果が以下のようになっている。CPU2006のベンチマークを実行し、テキストサイズと動的命令実行数を評価した。テキストサイズは平均で4%削減されたが、実行命令数は平均で3%増加となっている。

f:id:msyksphinz:20210403161011p:plain

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