工業製造
産業用モノのインターネット | 工業材料 | 機器のメンテナンスと修理 | 産業プログラミング |
home  MfgRobots >> 工業製造 >  >> Industrial programming >> VHDL

OSVVMで制約付きランダム検証をマスターする

制約付きランダム検証は、テスト対象デバイス(DUT)に対して疑似ランダム・トランザクションを生成するテストベンチ戦略です。目標は、DUTとのランダムな相互作用を通じて、事前に定義された多数のイベントの機能カバレッジを達成することです。

Open Source VHDL Verification Methodology(OSVVM)は、制約付きランダム・テストベンチの作成に便利な多数のパッケージを含む無料のVHDLライブラリです。特にRandomPkgとCoveragePckに注目しており、この記事でこれらを使用します。OSVVMのGitHubページにアクセスして、このライブラリの機能について詳しく学ぶことをお勧めします。

テスト対象デバイス

前回のブログ記事でリング・バッファーFIFOを作成しましたが、モジュールの正しさを検証する自己チェック型テストベンチは作成しませんでした。今回は、リング・バッファーFIFOの適切なテストベンチを制約付きランダム検証を使って作成します。

 entity ring_buffer is generic ( RAM_WIDTH : natural; RAM_DEPTH : natural ); port ( clk : in std_logic; rst : in std_logic; -- 書き込みポート wr_en : in std_logic; wr_data : in std_logic_vector(RAM_WIDTH - 1 downto 0); -- 読み取りポート rd_en : in std_logic; rd_valid : out std_logic; rd_data : out std_logic_vector(RAM_WIDTH - 1 downto 0); -- フラグ empty : out std_logic; empty_next : out std_logic; full : out std_logic; full_next : out std_logic; -- FIFO内の要素数 fill_count : out integer range RAM_DEPTH - 1 downto 0 ); end ring_buffer; 

上記のコードにリング・バッファー・モジュールのエンティティが示されています。DUTをブラック・ボックスとして扱い、DUTの実装方法について一切の知識を仮定しません。この記事はテストベンチについてのものであり、リング・バッファーFIFOについてではないからです。

テストベンチではエンティティ・インスタンス化方式でDUTをインスタンス化します。インスタンス化は自明なのでコードは省略しますが、後で記事からダウンロードできます。

DUTジェネリックは以下の値にマッピングされます:

テストベンチ戦略

実装に入る前に、テスト戦略を確認しましょう。以下の画像は、これから作成するテストベンチの主要コンセプトを示しています。

OSVVMで制約付きランダム検証をマスターする

DUTの入力側でランダムな書き込みトランザクションを実行します。入力データは毎クロック・サイクルでランダムな値に設定され、書き込みイネーブル入力のストローブはランダムな継続時間になります。

同様に、ランダムな読み取りを実行します。読み取りイネーブル信号を、ランダムなクロック・サイクル数続くバーストでアサートします。

DUTと並行して振る舞いモデルを配置します。これはリング・バッファーとは異なる実装のFIFOですが、同じインターフェースを持ちます。DUTとは異なり、振る舞いモデルは合成可能である必要がありません。これにより、高度なVHDLプログラミング機能を使用して作成する自由が得られます。

別プロセスで、DUTの出力と振る舞いモデルの出力を比較します。このプロセスは、毎クロック・サイクルでassert文を使用してこの比較を行う責任を持ちます。2つのFIFO実装がいつでも異なる振る舞いを示せば、アサーション失敗によりシミュレーションがエラーで終了します。

最後に、DUTへの/からのトランザクションを観測して機能カバレッジ・データを収集します。機能カバレッジ・ポイントは、例えば同時読み書き、またはFIFOが少なくとも1回フルになることなどを意味します。メイン・テストベンチ・シーケンサー・プロセスでこれらのイベントを監視します。監視しているすべての機能カバレッジ・イベントが発生したらシミュレーションを停止します。

OSVVMライブラリのインポート

OSVVMライブラリはVHDL-2008をサポートするあらゆるシミュレーターで使用できます。シミュレーターに付属のデフォルト・ライブラリに含まれている場合があります。ModelSim PE Student Editionには含まれており、Mentor Graphicsから無料でダウンロード可能です。

ModelSimには古いバージョンのOSVVMが同梱されていますが、必要なものはすべて揃っています。以下のようにランダムとカバレッジ・パッケージをインポートできます:

 library osvvm; use osvvm.RandomPkg.all; use osvvm.CoveragePkg.all; 

最新版のOSVVMライブラリは常にGitHubページからダウンロード可能です。シミュレーターに含まれていない場合や、最新機能を使用したい場合はこちらを入手してください。

OSVVM変数の宣言

OSVVMライブラリには保護型を含むパッケージがあります。これらの型で作成された変数は、定義されたプロセスのスコープに限定されます。したがって、テストベンチ・アーキテクチャの宣言領域で共有変数として宣言します:

 -- OSVVM変数 shared variable rv : RandomPType; shared variable bin1, bin2, bin3, bin4, bin5, bin6 : CovPType; 

RandomPType型のrv変数は乱数生成用です。すべてのプロセスで同じオブジェクトを使用できるため、1つだけ必要です。最後の行はCovPType型の6つの変数を宣言します。

6つのカバレッジ変数を宣言したのは、6つのカバレッジ目標があるためです。これらのオブジェクトを「ビン」と呼びます。共有変数はカバレッジ・データ収集に使用する前に初期化する必要があります。各CovPTypeビンでAddBins手順を呼び出して初期化します。

 -- カバレッジ・ビンの設定 bin1.AddBins("Write while empty", ONE_BIN); bin2.AddBins("Read while full", ONE_BIN); bin3.AddBins("Read and write while almost empty", ONE_BIN); bin4.AddBins("Read and write while almost full", ONE_BIN); bin5.AddBins("Read without write when almost empty", ONE_BIN); bin6.AddBins("Write without read when almost full", ONE_BIN); 

AddBins手順の第1パラメータとしてカバレッジ・ビンの文字列説明を指定します。この文字列は、シミュレーション終了時に各カバレッジ・ビンの統計を出力するときに再登場します。テキスト説明からわかるように、非常に特定のコーナー・ケースが発生したかどうかをチェックするためにビンを使用します。

AddBinsはオーバーロードされた手順で、ビン変数内に複数のスコアボードを作成するために使用できます。しかし、各ビンに関連付けられるスコアボードは1つだけです。したがって、AddBins手順のパラメータとして便利定数ONE_BINを指定します。これにより、CovPType変数がそれぞれ1つのビンで初期化されます。ビンで表されるスコアボードは、監視しているイベントが少なくとも1回発生したときにカバーされたと見なされます。

ランダム入力の生成

DUTへの入力データを生成するプロセスから始めましょう。リング・バッファーFIFOは、オーバーライトとオーバー・リードの試みを無視するように設計されています。したがって、ランダムなデータをランダムな継続時間のバーストで単純に書き込めます。DUTが実際にデータを吸収できるかどうかを考える必要はありません。

 PROC_WRITE : process begin wr_en <= rv.RandSlv(1)(1) and not rst; for i in 0 to rv.RandInt(0, 2 * RAM_DEPTH) loop wr_data <= rv.RandSlv(RAM_WIDTH); wait until rising_edge(clk); end loop; end process; 

このプロセスが考慮する唯一の点は、DUTがリセット状態でないことです。プロセスの最初の行で書き込みイネーブル信号をランダムにイネーブル/ディセーブルしますが、rstが'0'の場合のみイネーブルされます。

続くforループは、イネーブル信号がアクティブでなくても、ランダムなクロック・サイクル数の間、DUTにランダム・データを書き込みます。これはDUTがwr_en信号が'1'でない限りwr_dataポートを無視するはずだから可能です。forループ後、プログラムはプロセスの先頭にループ・バックし、別のランダム書き込みトランザクションをトリガーします。

ランダム読み取りの実行

DUTからデータを読み取るプロセスは書き込みプロセスと似ています。DUTは空のときの読み取り試行を無視するように設計されているため、rd_en信号をいつでもランダムにアクティブ化できます。rd_dataポートに現れるデータは実際にはチェックされません。このプロセスは読み取りイネーブル信号のみを制御します。

 PROC_READ : process begin rd_en <= rv.RandSlv(1)(1) and not rst; for i in 0 to rv.RandInt(0, 2 * RAM_DEPTH) loop wait until rising_edge(clk); end loop; end process; 

振る舞い検証

テストベンチ内でDUTの振る舞いモデルを構築し、その振る舞いを検証します。これはよく知られたテストベンチ戦略です。まず、DUTと同じ入力を同時に振る舞いモデルに供給します。次に、両者の出力を比較してDUTが正しい振る舞いをするかチェックします。

OSVVMで制約付きランダム検証をマスターする

上図は、このようなテストベンチの基本レイアウトを示しています。振る舞いモデルはDUTと並行して動作します。DUTの出力をチェックするための青写真として使用します。

テストベンチFIFO

振る舞いモデルには連結リストを使用します。連結リストは合成できませんが、テストベンチには最適です。常連読者ならVHDLで連結リストを作成する方法記事を覚えているかもしれません。そのコードを使用してリング・バッファーFIFOの振る舞いモデルを実装します。

 package DataStructures is type LinkedList is protected procedure Push(constant Data : in integer); impure function Pop return integer; impure function IsEmpty return boolean; end protected; end package DataStructures; 

上記コードに連結リストFIFOのパッケージ宣言を示します。Push、Pop、IsEmptyの3つの関数を持つ保護型です。これらはFIFOへの要素追加・削除、および要素がゼロかどうかのチェックに使用されます。

 -- DUTをエミュレートするテストベンチFIFO shared variable fifo : LinkedList; 

保護型はVHDLのクラスのような構造です。テストベンチの宣言領域で共有変数を宣言することで、連結リストのオブジェクトを作成します。

振る舞いモデル

リング・バッファーFIFOの振る舞いを完全にエミュレートするため、DUTの出力信号をミラーする2つの新しい信号を宣言します。1つ目は振る舞いモデルからの出力データ、2つ目は関連するvalid信号です。

 -- テストベンチFIFO信号 signal fifo_out : integer; signal fifo_out_valid : std_logic := '0'; 

上記コードに振る舞いモデルの2つの出力信号宣言を示します。振る舞いモデル専用の入力信号は不要です。DUTに接続されているものと同じだからです。DUT出力をエミュレートするために信号を使用するのは、後で見るようにカバレッジ・データ収集を容易にするためです。

 PROC_BEHAVIORAL_MODEL : process begin wait until rising_edge(clk) and rst = '0'; -- 書き込みをエミュレート if wr_en = '1' and full = '0' then fifo.Push(to_integer(unsigned(wr_data))); report "Push " & integer'image(to_integer(unsigned(wr_data))); end if; -- 読み取りをエミュレート if rd_en = '1' and empty = '0' then fifo_out <= fifo.Pop; fifo_out_valid <= '1'; else fifo_out_valid <= '0'; end if; end process; 

上記コードにリング・バッファーFIFOの振る舞いモデルを実装するプロセスを示します。このプロセスは、リセット信号がアクティブでない場合、毎クロック立上がりエッジでトリガーされます。

振る舞いモデルは、wr_en信号がアサートされfull信号が'0'のとき、テストベンチFIFOに新しい値をプッシュします。同様に、振る舞いモデル・プロセスの読み取りロジックはrd_enとempty信号を監視して動作します。後者はDUTによって制御されますが、次のプロセスでそれが正しく動作することを検証します。

出力のチェック

DUTの出力をチェックするすべてのロジックは、「PROC_VERIFY」というプロセスに集約されています。DUTの出力が振る舞いモデルのものと一致することをassert文でチェックします。また、DUTと振る舞いモデルがFIFOが空であるタイミングで合意していることもチェックします。

検証プロセスのコードを以下に示します。

 PROC_VERIFY : process begin wait until rising_edge(clk) and rst = '0'; -- DUTとTB FIFOが同時にemptyを報告しているかチェック assert (empty = '1' and fifo.IsEmpty) or (empty = '0' and not fifo.IsEmpty) report "empty=" & std_logic'image(empty) & " while fifo.IsEmpty=" & boolean'image(fifo.IsEmpty) severity failure; -- valid信号が一致するかチェック assert rd_valid = fifo_out_valid report "rd_valid=" & std_logic'image(rd_valid) & " while fifo_out_valid=" & std_logic'image(fifo_out_valid) severity failure; -- DUTの出力がTB FIFOの次の要素と一致するかチェック if rd_valid then assert fifo_out = to_integer(unsigned(rd_data)) report "rd_data=" & integer'image(to_integer(unsigned(rd_data))) & " while fifo_out=" & integer'image(fifo_out) severity failure; report "Pop " & integer'image(fifo_out); end if; end process; 

プロセスはクロックの立上がりエッジでトリガーされます。DUTはクロック同期プロセスであり、DUTに接続される下流ロジックもクロック信号に同期すると期待されます。したがって、出力を立上がりクロック・エッジでチェックするのは理にかなっています。

2番目のコード・ブロックは、DUTからのempty信号がテストベンチFIFOが空のときのみアサートされることをチェックします。DUTと振る舞いモデルの両方が、FIFOが空かどうかで合意しなければこのテストはパスしません。

続いて読み取りデータvalid信号の比較です。DUTと振る舞いモデルは同時にデータを出力すべきで、そうでなければ何かが間違っています。

最後に、DUTの出力データがテストベンチFIFOからポップする次の要素と一致することをチェックします。これはもちろんrd_valid信号がアサートされ、rd_data信号がサンプリング可能な場合のみ発生します。

カバレッジ・データの収集

テストベンチのメイン・フローを制御するため、シーケンサー・プロセスを作成します。このプロセスはカバレッジ・ビンを初期化し、テストを実行し、すべてのカバレッジ目標が達成されたらテストベンチを停止します。以下のコードに完全な「PROC_SEQUENCER」プロセスを示します。

 PROC_SEQUENCER : process begin -- カバレッジ・ビンの設定 bin1.AddBins("Write while empty", ONE_BIN); bin2.AddBins("Read while full", ONE_BIN); bin3.AddBins("Read and write while almost empty", ONE_BIN); bin4.AddBins("Read and write while almost full", ONE_BIN); bin5.AddBins("Read without write when almost empty", ONE_BIN); bin6.AddBins("Write without read when almost full", ONE_BIN); wait until rising_edge(clk); wait until rising_edge(clk); rst <= '0'; wait until rising_edge(clk); loop wait until rising_edge(clk); -- カバレッジ・データ収集 bin1.ICover(to_integer(wr_en = '1' and empty = '1')); bin2.ICover(to_integer(rd_en = '1' and full = '1')); bin3.ICover(to_integer(rd_en = '1' and wr_en = '1' and empty = '0' and empty_next = '1')); bin4.ICover(to_integer(rd_en = '1' and wr_en = '1' and full = '0' and full_next = '1')); bin5.ICover(to_integer(rd_en = '1' and wr_en = '0' and empty = '0' and empty_next = '1')); bin6.ICover(to_integer(rd_en = '0' and wr_en = '1' and full = '0' and full_next = '1')); -- すべてのカバレッジ目標達成でテスト停止 exit when bin1.IsCovered and bin2.IsCovered and bin3.IsCovered and bin4.IsCovered and bin5.IsCovered and bin6.IsCovered; end loop; report("Coverage goals met"); -- テスト終了前にDUTが空であることを確認 wr_en <= force '0'; rd_en <= force '1'; loop wait until rising_edge(clk); exit when empty = '1'; end loop; -- カバレッジ・データ出力 bin1.WriteBin; bin2.WriteBin; bin3.WriteBin; bin4.WriteBin; bin5.WriteBin; bin6.WriteBin; finish; end process; 

まず、前述の通りAddBins手順を呼び出してカバレッジ・ビン・オブジェクトを初期化します。リセット解除後、カバレッジ・データ収集に移ります。クロックの毎立上がりエッジで、ループ構造内のコードが実行されます。

ループ内の最初のコード・ブロックはカバレッジ・データ収集用です。ビンでICover手順を呼び出して、それが表すカバレッジ・ポイントへのヒットを記録します。整数パラメータ0を渡すと呼び出しは無効です。整数パラメータ1を使うとヒットとしてカウントされます。

ONE_BIN定数で初期化したため、各カバレッジ・ビン・オブジェクト内には1つの「ビン」のみ存在します。この単一ビンはICover(1)を呼ぶことで到達できます。ブール式をto_integer関数で整数1または0に変換することで、カバレッジ・ポイントへのヒットまたはミスを登録できます。

カバレッジ・データ記録後、すべてのビンでIsCovered関数を呼び出し、すべてのカバレッジ目標が達成されたかチェックします。すべて達成されていればループを抜けます。

テスト終了前にDUTが空であることを確認します。これを実現するため、ライターとリーダー・プロセスから制御を引き継ぎ、wr_en信号を'0'に、rd_en信号を'1'に強制します。

最後に、各カバレッジ・ビンでWriteBin関数を呼び出し、各カバレッジ目標が何回ヒットしたかの統計を出力します。プロセス末尾のfinishキーワードでシミュレーターにシミュレーション停止を指示します。

テストベンチの実行

以下のフォームを使用して、すべてのVHDLファイルを含むModelSimプロジェクト全体をダウンロードできます。

含まれるdo-fileを実行してプロジェクトをロードした後、ModelSimコンソールで「runtb」と入力するだけでテストベンチを実行できます。テストベンチの実行時間はランダムです。カバレッジ目標がランダムにヒットするためです。しかし、テスト結果は再現可能です。実際には疑似ランダム・シーケンスが使用されるためです。

コードでシードを初期化しなかったため、疑似乱数生成器のデフォルト・シード値が使用されます。RandomPTypeオブジェクトでInitSeed手順を呼び出すことで、異なるシードを設定でき、異なるランダム・シーケンスが生成されます。

コンソール出力

ModelSimコンソールで「runtb」コマンドを実行した後の出力を以下に示します。シミュレーション実行中、テストベンチFIFOへのランダムなプッシュとポップが大量に表示されます。

VSIM 2> runtb # ** Warning: Design size of 15929 statements or 2 leaf instances exceeds # ModelSim PE Student Edition recommended capacity. # Expect performance to be quite adversely affected. # ** Note: Push 34910 # Time: 790 ns Iteration: 0 Instance: /ring_buffer_tb ... # ** Note: Pop 37937 # Time: 83100 ns Iteration: 0 Instance: /ring_buffer_tb # ** Note: Pop 13898 # Time: 83110 ns Iteration: 0 Instance: /ring_buffer_tb # %% WriteBin: # %% Write while empty Bin:(1) Count = 2 AtLeast = 1 # # %% WriteBin: # %% Read while full Bin:(1) Count = 3 AtLeast = 1 # # %% WriteBin: # %% Read and write while almost empty Bin:(1) Count = 106 AtLeast = 1 # # %% WriteBin: # %% Read and write while almost full Bin:(1) Count = 1 AtLeast = 1 # # %% WriteBin: # %% Read without write when almost empty Bin:(1) Count = 1 AtLeast = 1 # # %% WriteBin: # %% Write without read when almost full Bin:(1) Count = 3 AtLeast = 1 # # Break in Process PROC_SEQUENCER at C:/crv/ring_buffer_tb.vhd line 127 

すべてのカバレッジ目標が達成されると、すべてのカバレッジ・ビンの統計が出力されます。いくつかのビンは1回しかヒットしていませんが、1つは106回ヒットしています。しかし最終的に、各ビンが少なくとも1回ヒットしています。したがって、カバレッジ・ビンとして定義したすべてのイベントがテストされ、検証されたことがわかります。

波形

テストベンチが何をしていたか把握するため、波形を確認しましょう。以下の画像は、fill_count信号をアナログ値として表現した波形です。この信号のトレースが上部にあるときFIFOはフル、下部にあるとき空です。

OSVVMで制約付きランダム検証をマスターする

波形からわかる通り、リング・バッファーはランダムに充填と排出を繰り返しています。しかし、これらの充填レベルの傾斜と降下を明示的にプログラムしたわけではありません。それにもかかわらず、DUTとの有機的な相互作用パターンが見られます。

制約付きランダム検証についてさらに

制約付きランダム検証は、テスト・ベクターの順列が多すぎて網羅的テストが現実的でない場合に適したテストベンチ戦略です。ランダムな相互作用は、指示されたコーナー・ケース・テストよりも自然な振る舞いを示し、精度を犠牲にしません。

カバレッジ・データ収集を正しく設定していれば、すべてのコーナー・ケースが達成されたことを確信できます。追加のメリットは、ランダム・テストがあなたが具体的にテストしていないDUTの弱点を露呈させる可能性が高いことです。すべてのコーナー・ケースを知っていれば、それらに対する指示テストを作成できます。しかしコーナー・ケースは容易に見落とされ、それが制約付きランダム検証手法の恩恵を受ける場面です。

この記事は制約付きランダム検証でできることの表面をなぞったに過ぎません。OSVVMのGitHubページのドキュメントを読んで、さらに深く掘り下げることをお勧めします。

また、SynthWorksのAdvanced VHDL Testbenches and Verificationコースもお勧めします。私はこのコースと無関係ですが、5日間の物理コース版に出席しました。講師はVHDL Analysis and Standardization Group(VASG)の議長であるJim Lewis氏です。全体として、VHDLテストベンチを次のレベルに引き上げたいあらゆる企業にとって、素晴らしい投資になるでしょう。

VHDL

  1. メンテナンスの予測不能な課題:エキスパートJason Afara氏の洞察
  2. QR・RFID・体温検知統合アクセス制御システム(Raspberry Piベース)
  3. 高速PCB設計:差動信号処理とシミュレーション検証
  4. ハーコTM8傾斜床旋盤:コンパクト設計、精密制御、多彩な工具対応
  5. CAGIロータリーコンプレッサー性能検証プログラム:信頼性と効率的運転を保証
  6. Cadence、100億ゲートSoC検証向け高速エミュレーション&プロトタイピングソリューションを発表
  7. Siemens、Veloceシステムに4つの新製品を追加しシームレスなハードウェア支援検証を推進
  8. VE-VIDESイニシアチブ、IoTセキュリティ向け信頼できる設計・検証フレームワークを発表
  9. Synopsys、マルチダイシステム向け完全HBM3 IPスイートを発表、帯域幅921GB/s達成
  10. Java 乱数生成:Random クラスと Math.random 実践ガイド
  11. Java 8ストリームAPI:宣言的データ処理の新しいアプローチ