FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス <a href="https://msyksphinz.github.io/github_pages/">

高速Verilogシミュレータ"Verilator"で「出来ないこと」

f:id:msyksphinz:20170201002310j:plain

本当は、 NVDLAをVerilatorで動かしてみたかったんです。。

NVDLAがVivado Simulatorに対応になったところで、ついでにVerilatorでNVDLAを動かすことは出来るのだろうかと考え、移植を試みた。 結論から言うと、動作させることはできていない。 これはVerilatorの「高速動作」であるが故のさまざまな制限があるからである。

これまではVerilatorは魔法のVerilog Simulatorくらいに思っていたけど(RISC-VのRocket Chipも動かすことが出来るくらいだし!)、Verilatorは意外と言語上の制約が多いと言うことが分かってきた。 日本語でこのような情報をまとめているものは無かったので、せっかくなのでまとめておこう。

ちなみに、マニュアルをざっと読みながらまとめたので、正しい情報は、以下を参照してください。

www.veripool.org

個人的には、以下が猛烈に大きい。

  • 全ての遅延記述 (#) は無視される。
  • event系のイベント (waitなど) はサポートされない。

言語上の制約

Verilatorでコンパイルできるのは、合成用の言語のみ

Verilatorは合成用の言語記述と、それに付随するいくつかのサポート言語、例えば$stop, $finish, $display程度しかサポートされていない。 従って、階層の参照や、イベント処理などの言語拡張は使用することが出来ない。

always @ (x) y = x & z;

上記の用の記述でも、xもしくはzが変化するとyの値が更新される(本当のシミュレータでは、xが更新された場合のみyが更新される)

Bind

Verilatorはbind記述はターゲットモジュール名のみサポートしており、インスタンスパスはサポートしない。

ドット記述による階層参照

Verilatorは変数、関数およびタスクの階層参照をサポートしているが、名前付きブロックや関数内のローカル変数への参照はサポートされていない。 ドット記述の前に配列位置参照は定数である必要がある。例えば a[2].bは参照することが出来るが、a[x].bは参照できない。

Generate文などで生成されたインスタンスVerilog標準の形式に変換されて参照することが出来る。例えば、Verilog{cellName}[{instanceNumber}]と記述すると {cellname}__BRA__{instanceNumber}__KET__ という形式に変換される。

VerilatorによるGenerate文はgenblkキーワードに、番号をつけた形で生成される。 このキーワードは、他のシミュレータでは異なるかもしれないが、Verilog仕様ではこの数値を規定してはいないので、特に問題はない。

浮動小数

Verilatorでは、浮動小数点表現(real)をサポートしない。

ラッチ

Verilatorはエッジセンシティブのデザインに最適化されており、ラッチ方式も実行することが出来るが、ラッチ記述の周りでは、最適化は抑止される。

構造体と共用体

Verilatorはpacked structpacked unionのみサポートしている。メンバ変数のrandおよびrandc記述は無視される。 全ての構造体と共用体は単純なベクタに変換される。 従って、メンバ内でブロッキング代入とノンブロッキング代入を併用することはサポートされていない。

時間

全ての遅延記述 (#) は無視される。

Unknownステート

Verilatorは2状態のみサポートしているシミュレータのため、4状態はサポートしていない。 従って、初期値の問題がしばしば発生する。

比較演算子(===, !==)は標準的な(==/!=)に変換される。 4状態のシミュレータでは、===による'X'の比較はFalseとなるが、Verilatorでは動作が異なる。

不定値'X'をassignした場合、実際にはランダムな値がassignされることになる(この値は--x-assignスイッチで変更できる)。 従って、この値が実際に後続の論理で使用されるとエラーを引き起こす可能性がある。 Verilog 2001では整数型の初期値はゼロであるが、Verilatorではランダムな値が設定される。

functionを使っている場合、全ての変数はランダム値が設定される。

いくつかのランダム値のシミュレーションを行うことで、リセット処理が正しく行われているかどうかを確認することが出来る。

最初の実行では、関数は変数をゼロに設定し、2回目では、初期値を1に設定する。3回目以降の実行では、初期値がランダムに設定する。 全ての結果が一致すれば、リセットは正しく動作していることが確認できる。

Tri/Inout

Verilatorは単純な構造を2ステートに変換する。 pullup, pulldown, buff0, buff1, notif0, notif1, pmos, nmos, tri0, tri1はサポートされている。

関数&タスク

全ての関数およびタスクはインライン化される(C言語に変換された際、関数化はされない)。

再帰関数および再帰タスクはサポートされない。 全ての入力および出力はautomaticとして宣言される。

クロック

Verilatorはクロックを正確に処理しようとするが、スケジューリングアルゴリズムと性能最適化のために問題を発生させる可能性がある。 もっとも安全なオプションは、モデルに入力される全てのクロックおよびwireは、主要な入力にattachされることである。 また、クロックを正しく動作させるためには、/* verilator clock_enable */ 属性を付加すること。

Big-Endianの範囲

ビット範囲の記述は、MSBがLSB以上になるように記述しなければならない。 リトルエンディアン[0:15]C++と互換性を保つのが難しいためサポートされていない。

ゲートプリミティブ

2状態のゲートプリミティブ(and, buf, nand, nor, not, or, xnor, xor)は単純に変換される。 3状態のMOSゲートはサポートされない。 テーブルはサポートされない。

Specify Block

全てのSpecify Blockおよびタイミングチェックはサポートされない。

配列の初期化

大きな配列を初期化する場合は、non-delayed assign文を使用する必要がある。 Verilatorはこのような記述されている場合警告を発生する。BLKLOOPINITエラーを参照のこと。

配列の領域超過

配列のアドレスを超過して書き込んだ場合、同じ配列内の別の場所が書き込まれるので注意すること。 2のべき乗のサイズの配列では、Verilatorは幅とアドレスの警告を発生する。 2のべき乗でない場合、インデックス0に書き込みが行われる。

配列のアドレスを超過して読み込みが行われた場合、アドレスが2のべき乗にラップされる。 2のべき乗のサイズではない場合、適切な幅の不定な値が返される。

アサーション

Verilatorはアサーションのサポートを始めている。 Verilatorはアサーションif(...) errorのみサポートしており、カバレッジのセクションで記述されているラインカウンタを増加させるためのカバレッジ文をサポートしている。

VerilatorはSEREをまだサポートしていない。全てのアサーションおよびカバレッジ文は、1サイクルで実行できる単純な式である必要がある。

キーワードの制約

以下のキーワードは完全にサポートされている。

`__FILE__, `__LINE__, `begin_keywords, `begin_keywords, `begin_keywords, `begin_keywords, `begin_keywords, `define, `else, `elsif, `end_keywords, `endif, `error, `ifdef, `ifndef, `include, `line, `systemc_ctor, `systemc_dtor, `systemc_header, `systemc_imp_header, `systemc_implementation, `systemc_interface, `timescale, `undef, `verilog

以下のキーワードは一般的にサポートされている。

always, always_comb, always_ff, always_latch, and, assign, begin, buf, byte, case, casex, casez, default, defparam, do-while, else, end, endcase, endfunction, endgenerate, endmodule, endspecify, endtask, final, for, function, generate, genvar, if, initial, inout, input, int, integer, localparam, logic, longint, macromodule, module, nand, negedge, nor, not, or, output, parameter, posedge, reg, scalared, shortint, signed, supply0, supply1, task, time, tri, typedef, var, vectored, while, wire, xnor, xor

++, -- 演算子

独立したインクリメント、デクリメント文の形式でのみサポートされている。

'{} 演算子

一部サポートされない。

cast演算子

単純なスカラ型から、signed, unsigned型の変換しかサポートされない。

chandle型

chandle型は単純にlongintに変換される。

disable

disable文はブロックを無効化するときのみ使用できる。

inside

inside式には、unpacked配列の探索や $ による上位の境界は含まれない。 case insidecase match はサポートされない。

interface

modports周辺でのブロックの生成、virtual interface, unnamed interfaceははサポートされない。

priority if, unique if

単純なif文に変換される。

specify specparam

無視される。

string

Stringは単純な演算でのみサポートされる。Stringによるメソッドの呼び出しはサポートされない。

timeunit, timeprecision

無視される。

uwire

uwireに関する警告は発生しない。通常のwireのように扱われる。

$bits, $countones, $error, $fatal, $finish, $info, $isunknown, $onehot, $onehot0, $readmemb, $readmemh, $signed, $stime, $stop, $time, $unsigned, $warning

一般的にサポートされる。

$display, $write, $fdisplay, $fwrite, $swrite

フォーマット無しで、変数リストのみでprintするような記述はサポートされない。

$displayb, $displayh, $displayo, $writeb, $writeh, $writeo, ...

サポートされない。

$finish, $stop

これらの変数のオプション類はサポートされない。

$fopen, $fclose, $fdisplay, $feof, $fflush, $fgetc, $fgets, $fscanf, $fwrite

PLIのファイル系のコマンドに使用されるものは、ファイルディスクリプタで無ければならない。

$fscanf, $sscanf

整数フォーマットのみサポートされる。

$fullskew, $hold, $nochange, $period, $recovery, $recrem, $removal, $setup, $setuphold, $skew, $timeskew, $width

サポートされない。

$random

シードを設定するためのオプションはサポートされない。

$readmemb, $readmemh

2次元以上の配列に対するreadmemhはサポートされない。

$test$plusargs, $value$plusargs

サポートされているが、これらのタスクを使用するためには、C++/SystemCテストベンチでは、以下を呼び出す必要がある。

    Verilated::commandArgs(argc, argv);

$timeformat

Verilatorではサポートされない。