少し前になりますが、Mentor GraphicsからASIC・FPGAの検証方法について、レポートが出ましたね。
- 2020 Wilson Research Group Functional Verification Study: FPGA Functional Verification Trend Report
- 2020 Wilson Research Group Functional Verification Study: IC/ASIC Functional Verification Trend Report
レポートによるとUVMの採用率は、
- ASICプロジェクトのうち約72%
- FPGAプロジェクトのうち約48%
のようです。
ホントか!? 僕はFPGAしか触らないのですが、そんなにUVMが流行っているイメージはありません・・・。Qiitaをはじめ、ググってもろくにUVMを日本語で扱っているサイトは見当たらないし。
リサーチ対象に偏りがある気もするのですが、まぁ、世界的にはスタンダードになってきていると考えていいのかな。
僕がUVMを勉強している理由は、テストライブラリを自前で作るのが面倒だからです。自前の環境を作ったところで、他人はおろか、未来の自分さえ理解できないですからねw 日本でもスタンダードになってくれると、引継ぎが楽でいいんだけどなー。
config_dbとの出会い
さて、本題になります。UVMのチュートリアルをやっていると、必ず以下のような記述を目にすると思います。
uvm_config_db#(virtual xxx_if)::set(null, "*", "vif", vif);
uvm_config_db#(virtual xxx_if)::get(this, "", "vif", vif);
そして以下のような解説が続きます。
「uvm_config_dbは、UVMに内蔵しているデータベースで、とても便利だ。ここにデータを登録すると、(uvmのスコープ上からは)どこからでもデータを参照できる」
いやいや、便利と言われても。。。
引数4つもあるし、仕組みわかんないし、困るんですけどw
それが僕の最初の感想で、今回のタイトルです。この疑問はしばらく続きました。UVMを使っている中で、ようやく腑に落ちてきたので、僕なりに解説したいと思います。
いつ使うの?
いつconfig_dbを使えばいいのでしょうか。チュートリアルを見る感じ、黙って真似しろよという雰囲気はありますがw
そうはいっても最初はとっつきにくいにので、ユースケースの一つを紹介します。
あくまで僕のイメージですが、UVM(SystemeVerilog)の世界を現実世界に例えると、以下のようになります。
現実世界 | UVMの世界 |
---|---|
専用IC | テスト対象のモジュール |
評価基板 | テストベンチ |
コネクタ | SystemVerilogのinterface |
測定器 | テストクラス |
ある専用ICをテストするために、評価基板を起こした様子を想像しています。
基板設計と、IC設計はきっと別の人が担当するでしょう。
ここで基板設計者側の気持ちに立って考えてみます。
「IC設計者に言われたから、基板上にコネクタの配置まではやりました。
ICに対してどんな試験をするかって?
IC設計者に聞いてくれよ。
こっちは回路設計やアートワークで大変だったんや。」
UVMの世界でも同じことがいえます。
テストベンチは静的要素ですから、動的要素であるテストクラスまでケアするのは大変です。テストベンチ側でinterfaceまで用意したら、後はテストクラス側の責任としたいものです。すると、テストベンチとテストクラスでinterfaceの橋渡しをする機構が必要になります。
ここでconfig_dbの出番というわけです!
僕はテストベンチを用意したら、とりあえずinterfaceをconfig_dbに登録しちゃいます。そしてテストクラスを作る時に、興味のあるinterfaceをconfig_dbから引っ張てきます。他にもconfig_dbの活用法はありますが、これが一番シンプルな使い方な気がします。
どうやって使うの?
それでは使い方の説明です。
まずconfig_dbの構造ですが、以下のようになっています。
(公式の情報ではありませんが、ベンダの資料を参考にしています)
name | scope | type | value |
---|---|---|---|
conf1 | * | int | 1 |
conf2 | uvm_test_top.env.agent | string | test |
データベースのカラムは(name、scope、type、value)になります。
登録済みのレコードは一例です。また、以降の説明ではapb_ifという名前のinterfaceを使用します(=ARMペリフェラルバスです)。
interface apb_if (input clk, input resetn);
logic [31:0] addr;
logic write;
logic [31:0] wdata
logic enable;
logic [15:0] sel
logic [31:0] rdata;
logic slverr;
logic ready;
endinterface : apb_if
レコードを登録する
データベースにレコードを登録するには、以下のように記述します。
uvm_config_db#(virtual apb_if)::set(null, "*", "vif", vif);
仰々しい記述ですね。僕はさっそく気が滅入ったのを覚えています。
意味を説明すると、
uvm_config_db#(virtual apb_if)::
uvm_config_dbクラスの持つ関数は全てスタティックです。またパラメータとして型を指定する必要があります。
set(null, "*", "vif", vif)
第一引数、第二引数はスコープです。今回はスコープを設定しない場合の記述ですね。スコープを設定する場合は後述します。
第三引数はconfig_db上の名前です。
第四引数は値です。パラメータで指定した型の値を入れてください。
なお、今回の例では第三引数と第四引数の文字列が同じですが、一致させる必要はありません。
実行すると、データベースが更新されます。
name | scope | type | value |
---|---|---|---|
conf1 | * | int | 1 |
conf2 | uvm_test_top.env.agent | string | test |
vif | * | virtual apb_if | /tb_top/vif |
レコードを参照する
先ほど登録したレコードを参照するには、以下のように記述します。
virtual apb_if vif;
uvm_config_db#(virtual apb_if)::get(null, "*", "vif", vif);
get()の引数の意味は、set()と同じです。
get()が成功すると、変数vifを通して、先ほど登録したinterfaceにアクセスできるようになります。
もしconfig_dbに検索条件に合ったレコードがない場合、get()の結果は失敗に終わります。この時は返り値としてfalseが返るので、何かしらのエラーハンドリングが必要になります。
スコープについて
uvmのテストクラス階層に基づいて、スコープを設定できます。
例えば、uvm_test_top -> env -> agent1という階層があるとします。envから見て、agent1を対象としたデータを登録する場合、以下のように記述します。
uvm_config_db#(int)::set(this, "agent1", "conf_agent1", 10);
config_dbは、以下のように更新されます。
name | scope | type | value |
---|---|---|---|
conf1 | * | int | 1 |
conf2 | uvm_test_top.env.agent | string | test |
vif | * | virtual apb_if | /tb_top/vif |
conf_agent1 | uvm_test_top.env.agent1 | int | 10 |
scope列が「uvm_test_top.env.agent1」となっていることに注目です。
set()の第1引数として指定した「this」は、「uvm_test_top.env」に変換されています。これはSystem Verilogのキーワードですね。そしてset()の第2引数「agent1」を結合した文字列が、scope列に入ったことになります。
このことは、set()の第一引数と第二引数に機能的な違いはないことを意味します。コンテキスト(第一引数)とインスタンス(第二引数)に分けた方が、プログラム上で理解しやすいというだけの話です。
agent1内でこのデータを参照するには、以下のように記述します。
int conf_agent1;
uvm_config_db#(int)::get(this, "", "conf_agent1", conf_agent1);
型について
nameやscopeが同じでも、typeが違うと、別のレコードとして扱われます。
例えば、asb_if(ARMシステムバス)があるとしたら、以下みたいな登録も可能です。
name | scope | type | value |
---|---|---|---|
conf1 | * | int | 1 |
conf2 | uvm_test_top.env.agent | string | test |
vif | * | virtual apb_if | /tb_top/vif |
conf_agent1 | uvm_test_top.env.agent1 | int | 10 |
vif | * | virtual asb_if | /tb_top/vif |
デバックしてみる
簡単に使い方を紹介してみましたが、理解するには実際に触るのが一番です。
最後にデバック方法を紹介して、終わりとしたいと思います。
〇方法1
ソースコード上に次のように記述する。
uvm_config_db_options::turn_on_tracing();
〇方法2
シミュレータのオプションで「+UVM_CONFIG_DB_TRACE」を指定する。
いずれも結果は同じで、config_dbへの操作を行うたびに、シミュレータにログが表示されます。
#UVM_INFO C:/intelFPGA_lite/18.1/modelsim_ase/verilog_src/uvm-1.2/src/base/uvm_resource_db.svh(121) @ 0: reporter [CFGDB/SET] Configuration '*.conf1' (type int) set by = (int) 1
#UVM_INFO C:/intelFPGA_lite/18.1/modelsim_ase/verilog_src/uvm-1.2/src/base/uvm_resource_db.svh(121) @ 0: reporter [CFGDB/SET] Configuration '*.conf1' (type string) set by = (string) test
以上です。