【SystemVerilog】UVMのconfig_dbって何なの

少し前になりますが、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のチュートリアルをやっていると、必ず以下のような記述を目にすると思います。

```verilog:test.sv
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_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628886/0b60badc-fc9c-4d08-4024-b715c89eefdf.png)

|現実世界 |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ペリフェラルバスです）。

```verilog:apb_if.sv
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
```

### レコードを登録する
データベースにレコードを登録するには、以下のように記述します。

```verilog:tb_top.svh
uvm_config_db#(virtual apb_if)::set(null, "*", "vif", vif);
```
仰々しい記述ですね。僕はさっそく気が滅入ったのを覚えています。
意味を説明すると、

<b>uvm_config_db#(virtual apb_if)::</b>
uvm_config_dbクラスの持つ関数は全てスタティックです。またパラメータとして型を指定する必要があります。

<b>set(null, "*", "vif", vif)</b>
第一引数、第二引数はスコープです。今回はスコープを設定しない場合の記述ですね。スコープを設定する場合は後述します。
第三引数はconfig_db上の名前です。
第四引数は値です。パラメータで指定した型の値を入れてください。
なお、今回の例では第三引数と第四引数の文字列が同じですが、一致させる必要はありません。

実行すると、データベースが更新されます。

|name |scope |type |value |
|---|---|---|---
|conf1|*|int|1|
|conf2|uvm_test_top.env.agent|string|test|
|vif|*|virtual apb_if|/tb_top/vif|

### レコードを参照する
先ほど登録したレコードを参照するには、以下のように記述します。

```verilog:tb_top.v
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を対象としたデータを登録する場合、以下のように記述します。

```verilog:env.svh
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()の第１引数として指定した「this」は、「uvm_test_top.env」に変換されています。これはSystem Verilogのキーワードですね。そしてset()の第２引数「agent1」を結合した文字列が、scope列に入ったことになります。
このことは、set()の第一引数と第二引数に機能的な違いはないことを意味します。コンテキスト（第一引数）とインスタンス（第二引数）に分けた方が、プログラム上で理解しやすいというだけの話です。

agent1内でこのデータを参照するには、以下のように記述します。

```verilog:agent1.svh
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|


## デバックしてみる
簡単に使い方を紹介してみましたが、理解するには実際に触るのが一番です。
最後にデバック方法を紹介して、終わりとしたいと思います。

〇方法１
ソースコード上に次のように記述する。

```verilog:debug.svh
uvm_config_db_options::turn_on_tracing();
```

〇方法２
シミュレータのオプションで「+UVM_CONFIG_DB_TRACE」を指定する。

いずれも結果は同じで、config_dbへの操作を行うたびに、シミュレータにログが表示されます。

```text:
#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
```

以上です。