検証環境とテストシナリオの実装方針

# 検証環境とテストシナリオの実装方針

## はじめに

[Design Solution Forum 2023](https://dsforum.jp/2023/) 参加時に、前職の後輩より表題の質問を受けました。ちょうど、Advent Calendar の時期なので、その際に答えた自分の方針を文章化しておこうと思います。

## 前提

[現職](https://www.pezy.co.jp)では、

* PCI Express IP の組み込みと周辺回路 (バスブリッジ、DMAC など) の RTL 設計とブロック検証
* チップトップ検証
    * 主にバスの接続確認

を担当しており、主な検証対象はバス周辺となります。なので、これから述べる方針は、バス周辺の検証に関する方針になります。他の検証対象 (プロセッサ、キャッシュ、信号処理、etc.) には当てはまらないかもしれません。

## 検証環境

* 前職より、なんやかんやで、10 年近く使っている
* 商用シミュレータを潤沢に使える環境にある

と言う状態なので、[SystemVerilog](https://ieeexplore.ieee.org/document/8299595) + [UVM](https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/index.html) を使って実装します。

### データパスのチェック

バスブリッジや DMAC が、ブロック検証の際の検証対象です。これらのモジュールは、

* Bus master から来たリクエスト
* Bus slave から来たレスポンス

を加工して反対側に送る、が主な機能となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144465/bbc61555-1bdc-a169-4ccd-4b606180bbd1.png)

UVM のお作法を当てはめやすい検証対象なので、リクエストとレスポンスの加工部のチェックは、Scoreboard を実装して、シミュレーション実行中に自動的にチェックを行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144465/1ddfcf86-bebf-6fe5-6d04-7119870f2c3d.png)

### 割り込みのチェック

実行時のチェックですが、~~実装が面倒なので~~自動的なチェックは使わずに、sequence やテストシナリオ上で、想定した割り込みが上がるかどうかをチェックしています。

割り込みは、大別すると、

* 動作完了などを伝える、正常系の割り込み
* エラーなどが起こったことを伝える、異常系の割り込み

の 2 種類があります。割り込みチェックの方針もこの分別に沿った方針になっています。

#### 正常系割り込み

まずは、正常系割り込みについてです。UVM では、設定など一連の動作は __sequence__ としてまとめられます。この sequence に、期待される動作として、割り込み待ちを組み込んでしまいます。例えば、DMAC の場合、

* アドレスやサイズなど、DMA 転送に必要な設定の実施
* DMA 転送の開始
* DMA 転送の終了待ち
    * 割り込みがアサートされることを待つ

が DMA 転送の一連の流れとなり、これらが DMA 転送を行うための sequence としてまとめられます。コードにすると、以下の様になります。

```verilog
task body();
  setup_dmac();        // サイズやアドレスなどを設定
  start_dma_access();  // DMA 転送開始
  wait_for_irq();      // 割り込みを用いて、終了待ち
endtask
```

期待通りに割り込みがアサートされなかった場合、`wait_for_irq` から抜けることができず、sequence が終了しません。UVM の場合、一定時間が経過するとタイムアウトが発生し、エラーになります。タイムアウトの有無によって、割り込みの動作確認を行います。

#### 異常系割り込み

次に、異常系割り込みについてです。~~面倒なので~~検証環境側では対応せず、テストシナリオ上で、明示的に割り込みがアサートされる事を待つことにします。エラーを起こすリクエストを明示的に検証対象に与えて、割り込み待ちを行う task が終了するかどうかで、割り込みの動作確認を行います。コードにすると、以下の様になります。

```verilog
task body();
  fork
    wait_for_error_irq();
    send_error_request();
  join
endtask
```

割り込みが期待通りアサートされた場合、fork/join ブロックを抜けて、テストシナリオが終了します。アサートされなかった場合、fork/join ブロックから抜けられず、正常系の時と同様に、タイムアウトでエラーになります。

## テストシナリオ

### ブロック検証

ブロック検証におけるテストシナリオの構成は、ダイレクト検証を基本としつつ、ランダム検証の要素を加えたテストシナリオになっています。UVM のお作法に則って、sequence や sequece_item をランダマイズして、検証対象への設定や入力を生成します。その際に、そのテストが注目するテスト内容になるように追加の制約を与えます。
例えば、DMAC のテストシナリオで、最大と最小の転送サイズに注目するテストシナリオの場合、sequence 実行コードは以下の様になります。

```verilog
`uvm_do_with(run_dma_sequence, { size == DMAC_MIN_SIZE; })
`uvm_do_with(run_dma_sequence, { size == DMAC_MAX_SIZE; })
```

転送長が最大長、最小長になるように決め打ち的な制約を与えますが、アドレスやリクエスト分割設定などはランダムに検証対象に与えられます。

先に書いたとおり、scoreboard を使っているので、データパスの期待値チェックはテストシナリオ上では行っていません。ただ、異常系のテストシナリオについては、

* エラーリクエストを起こすなど、異常状況を起こす
* 異常状態を検知できることを確認
    * 割り込みがアサートされること
    * ステータスレジスタが期待通りの値になっていること
* 正常動作に復帰できることを確認

をテストシナリオ上で明示的に記述します。

#### なぜダイレクト検証を基本とするか？

以下の理由により、ダイレクト検証を基本とした構成にしています。

* 実行している内容を明確にできる
    * ランダムだと何をしようとしているのか分からないので、デバッグをし辛い
* リグレッションの際に、確実に見たい機能の確認を実施できる

### トップ検証

トップ検証においては、全ての Bus master から、全ての Bus slave (キャッシュ、PCIe など) にアクセスできるかを確認しています。なので、ダイレクト的なテストシナリオになっています。つまり、

* ある Bus master から、ある Bus slave に書き込みを行う
* 全ての Bus master から、先の Bus slave に読み出しを行い、書いた値が読めることを確認

を行っています。単純なチェック内容のため、テストシナリオ内で期待値のチェックも行っています。

## 最後に

検証環境やテストシナリオの実装方針について、つらつらとまとめてみました。他の検証対象の際の方針など、ご意見がありましたら、コメント欄に投稿いただけると幸いです。