[UVM] PLI なしで Backdoor アクセスを実現する

# PLI なしで Backdoor アクセスを実現する

石谷 @ [PEZY Computing](https://www.pezy.co.jp) です。 [Hardware Description Language Advent Calendar 2023](https://qiita.com/advent-calendar/2023/hdl) の３日目と言うことで、今年は、RgGen が生成する UVM RAL モデルのとある機能の解説という誰得な投稿をしようと思います。

## はじめに

[UVM RAL](https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/files/overviews/registers-txt.html) は CSR への読み書きを抽象化するための仕組みで、アドレスやプロトコルを意識せずに、CSR への読み書きを実行できます。

```verilog
uart_ral_model.dll.write(status, 'haa);
uart_ral_model.dlm.read(status, value);
```

UVM RAL には `backdoor アクセス` と言って、バスアクセスを起こさずに、対象レジスタに値を設定したり、読んだりる機能があります。

```verilog
uart_ral_model.dll.poke(status, 'haa);  // write に相当
uart_ral_model.dlm.peek(status, value); // read に相当
```

対象レジスタを直接操作するので、バスアクセスを起こすより、シミュレーション時間を短縮することができます。ただ、既存の実装では、PLI を使った C のプログラムを介して実装されており、PLI の使用は実行時間の増加につながります。

拙作の [RgGen](https://github.com/rggen/rggen) が出力する RAL モデルでは、PLI を使わない backdoor アクセスを実現させています。本稿では、どのように実現したかを解説したいと思います。

## なぜ PLI を使っているか？

なぜ、既存の実装では、実行時間の悪化を許容してまで、PLI を使って backdoor アクセスを実現させているのでしょうか？
それは、`package` 内で定義された `class` からは、以下のように、

```verilog
tb.dut.u_csr_block.u_dll.value_reg = 'haa;
```

階層アクセスを用いる事が出来ないためです。そのため、対象レジスタへの階層パスを文字列で指定し、PLI を使って実装された [C の関数](https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/files/dpi/uvm_hdl-svh.html)を SystemVerilog のコー上から呼び出すことで、backdoor アクセスを実現させています。

## RgGen での実現方法

### 方針

`module` (回路) の世界と `class` (検証環境) の世界を繋ぐ方法は、

1. `module` 中に `interface` をインスタンスする
1. `interface` のインスタンスを `virtual interface` として、class に渡す
1. `vittual interface` を介して、`interface` 中の信号を操作する

が王道パターンです。RgGen でもこの方法を採用します。RgGen では、RTL も自動生成なので、生成した RTL に、内部信号を制御するための `interface` を事前にインスタンスしておきます。

RgGen が生成する RTL の構造は、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144465/9b9fb20e-6c0e-aa35-2b00-140daa566f5f.png)

のようになっています。
`u_register` は register 階層の機能を実装していて、

* CSR バスから来たリクエストをデコードし、配下の bit field に分配する
    * 書き込みマスク、読み出しマスクを生成
* 配下の bit field が保持する値を纏めて、読み出しデータを作る

を行っています。
`u_bit_field` は bit field 階層の機能を実現していて、CSR の記憶素子の役割を担っています。

つまり、`u_register` を乗っ取って、

* 書き込みデータ、書き込みマスク、読み出しマスクを上書き
* 纏めた読み出しデータを参照

するための `interface` をインスタンスし、それの `virtual interface` を検証環境の世界に渡すことができれば、PLI なしに backdoor アクセスを実現できます。

### RTL 側の準備

対象となる CSR ブロックの RTL も自動生成されるので、上記の仕組みを予め仕込んでおくことができます。

register 階層には、[rggen_register_common](https://github.com/rggen/rggen-sv-rtl/blob/master/rggen_register_common.sv) がインスタンスされています。このモジュール中に、[rggen_backdoor](https://github.com/rggen/rggen-sv-rtl/blob/master/rggen_backdoor.sv) および [rggen_backdoor_if](https://github.com/rggen/rggen-sv-rtl/blob/master/rggen_backdoor_if.sv) がインスタンスされています。

rggen_backdoor_if には、レジスタアクセスを乗っ取る用の

* 書き込みデータ
* 書き込みマスク
* 読み出しマスク

が宣言されています。これらが有効の際は、バスからのアクセスに代わり、配下の bit_field を更新します。

https://github.com/rggen/rggen-sv-rtl/blob/b3fcdd239d556fb99b86c0a0133f30e85809be61/rggen_register_common.sv#L60-L63

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144465/e32d4cd1-6586-0c1b-342a-f057e392a33d.png)

また、rggen_backdoor_if に宣言されている読み出しデータには、各 bit field からの読み出しデータが接続されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144465/dceeee28-fa89-b85b-82da-7470c65503b0.png)

rggen_backdoor_if には、以下のメソッドが定義されていて、

* [`backdoor_read`](https://github.com/rggen/rggen-sv-rtl/blob/b3fcdd239d556fb99b86c0a0133f30e85809be61/rggen_backdoor_if.sv#L46-L51)
* [`backdoor_write`](https://github.com/rggen/rggen-sv-rtl/blob/b3fcdd239d556fb99b86c0a0133f30e85809be61/rggen_backdoor_if.sv#L53-L58)
* [`ger_read_data`](https://github.com/rggen/rggen-sv-rtl/blob/b3fcdd239d556fb99b86c0a0133f30e85809be61/rggen_backdoor_if.sv#L89-L91)

これらのメソッドを用いて、レジスタアクセスを起こしたり、リードデータを取得したりします。
rggen_backdoor_if は、RTL 中で BFM (Bus Function Model) として機能するようになっています。

### 検証環境との接続

`class` からの`module` への階層アクセスは使えないので、rggen_backdoor_if の `virtual interface` を直接取得することは不可能です。`package` 中に `virtual interface` を保持するための変数を宣言し、この変数を介して、`module` の世界から `virtual interface` を取り出します。

また、

* `$sformatf("%m")` で、これが呼び出された `module` の階層パスを取得できる
* 生成された RAL モデルには、すでに階層パスが設定されている

ことから、階層パスの文字列をキーとすることで、`virtual interface` の受け渡しを簡単に行うことができます。

[rggen_backdoor_pkg](https://github.com/rggen/rggen-sv-rtl/blob/master/rggen_backdoor_pkg.sv) がそのための `package` です。この `package` には、rggen_backdoor_if の `virtual interface` の連想配列が宣言されています。
RTL 側で `$sformatf("%m")` をキーにして、この連想配列に rggen_backdoor_if の `virtual interface` を設定します。

https://github.com/rggen/rggen-sv-rtl/blob/b3fcdd239d556fb99b86c0a0133f30e85809be61/rggen_backdoor.sv#L40-L42

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144465/110ce613-ec94-d849-00d4-ba2b4b3e914d.png)

### 検証環境側の対応

[uvm_reg](https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/files/reg/uvm_reg-svh.html) は register 階層のモデリングを行う class です。この `class` に、`write`/`read` などのレジスタアクセスを行うメソッドが定義されています。また、この `class` には、以下の backdoor アクセスを行うメソッドが定義されており、

* [`backdoor_read`](https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/files/reg/uvm_reg-svh.html#uvm_reg.backdoor_read)
* [`backdoor_write`](https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/files/reg/uvm_reg-svh.html#uvm_reg.backdoor_write)
* [`backdoor_read_func`](https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/files/reg/uvm_reg-svh.html#uvm_reg.backdoor_read_func)
* [`backdoor_watch`](https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/files/reg/uvm_reg-svh.html#uvm_reg.backdoor_watch)

これらのメソッドをオーバーラードして、対応します。

これらのメソッドが呼ばれたら、

* 設定されている階層パスをキーにして、rggen_backdoor_if の `virtual interface` を取り出す
* rggen_backdoor_if 内に定義されているメソッドを呼び出し、レジスタアクセスを起こす

ようになっています。

https://github.com/rggen/rggen-sv-ral/blob/727da7c095f380a46dfc9d14a171ed6d1d2383e1/rggen_ral_reg.svh#L23-L62

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144465/6ac71ce8-5c4a-6ec5-572f-3346f5e16011.png)

https://github.com/rggen/rggen-sv-ral/blob/727da7c095f380a46dfc9d14a171ed6d1d2383e1/rggen_ral_backdoor_pkg.sv#L64-L70

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144465/5044df33-efa6-6ac9-0f93-4ac23eae5ba7.png)

:::note info
アクセスにもう一階層経由していたり、各シミュレータや言語の差分を吸収するための記述があったりしますが、今回は割愛します。
詳細は、[rggen_backdoor_pkg](https://github.com/rggen/rggen-sv-rtl/blob/master/rggen_backdoor_pkg.sv) や [rggen_ral_backdoor_pkg](https://github.com/rggen/rggen-sv-ral/blob/master/rggen_ral_backdoor_pkg.sv) の実装を参照ください。
:::

## まとめ

このように、RgGen では、BFM として機能する `interface` を RTL 中にインスタンスすることで、PLI なしの backdoor アクセスを実現してします。PLI 未使用なので、シミュレーション実行時間への影響が少ない事が利点の１つですが、他ににも利点があります。

まずは、副作用 (write 1 clear/read clear など) も再現できる、と言う点です。bit field から見ると、backdoor アクセスも通常のアクセスも何ら変わりがないからです。 PLI を使った backdoor アクセスでは、値を強制的に上書きしているだけなので、副作用の再現まではできません。

次に、UVM 未使用でも backdoor アクセスを使える、と言う点です。backdoor アクセスを実現している rggen_backdoor_if や rggen_backdoor_pkg は、UVM を使っていません。従来の module ベースの検証環境などからも、backdoor アクセスを行うことができます。

ただ、PLI を使った実装に比べて、以下の欠点があります。

まずは、RTL を検証用に変更している、と言う点です。合成の際は rggen_backdoor_if のインスタンスを作らないようにしているので、検証時と合成時とでは、記述に差分があることになります。なので、これを良しとするかは、使用時に検討が必要になるでしょう。

https://github.com/rggen/rggen-sv-rtl/blob/b3fcdd239d556fb99b86c0a0133f30e85809be61/rggen_register_common.sv#L109-L132

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144465/cac0f53c-26cb-ace7-1249-f36c0bd53b8e.png)

次に、書き込みに 1 サイクル分の時間がかかる、と言う点です。副作用の再現を重視して、通常のアクセスと同じようにレジスタアクセスを起こすようになっています。そのため、PLI を使った backdoor アクセスでは 0 時間でアクセスを実行できますが、本実装では 1 サイクル分の時間を要してしまいます (バスモデルからバスアクセスを起こすことに比べれば、短時間ですが。)。アクセスするレジスタの数が多く、シーケンシャルにアクセスを実行する場合に、問題になるかもしれません。

そして、合成後のネットリストに対しては適用できない、と言う点です。PLI を使った場合では、階層パスを適切に設定すれば、合成後のネットリストに対しても backdoor アクセスを実行できます。本実装の場合は、ネットリストは合成ツールが生成する Verilog なので、rggen_backdoor_if をインスタンスしようがありません。

## 最後に

宣伝です。社内で使っているツールや、共通モジュールなどを公開しています。

* [pzbcm](https://github.com/pezy-computing/pzbcm)
    * FIFO や調停回路などの共通モジュール詰め合わせ
    * 社内で使っているバスプロトコル (pzcorebus) 用の interface 定義や各種モジュール
* [pztb-core](https://github.com/pezy-computing/pztb-core)
    * 検証用コンポーネント詰め合わせ
    * pzcorebus や tilelink、UART などの VIP (UVM ベース)
        * https://github.com/pezy-computing/pztb-core/tree/main/pzvip
    * UVM ベースの検証環境を作るための共通クラス群
        * https://github.com/pezy-computing/pztb-core/tree/main/pztb_common_env
* [flgen](https://github.com/pezy-computing/flgen)
    * ファイルリストを記述するための DSL と生成ツールを提供
    * Ruby の構文が使えるので、かなり力業なこともできる
        * https://github.com/taichi-ishitani/rice/blob/master/tb/cosim/cosim.rb
            * 実行ディレクトリにある *.h を読んで、Verilog のマクロを定義