FPGA開発日記

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

Verilatorのコンパイルフローを観察する (10. Veritalor Internalsのドキュメントを読む)

VerilatorのInternalドキュメントを読む。ソースコードを読んでいるだけではだんだん良く分からなくなってきたので、一応概要を確認しておく。 概要の続き。

github.com

コーディング規約

インデントスタイル

Verilator C++ソースのインデントをマッチさせるために、レベル毎に4つのスペースを使用し、タブは8列にして、1つおきのインデントレベルがタブストップになるようにする。

全てのファイルは標準的なインデントを保証するために、以下のマジックヘッダを挿入するべきである:

// -*- mode: C++; c-file-style: "cc-mode" -*-

このコメントにより、インデントスタイルはcc-modeに設定される(Verilatorは数年前のCC-modeの変更に先立って、GNUスタイルのインデントでデフォルトを上書きしていたが、c-set-styleはそれを元に戻す)。

astgenスクリプト

いくつかのPassの実装は非常に多くの繰り返しが含まれているのと、AstNodeのサブクラスを実装する必要がある。しかし、繰り返しの最中の最中にはこれらは非常に可動的であり、C++のマクロにより処理される。

VerilatorではこれはPerlスクリプトastgenを使って実装されており、C++コードをプリプロセスする。例えばV3Const.cppvisit()を関数を、各バイナリ演算をTREEOPマクロを使って実装してある。

オリジナルのC++ソースコードobj_optobj_dbgサブディレクトリ(前者はVerilator向けに最適化されたもの、後者はデバッグ版)に変換される。例えば、V3Const.cppV3Const__gen.cppに変換される。

Visitor関数

VerilatorはVisitorデザインパタンを使ってPassのリファインと最適化を行う。これによりPassのアルゴリズムとそれが動作するASTを分離することができる。WikipediaではVisitorのコンセプトについての説明がある (http://en.wikipedia.org/wiki/Visitor_pattern: 日本語のページは https://ja.wikipedia.org/wiki/Visitor_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)

上記で示したよううに、すべてのVisitorはAstNVisitorからの派生クラスである。すべてのAstNodeの派生クラスはacceptメソッドを実装する。acceptメソッドはAstNVisitorの派生クラスのインスタンスへの参照を渡し、AstNVisitorのvisitメソッドを実行してAstNodeインスタンス(例えばthis)を誘起する。

困難な点としては、accept の呼び出しが、引数として受け取ったノードを破壊するような編集を実行する可能性がある。AstNodeacceptSubtreeReturnEditsメソッドは、acceptを適用し、元のノードが破壊されても結果のノードを返すために用意されている(破壊されない場合は元のノードが返されるだけ)。

visitorクラスの動作は、さまざまな AstNode 派生クラスの visit 関数をオーバーロードすることで実現している。特定の実装が見つからない場合、システムは継承階層上でオーバーロードされた実装を順に探していきます。たとえば、AstIf の accept を呼び出すと、順に以下のようになる。

void visit (AstIf* nodep, AstNUser* vup)
void visit (AstNodeIf* nodep, AstNUser* vup)
void visit (AstNodeStmt* nodep, AstNUser* vup)
void visit (AstNode* nodep, AstNUser* vup)

visitor関数間のデータの受け渡しには3つの方法がある。

  1. visitorクラスのメンバー変数である。これは一般的には「親」の情報を子供に渡すために使用される。m_modp がその例である。この変数はコンストラクタで NULL に設定され、そのノード(AstModule のビジター)が設定し、その後、子供たちが反復され、クリアされる。AstModule 以下の子供には NULL が設定され、それ以外のノードには NULL が消去される。ネストしたアイテムがある場合(例えば、AstForの下にAstForがある場合)、変数はAstForのビジターでsave-set-restoreされる必要があり、そうしないと下のforを終了すると上のforの設定が失われます。

  2. ユーザー属性。各AstNode(注:visitorではなく AST ノード)は、5 つのユーザ属性を持ち、 user1()user5() メソッドを用いて整数値でアクセスしたり、 user1p()user5p() メソッドを用いてポインタ(AstNUser 型)でアクセスしたりすることができる(グラフ探索パッケージからの一般的な手法である)。 visitorは、まず AstNode::user#ClearTree() を呼び出して使用したいノードをクリアし、その後、任意のノードの user()に必要なデータをマークすることができる。読者は単に nodep->user() を呼び出すだけであるが、適切にキャストする必要がある場合もあるので、nodep->userp()->castSOMETYPE() をよく見かけます。各ビジターの先頭には、user()の内容がそのビジタークラスにどのように適用されるかを説明するコメントがある。例えば、以下のようになる。

    cpp // NODE STATE // Cleared entire netlist // AstModule::user1p() // bool. True to inline this module

    これは、AstNetlistuser1ClearTree()が呼ばれることを表している。各 AstModuleuser1() は、インライン化するかどうかを示すために使用される。

    これらのコメントは、特定の AstNode タイプの user#() が 2 つの異なる目的に使用されることがないようにするために重要である。

    user#ClearTree の呼び出しは高速で、ツリーを歩くわけではないので、かなり頻繁に呼び出しても問題ないことに注意してください。たとえば、すべてのモジュールで共通して呼び出される。

  3. パラメータは、「通常の」関数の呼び出し側から呼び出し側への方法に近い形で、ビジター間で渡すことができる。これは、ほとんどのvisitor関数で無視されるAstNUser型の2番目のvupパラメータである。V3Widthはこれを行いるが、上記の方法よりも面倒であることがわかり、非推奨となりました。(V3Widthはほぼ最初に書かれたモジュールでした。vupをいたるところで通さなければならないのはプログラムが遅くなるので、いつかはこの方式が削除されるかもしれません)

イテレータ

AstNodeはツリー内を歩き回るためにいくつかのイテレータを提供している。それぞれは2つの引数、visitorv, (AstNVisitor型)、オプションであるユーザデータのポインタvup(`AstNUser*型)である。2番目の引数はVisitor関数の節で説明したパラメータを渡すための方法であるが、この方式は廃止されており新しいvisitorクラスでは使用するべきではない。

iterate()

AstNodeacceptメソッドを実行し、visitor関数を実行する

IterateAndNextIgnoreEdit()

リスト中のAstNodeに対してacceptメソッドを実行する(例えば、nextpbackpポインタに接続されているリストなど)

iterateAndNext()

リスト中のAstNodeに対してacceptメソッドを実行する。もしノードがacceptにより変更されるのであれば、ノードが変更されなくなるまでacceptを適用し続ける。

iterateListBackwards()

リスト中のAstNodeに対してacceptメソッドを、最後の要素から順番に適用する。

iterateChildren()

iterateAndNextメソッドをop1pからop4pまでの各子供要素に適用する。

iterateChildrenBackwards()

iterateListBackwardsメソッドをop1pからop4pまでの各子供要素適用する。

派生クラスの識別

一般的な要件として、特定のAstNodeクラスを識別することが挙げられる。例えばVisitorはAstIfAstGenIfに対して個別のvisitメソッドを実装するのではなく、ベースクラスに対して1つのメソッドを実装する場合がある:

void visit (AstNodeIf* nodep, AstNUser* vup)

しかし、このメソッドは、AstGenIf のために呼び出された場合、追加のコードを指定したい場合がある。Verilator は、C++ の dynamic_cast を使用して、可能なノードタイプごとに castSOMETYPE() メソッドを提供することでこれを実現している。このメソッドは、その型にキャストされたオブジェクトへのポインタを返すか(SOMETYPE クラスまたは SOMETYPE の派生クラスの場合)、さもなければ NULL を返す。よって、私たちのvisitorメソッドは次のようになる:

if (nodep->castAstGenIf()) {
    <code specific to AstGenIf>
}

共通のテストはAstNetlistのためのもので、これはASTのルートとなるノードである。