Verilatorについて全く知らない人が、どのように使えば良いのかきちんとした文章が世の中に存在していない気がするので、少しまとめてみることにした。VerilatorはフリーでオープンソースのVerilogシミュレーションシステムなので、うまく活用すれば強力な武器になる。高額なEDAツールを使わずとも家でハードウェア開発ができるようになる強力なツールだ。
RTLシミュレータと言えば、有償のものも含めれば代表的なものは以下のようなものであろうか:
- Synopsys VCS : 有償。
- Cadence Xcelium:有償。
- Mentor QuestaSim:有償
と、ほとんどのツールが有償であるが、Verilatorは無償かつオープンソースである。かつ性能も高いので、オープンソースプロジェクトなどで使うのにはちょうど良い。
予め説明しておくが、Verilatorは良いことばかりではない。なぜ高性能なのか?どういう制約があるのか?昔私のブログで紹介していた。
- 高速Verilogシミュレータ"Verilator"で「出来ないこと」
個人的には、以下が猛烈に大きい。
全ての遅延記述 (
#
) は無視される。event系のイベント (waitなど) はサポートされない。
Unknownステートはサポートされない
前提条件:VerilatorはVerilogをそのままシミュレーションするわけではない
上記の制約も含め、Verilatorの基本的な考え方を説明する。
Verilatorは他の多くのRTLシミュレーションシステムと同様にコンパイル型である。つまり、
という2段階構成を踏む。この際に気をつけなければならないのが上記の制約で、遅延記述やイベント系の構文は全くサポートされない。つまり、テストベンチ系のコードは全くコンパイルすることができないのだ。ほとんどデジタル回路に直結するVerilog記述しか受け付けることができないと考えて良い。
有償のシミュレータと考え方を比較してみる。有償シミュレータはテストすべき回路記述部分(DUT:Design Under Test)とそれを取り囲む形でテストベンチが構成される。DUTとテストベンチは両方Verilogとして記述されている。
しかしVerilatorの場合は違う。DUTはVerilogとして記述されるが、それ以外のテストベンチは殆どの記述が受け付けられないので、C言語を使ってテストベンチを構成する必要がある。これはVerilatorがDUTをC言語プログラムとして変換するため、これをリンクする形でC言語のテストベンチプログラムを用意するという形になる。
カウンタ回路を作ってVerilatorでシミュレーションする
具体的な例を考えて行こう。以下のようなVerilogで記述した回路を考える。en
信号でカウントアップする簡単なカウンタだ。
counter_4bit.v
module counter ( input logic clk, input logic reset_n, input logic en, output logic [3:0] cnt ); always_ff @(posedge clk, negedge reset_n) begin if (!reset_n) begin cnt <= 4'h0; end else begin if (en) begin cnt <= cnt + 4'h1; end end end endmodule // counter
Verilogでテストベンチを書くのは簡単だが、ここではVerilatorを使う。このカウンタをシミュレーションするVerilator環境を作ってみよう。
使用するVerilatorのバージョンは以下となる。
$ verilator --version Verilator 4.032 2020-04-04 rev UNKNOWN_REV
まずはこのDUTをコンパイルしてみよう。
$ verilator -cc counter_4bit.v
obj_dir
というディレクトリが作成されている。
$ ls -1 obj_dir Vcounter_4bit.cpp Vcounter_4bit.h Vcounter_4bit.mk Vcounter_4bit__Syms.cpp Vcounter_4bit__Syms.h Vcounter_4bit__ver.d Vcounter_4bit__verFiles.dat Vcounter_4bit_classes.mk
これらがcounter_4bit.v
から生成されたファイル群だ。C言語のコードに変換されていることが分かる。これをシミュレーションするための環境を作っていく。
C++を使ってcounter_4bitのテスト環境を作っていく
次はcounter_4bit
のテストベンチ環境を作っていく。説明したとおり、テストベンチはC/C++で記述する必要があり、これが有償シミュレータとの大きな違いだ。このテストベンチの構成方法について、ざっくりとしたテンプレートから示していこうと思う。
// 必要なファイルのインクルード #include <iostream> #include <verilated.h> #include "Vcounter_4bit.h" int main(int argc, char **argv) { // Verilatorのコマンド解析 // DUTモジュールのインスタンス化 // DUTモジュールのインタフェースの初期化 // while文 { // クロックを1サイクルずつ進めていく記述 // DUTを評価(回路を実行する)記述 // DUTインタフェースを評価する処理 // } // 終了 }
この流れを見ると、Verilogで記述しているテストベンチと流れは大して変わらないことが分かる。Verilogで記述しているテストベンチを、C/C++で書き直しているだけである。ただし書き直しているといってもVerilog記述のように自然に並列記述をすることはできないため、並列なテスト動作を記述するためには少し工夫する必要がある。
また、while
文の内部についても少し考慮が必要だ。DUT内の論理回路の実行は、具体的にはdut->eval();
という記述で実行されるが、これはdut
が最小時間単位で動いていることを意味する。つまりVerilogで言うtimescale
記述のようなものだと考えて良い。クロックとの関係性で言えば、例えば10回分eval();
を進めたところで外部クロック信号をトグルする、というような記述を行う。以下のようなイメージだ。
dut->clk = 0; while (true) { dut->eval(); if (time_counter % 5 == 0) { // 5 eval()に1回clkをtoggleする。(つまり周期は10 eval()分) dut->clk = !dut->clk; } time_counter ++; }
ではさっそくテストベンチを記述する。以下のようになった。
#include <iostream> #include <verilated.h> #include "Vcounter_4bit.h" int time_counter = 0; int main(int argc, char** argv) { Verilated::commandArgs(argc, argv); // Instantiate DUT Vcounter_4bit *dut = new Vcounter_4bit(); // Format dut->reset_n = 0; dut->clk = 0; dut->en = 0; // Reset Time while (time_counter < 100) { dut->eval(); time_counter++; } // Release reset dut->reset_n = 1; int cycle = 0; while (time_counter < 500) { if ((time_counter % 5) == 0) { dut->clk = !dut->clk; // Toggle clock } if ((time_counter % 10) == 0) { // Cycle Count cycle ++; } if (cycle % 5 == 0) { dut->en = 1; // Assert En } else { dut->en = 0; // Deassert En } // Evaluate DUT dut->eval(); time_counter++; } // std::cout << "Final Counter Value = " << dut->cnt << '\n'; printf("Final Counter Value = %d\n", dut->cnt); dut->final(); }
上記で説明したとおりであり、
dut
としてcounter_4bit
モジュールをインスタンス化する。reset_n
/clk
/en
を初期化する。- 100単位時間ほどリセット状態を保持する。
- リセットをリリースし、シミュレーション開始する。
- 5単位時間に1回
clk
をトグルする。 - 5サイクルに1回
en
信号を有効化する。単位時間毎にAssetを変更してしまうと一瞬でDeassertされてしまうので注意。 - 毎単位時間に
dut->eval()
を呼び出しDUTを評価する。 - 所定時間シミュレーションを実行すると終了し、最後に結果
dut->cnt
を出力する。
ではこれをシミュレーション環境を準備してみよう。以下のように実行する。
$ verilator --cc counter_4bit.v -exe tb_counter_4bit.cpp $ cd obj_dir $ make -C obj_dir -f Vcounter_4bit.mk
obj_dir/Vcounter_4bit
というバイナリが生成されていることが分かる。これがシミュレーション本体だ。ではさっそく実行してみよう。
$ ./obj_dir/Vcounter_4bit Final Counter Value = 8
問題なくカウンタが動作していることが分かった。ここまででVerilatorの基礎は完了だ。以降では、波形のダンプ方法、様々なVerilatorの機能について紹介していく。