FPGA開発日記

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

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

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

github.com

Verilator Internals

このファイルではVerilatorの内部構造とプログラミングの詳細について議論する。これは開発者が問題をデバッグするときに参照するためのものである。

Verilator Internalsのプレゼンテーション http://www.veripool.org も参照のこと。

コードフロー

Verilatorフロー

Verilatorのメインフローは、Verilator.cppのprocess()関数を追いかけることができる。

最初に、コマンドラインで指定されたファイルが読み込まれる。ファイルを読み込んだことでプリプロセッシングが動作しFlexによる字句解析とBisonによる構文解析が行われる。これによりデザインを表現する抽象文法木(Abstract Syntax Tree: AST)表現が生成され、以降で説明する.teeファイルで可視化される。

Verilatorは次にAST上でいくつかのPassを形成し、ASTのリファインと最適化を行う。

AST内のCellが最初にリンクされ、上記のさらに別のファイルが読み込まれ構文解析が行われる。

パラメータが解決され、デザインがエラボレートされる。

次に、Verilatorは階層デザインに対して様々な変更や最適化を行う。これにはカバレッジアサーション、Xの除去、インライン化、定数伝搬、不使用コードの削除などが含まれる。

デザイン中の参照は次に疑似的にフラット化される。各モジュールの変数と関数は「スコープ」参照を持つようになる。スコープ参照はフラット化された階層内でフラット化されていない変数を発生させる。階層構造中に一度しか現れないモジュールは各変数に対して単一のスコープと単一のVarScopeしか持たない。2回登場するデザインはそれぞれの登場に対して1つのスコープと、各変数につき2つのVarScopeを持つ。これによりフラット化されたデザインに対して、階層を維持しつつ最適化を実行することができるようになる。

更なる変更と最適化は疑似的にフラット化されたデザイン中で実行される。これらの処理にはモジュール参照、関数のインライン化、ループアンローリング、変数のライフタイム解析、ルックアップテーブルの作成、alwaysの分割、論理ゲートの簡単化(インバータの挿入、など)が含まれる。

Verilatorはコードを順番化する。最良のケースでは、この結果により単一のeval()関数によりすべてのalways文が上から下にループすることなく実行される。

Verilatorはフラット化したもののほとんどを捨てる、これにより同じモジュールが複数回呼び出されたときには、コードが共有化される。これにより変数がローカライズされ、個々の関数が組み合わさり、マクロがCのプリミティブに展開され、分岐予測のヒントが与えられ、さらに定数伝搬が実行される。

Verilatorは最終的にC++のモジュールを生成する。

Verilatorのフローで使用されるカギとなるクラス

AstNode

ASTはAstNodeクラスのトップレベルで表現される。この抽象クラスから、個々のコンポーネント(例えば、AstGenerateはgenerateブロックのためのコンポーネントである)やコンポーネントのグループ(例えば、AstNodeFTaskは関数とタスクのためのクラスであり、AstFuncAstTaskクラスに置き換えられる)。

AstNodeは4つの子供へのポインタを持っている。これらはop1pからop4pと呼ばれる。これらのメソッドは次に特定のAst*ノードクラスにより抽象化される。例えば、AstIfノード(if文用のノード)では、ifspop2pを呼び出すことでthenブロックのASTへのポインタを取得しelsespop3pを返すことでelseブロックのASTか、elseブロックが無い場合はNULLを取得することができる。

AstNodeクラスは次と前のASTのコンセプトを持っている。例えばブロック中の前後の文などである。これらの文のASTへのポインタは(もし存在すれば)backnextメソッドを用いることで取得することができる。

AstNetListはツリーのトップに配置されており、もしツリーのトップにいる場合はこのクラスをチェックすることが内部をチェックす標準的な方法であることを覚えておくとよい。

慣習により、各関数やメソッドはnodepという変数を、現在処理しているAstNodeへのポインタとして使用している。

AstNVisitor

PASSはASTビジタークラス(「ビジター関数」を参照のこと)により実装されている。これらは抽象クラスであるAstNVisitorのサブクラスとして実装してある。各PASSはビジタークラスのインスタンスを生成し、PASSの処理を実装することになる。

V3Graph

いくつかのPASSはグラフアルゴリズムを使うことになり、V3Graphクラスがグラフを表現するために使用される。グラフは有向グラフであり、グラフを操作するためのアルゴリズムと、その結果をGraphVizのdotフォーマット(http://www.graphviz.org を参照のこと)を出力する。V3Graph.hにクラスのドキュメントが格納されている。

V3GraphVertex

これはグラフの頂点を示すためのベースクラスである。頂点はfanoutcolorrank属性が付けられており、グラフを処理するためのアルゴリズムで使用される。汎用的なuser/userpメンバ変数も提供される。

dotファイルを出力するための名前、色、形、スタイルを指定するための仮想メソッドが提供される。典型的にはユーザはV3GraphVertexからの派生クラスを作成し、これらのメソッドを再実装することになる。

エッジの入出力に対してアクセスするためのイテレータが提供されている。典型的にはこれらな以下の形式で使用される。

for (V3GraphEdge *edgep = vertexp->inBeginp();
     edgep;
     edgep = edgep->inNextp()) {

V3GraphEdge

これはグラフの頂点を有向エッジを表現するためのクラスである。エッジにはweightcutabe属性が付けられている。汎用的なuser/userpメンバ変数も提供されている。

fromptopアクセサはそれぞれエッジのfromtoの頂点を返す。

dotファイルを出力するためのラベル、色、スタイルを指定するための仮想めそっだが提供される。典型的には、ユーザはV3Graphedgeからの派生クラスを作成し、これらのメソッドを再実装することになる。

V3GraphAlg

これはグラフアルゴリズムのベースとなるクラスである。このクラスではboolを返すメソッドであるfollowEdgeメソッドを実装し、アルゴリズムがエッジをどのように追いかけていくかどうかを決めることができる。このメソッドはあるユーザ関数(edgeFuncp():コンストラクタにより提供する)よりも重みが大きければtrueを返す。

あらかじめ定義されたアルゴリズムのクラスとメソッドはV3GraphAlg.cppから取得可能である。

Verilatedフロー

Verilatorにより生成された評価ループは、殆どの状況において、評価を実行するために実行する単一の関数としてデザインされている。

最初の評価では、Verilatedなコードはinitialブロックを呼び出し、すべての信号が安定化するまで(alwaysステートメントにより)関数を評価するモジュールを"settle"状態にする。

他の評価では、Verilatedコードはどの入力信号が変化したかをチェックする。クロックである場合、(alwyas @ posedgeステートメントから)適切なシーケンシャル関数を呼び出す。シーケンシャル関数の間には、組み合わせ回路関数(always @より)が呼び出される。これが終わると、組み合わせループや内部で生成されたクロックによる変化を検出し、それが見つかった場合にはモデルを再評価しなければならない。

SystemCコードでは、eval()関数はSystemC SC_METHODによりラップされる。これはすべての入力に対して反応する(理想的にはクロックと組み合わせ入力について伸び反応すれば良いが、トレースのためにはすべての信号に対して評価が必要であり、また性能への影響も小さい)。

トレースが有効である場合、コールバック関数はすべての変数の変化をチェックし、それぞれの変化をトレースに書き込む。頃プロセスを高速化するためには評価プロセスは変数うのビットマスクを評価し、変化を検出する; もしクリアされている場合には、その信号の変化に対するチェックはスキップする。