VHDL コード ロック モジュール用の Tcl 駆動型テストベンチを作成する方法
ほとんどの VHDL シミュレーターは、ツール コマンド言語 (Tcl) をスクリプト言語として使用します。シミュレータのコンソールにコマンドを入力すると、Tcl が使用されます。さらに、シミュレーターで実行され、VHDL コードと対話する Tcl を使用してスクリプトを作成できます。
この記事では、VHDL の代わりに Tcl を使用して VHDL モジュールが正しく動作することを検証するセルフチェック テストベンチを作成します。
こちらもご覧ください:
Tcl を学ぶ必要がある理由
Tcl を使用したインタラクティブなテストベンチ
以下のフォームを使用して、この記事と ModelSim プロジェクトからコードをダウンロードできます。
DUT:VHDL のコード ロック モジュール
テストベンチを開始する前に、被試験デバイス (DUT) を紹介します。 PIN パッドに正しい番号シーケンスを入力すると、ボールトのロックが解除されるコード ロック モジュールになります。
人々はしばしばコードロックをコンビネーションロックと呼んでいます .しかし、この用語は不正確だと思います。ロックを解除するには、正しい数字の組み合わせを入力するだけでは十分ではありません。また、正しい順序で入力する必要があります。厳密に言えば、コンビネーション ロックは実際には順列ロックです。 、しかしそれをコードロックと呼びましょう .

上の画像は、ホテルの金庫のようなコードロックを示しています。簡単にするために、この例では数字キーのみを使用し、「CLEAR」および「LOCK」ボタンは使用しません。
コード ロック モジュールの仕組み
モジュールはロックされた位置から始まり、秘密の PIN コードと一致する 4 桁の数字を続けて入力すると、金庫のロックが解除されます。再度ロックするには、別の間違った番号を入力します。したがって、VHDL でシーケンス検出器を作成する必要があります。
上の波形は、コード ロック モジュールがどのように機能するかを示しています。クロックとリセット以外に、2 つの入力信号があります:input_digit および input_enable .クロックの立ち上がりエッジでイネーブルが「1」の場合、モジュールは入力桁をサンプリングします。
このモジュールからの出力は 1 つだけです:unlock 信号。金庫や金庫のロック機構を制御していると想像してみてください。 ロック解除 信号は、ユーザーが正しい PIN に一致する 4 つの連続する数字を入力した場合にのみ「1」になります。この記事では、パスコードとして 1234 を使用します。
実体
以下のコードは、コード ロック モジュールの実体を示しています。このモジュールの目的は、TCL ベースのテストベンチ用の単純なサンプル DUT にすることであるため、ジェネリックを使用して秘密のパスコードをハードコーディングしています。 4 つの汎用定数は、範囲が制限された整数として実現される 2 進化 10 進数 (BCD) です。
entity code_lock is generic (pin0, pin1, pin2, pin3 : integer range 0 to 9); port ( clk : in std_logic; rst : in std_logic; input_digit : in integer range 0 to 9; input_enable : in std_logic; unlock : out std_logic ); end code_lock;
パスコードと同じように、input_digit 信号もBCDタイプです。他の入力と出力は std_logics です。
宣言領域
このモジュールには、ユーザーが入力した最後の 4 桁を含むシフト レジスタという 1 つの内部信号しかありません。しかし、0 から 9 の BCD 範囲を使用する代わりに、数値を -1 から 9 にします。これは 11 の可能な値です。
type pins_type is array (0 to 3) of integer range -1 to 9; signal pins : pins_type;
ユーザーが入力できる数字ではないリセット値を使用する必要があり、それが -1 の目的です。 ピンに 0 から 9 の範囲を使用した場合 秘密のパスコードを 0000 に設定すると、最初にボールトが開かれます。このスキームでは、ユーザーは明示的に 4 つの 0 を入力する必要があります。
実装
アーキテクチャ リージョンの上部に、ピン signal はジェネリック定数と一致します。以下のコードは組み合わせですが、ピン 信号がクロックされ、ロック解除 信号はクロックの立ち上がりエッジでのみ変化します。
unlock <= '1' when pins = (pin3, pin2, pin1, pin0) else '0';
以下のコードは、ユーザー入力を読み取るプロセスを示しています。 ピンからシフト レジスタを作成します input_enable の場合、すべての値をシフトすることで信号を送ります クロックの立ち上がりエッジで「1」になります。その結果、ユーザーが入力した最後の 4 桁が ピン に保存されます。
PINS_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then pins <= (others => -1); else if input_enable = '1' then pins(0) <= input_digit; pins(1 to 3) <= pins(0 to 2); end if; end if; end if; end process;
VHDL テストベンチ
まず、検証に Tcl を使用していますが、依然として基本的な VHDL テストベンチが必要です。以下のコードは、完全な VHDL ファイルを示しています。 DUT をインスタンス化し、クロック信号を作成しましたが、それだけです。クロックを生成すること以外、このテストベンチは何もしません。
library ieee; use ieee.std_logic_1164.all; entity code_lock_tb is end code_lock_tb; architecture sim of code_lock_tb is constant clk_hz : integer := 100e6; constant clock_period : time := 1 sec / clk_hz; signal clk : std_logic := '1'; signal rst : std_logic := '1'; signal input_digit : integer range 0 to 9; signal input_enable : std_logic := '0'; signal unlock : std_logic; begin clk <= not clk after clock_period; DUT : entity work.code_lock(rtl) generic map (1,2,3,4) port map ( clk => clk, rst => rst, input_digit => input_digit, input_enable => input_enable, unlock => unlock ); end architecture;
Tcl テストベンチ
この例の Tcl コードは、ModelSim VHDL シミュレータでのみ機能します。たとえば、Vivado で使用する場合は、いくつかの変更を加える必要があります。これは、このシミュレーターに固有のいくつかのコマンドを使用するためです。 Tcl を使用することの欠点は、コードが特定のシミュレーター ベンダーにロックされることです。
参考までに、Tcl 言語全般をカバーする Tcl Developer Xchange と、ModelSim 固有のすべてのコマンドを説明する ModelSim Command Reference Manual をお勧めします。
ModelSim がインストールされている場合は、以下のフォームを使用してサンプル プロジェクトをダウンロードできます。
名前空間の使用
最初にお勧めするのは、Tcl 名前空間を作成することです。そうしないと、Tcl スクリプトのグローバル変数を意図せず上書きしてしまう可能性があるため、これは良い考えです。すべてのコードを名前空間にラップすることで、潜在的な混乱を回避できます。これから記述するすべての Tcl コードを codelocktb 内に配置します。 以下に示すように、名前空間。
namespace eval ::codelocktb { # Put all the Tcl code in here }
以下に示すように、名前空間内でシミュレーションを開始する必要があります。 vsim でそれを行います コマンドの後に、VHDL テストベンチのライブラリ名とエンティティ名が続きます。これによりシミュレーションが読み込まれますが、実行されません。 run を使用するまでシミュレーション時間は経過しません スクリプトの後半でコマンドを実行します。波形が存在する場合はそれをロードする If ステートメントも含めたいと思います。
# Load the simulation vsim work.code_lock_tb # Load the waveform if {[file exists wave.do]} { do wave.do }
名前空間変数の宣言
シミュレーションをロードしたので、VHDL コードの操作を開始できます。まず、clock_period を読み取りたい 定数とパスコード ジェネリックを Tcl 環境に追加します。
以下のコードでは、ModelSim 固有の examine を使用しています。 VHDL 信号と Tcl の定数値を読み取るコマンド。次に、Tcl 文字列とリスト コマンドを使用して、時間値と時間単位を抽出します。 ピンコード variable は、汎用定数から読み取った 4 桁のリストになります。
# Read the clock period constant from the VHDL TB variable clockPeriod [examine clock_period] # Strip the braces: "{10 ns}" => "10 ns" variable clockPeriod [string trim $clockPeriod "{}"] # Split the number and the time unit variable timeUnits [lindex $clockPeriod 1] variable clockPeriod [lindex $clockPeriod 0] # Read the correct PIN from the VHDL generics variable pinCode [examine dut.pin0 dut.pin1 dut.pin2 dut.pin3]
Tcl スクリプトでは、VHDL コードとは異なるコーディング スタイルを使用していることに注意してください。アンダースコアの代わりに、キャメル ケーシングを使用しています。これは、Tcl スタイル ガイドに従っているからです。もちろん、お好みであれば、Tcl ファイルと VHDL ファイルで同じスタイルを使用することを妨げるものは何もありません。
また、名前空間なしで Tcl を使用したことがある場合は、Tcl で変数を定義する標準的な方法である set キーワードについてご存知でしょう。ここでは、代わりに新しい変数キーワードを使用しています。これは、グローバル スコープではなく、現在の名前空間に関連付けられたグローバル変数のようなものです。
最後に、errorCount という名前の変数を宣言します 以下に示すように、0に初期化します。シミュレーションがテスト ケースを進めていくにつれて、エラーが検出されるたびに値が増加します。最後に、モジュールが成功したか失敗したかを判断するために使用できます。
variable errorCount 0
ModelSim でのテキストの印刷
puts コマンドは、Tcl でテキストをコンソールに出力する標準的な方法です。しかし、この方法は ModelSim ではうまく機能しません。 Windows バージョンは期待どおりの動作をします。文字列をコンソールに出力します。一方、Linux 版では、テキストは GUI 内のコンソールではなく、ModelSim を起動したシェルに出力されます。
下の画像は、puts を入力したときに何が起こるかを示しています コマンドを ModelSim コンソールで実行します。背後の端末ウィンドウに表示されます。さらに悪いことに、デスクトップ ショートカットを使用して ModelSim を起動すると、シェルが非表示になるため、出力が表示されません。
puts の動作を変更するための回避策があります 指図。たとえば、再定義して (はい! Tcl で実行できます)、両方のプラットフォームで動作させることができます。しかし、Linux と Windows の両方でテキストをコンソールに出力するより簡単な方法は、ModelSim 固有の echo を使用することです。 コマンド。
以下に示すカスタム Tcl プロシージャを使用して、テキストを出力します。その際、メッセージの先頭に現在のシミュレーション時間を追加します。 ModelSim では、$now を使用していつでも取得できます。 グローバル変数。
proc printMsg { msg } { global now variable timeUnits echo $now $timeUnits: $msg }
N クロック サイクルのシミュレーション
DUT はクロック モジュールです。つまり、立ち上がりクロック エッジ間では何も起こりません。したがって、クロック サイクルの期間に基づいて段階的にシミュレートする必要があります。以下の Tcl プロシージャは clockPeriod を使用します と timeUnits
proc runClockCycles { count } { variable clockPeriod variable timeUnits set t [expr {$clockPeriod * $count}] run $t $timeUnits }
このプロシージャは 1 つのパラメータを取ります:count .これに 1 クロック周期の長さを掛けて、N クロック サイクルの期間を取得します。最後に、ModelSim run を使用します 正確にその長さをシミュレートするコマンド。
Tcl からのシグナル値のチェック
ModelSim では、examine を使用して Tcl から VHDL 信号を読み取ることができます。 指図。以下のコードは、信号値を読み取り、期待どおりであることを確認するために使用している Tcl プロシージャを示しています。シグナルが expectedVal と一致しない場合 パラメータ、厄介なメッセージを出力し、errorCount をインクリメントします
proc checkSignal { signalName expectedVal } { variable errorCount set val [examine $signalName] if {$val != $expectedVal} { printMsg "ERROR: $signalName=$val (expected=$expectedVal)" incr errorCount } }
PIN シーケンスのテスト
コード ロック モジュールからの出力は、現在の入力だけでなく、以前の値にも依存します。したがって、出力のチェックは、少なくとも 4 桁を DUT に送信した後に行う必要があります。 PIN が正しい場合にのみ、ロック解除信号が「0」から「1」に変化する必要があります。
以下の Tcl 手順では、ModelSim force を使用します。 VHDL シグナルを Tcl から変更するためのキーワード。 デポジット 力に切り替える キーワードは、ModelSim が値を変更することを意味しますが、テストベンチで DUT 入力を制御するエンティティは他にありませんが、後で別の VHDL ドライバーに制御させます。
proc tryPin { digits } { variable pinCode set pinStatus "incorrect" if { $digits == $pinCode } { set pinStatus "correct" } printMsg "Entering $pinStatus PIN code: $digits" foreach i $digits { force input_digit $i -deposit force input_enable 1 -deposit runClockCycles 1 force input_enable 0 -deposit runClockCycles 1 } if { $pinStatus == "correct" } { checkSignal unlock 1 } else { checkSignal unlock 0 } }
tryPin 手順は printMsg を使用します それが何をしているのか、どの PIN コードを入力しているのか、そしてそれが正しいパスコードであるかどうかを通知する手順。 runClockCycles も使用します PIN を入力するユーザーをシミュレートするために DUT 入力を操作しながら、正確に 1 クロック周期実行する手順。
最後に、checkSignal を使用します DUT が期待どおりに動作していることを確認する手順。すでに説明したように、checkSignal プロシージャはエラー メッセージを出力し、errorCount をインクリメントします。 ロック解除の場合は変数 シグナルが期待値と一致しません。
テストケースと終了ステータス
上記の Tcl コードでは、シミュレーションを開始し、一連の変数とプロシージャを定義しましたが、シミュレーションはまったく行っていません。シミュレーションはまだ 0 ns です。シミュレーション時間が経過していません。
カスタム名前空間の終わりに向かって、Tcl プロシージャの呼び出しを開始します。以下のコードに示すように、10 クロック サイクル実行することから始めます。その後、リセットを解除し、ロック解除を確認します 出力の期待値は「0」です。
runClockCycles 10 # Release reset force rst '0' -deposit runClockCycles 1 # Check reset value printMsg "Checking reset value" checkSignal unlock 0 # Try a few corner cases tryPin {0 0 0 0} tryPin {9 9 9 9} tryPin $pinCode tryPin [lreverse $pinCode] if { $errorCount == 0 } { printMsg "Test: OK" } else { printMsg "Test: Failure ($errorCount errors)" }
10000 種類の PIN コードをすべて試すこともできますが、それにはかなりの時間がかかります。 Tcl 駆動のシミュレーションは、純粋な VHDL テストベンチよりもはるかに遅くなります。シミュレーターは頻繁に開始および停止する必要があり、それには多くの時間がかかります。そのため、まれなケースのみを確認することにしました。
tryPin を呼び出します PIN コードで 4 回:0000、9999、正しい PIN、正しい PIN の数字を逆の順序で。コードロックを作成するときに、番号の順序ではなく組み合わせだけを見て、これは犯しやすい間違いだと思います.
最後に、Tcl コードの最後で、まだネームスペース内で、errorCount をチェックします。 変数を開き、「Test:OK」または「Test Failure」を出力します。
テストベンチの実行
テストベンチの実行です。以下に示すように、私は Tcl ソース コマンドを使用することを好みますが、ModelSim 固有の do コマンドを使用することもできます。 指図。実際、ModelSim DO ファイルは実際には、異なる接尾辞を持つ単なる Tcl ファイルです。
source code_lock/code_lock_tb.tcl
私のコードの最終バージョンでは、エラーはありません。以下のリストは、成功したシミュレーションからの出力を示しています。 Tcl スクリプトは実行内容を通知し、すべてのメッセージ行にタイムスタンプがあることがわかります。それが printMsg です 職場での手続き。最後に、テストベンチが停止し、「Test:OK」と表示されます。
VSIM> source code_lock/code_lock_tb.tcl # vsim ... # 110 ns: Checking reset value # 110 ns: Entering incorrect PIN code: 0 0 0 0 # 190 ns: Entering incorrect PIN code: 9 9 9 9 # 270 ns: Entering correct PIN code: 1 2 3 4 # 350 ns: Entering incorrect PIN code: 4 3 2 1 # 430 ns: Test: OK
ただし、DUT がテストに失敗したときの様子をお見せしたいと思います。そのために、コード ロック モジュールでエラーを作成しました。 pin1 のチェックを置き換えました pin2 で DUT が pin1 を無視するように 価値。以下のコードに示すように、これは簡単にタイプミスします。
unlock <= '1' when pins = (pin3, pin2, pin2, pin0) else '0';
テストベンチを実行すると、以下のリストから障害が検出されたことがわかります。そして最後に、テストベンチは「テスト:失敗」とエラー数を出力します。
VSIM> source code_lock/code_lock_tb.tcl # vsim ... # 110 ns: Checking reset value # 110 ns: Entering incorrect PIN code: 0 0 0 0 # 190 ns: Entering incorrect PIN code: 9 9 9 9 # 270 ns: Entering correct PIN code: 1 2 3 4 # 350 ns: ERROR: unlock=0 (expected=1) # 350 ns: Entering incorrect PIN code: 4 3 2 1 # 430 ns: Test: Failure (1 errors)
最終的な考え
私はキャリアの中で多くの Tcl ベースのテストベンチを作成してきましたが、それらに関する私の意見は多少分かれています。
一方では、VHDL だけでは不可能な優れたことができます。たとえば、インタラクティブなテストベンチです。また、再コンパイルせずにテストベンチを変更できるのも便利です。そして最後に、非常に異なる言語を使用した検証が有利になる可能性があります。検出されずに通過するには、2 つの異なるテクノロジで同じ間違いを犯す必要がありますが、その可能性は低いです。
一方で、デメリットもあります。 Tcl ベースのテストベンチは、対応する VHDL よりも大幅に低速です。もう 1 つの重要な問題は、ベンダー ロックインです。完全に移植可能な Tcl テストベンチを作成することは不可能ですが、VHDL テストベンチは機能するシミュレーターで実行できます。
そして、Tcl テストベンチに価値がない最後の理由は、言語そのものです。プログラミング エラーを防止する優れた機能はなく、Tcl の問題をデバッグするのは困難です。 Python や Java のような直感的で寛容な言語ではありません。
ただし、VHDL とソフトウェアの世界の間の接着言語としての役割を果たします。また、シミュレーターだけでなく、ほとんどの FPGA ツールが Tcl をサポートしているため、Tcl を学習することを強くお勧めします。
これらの考えは私の意見です。コメント セクションであなたの考えを教えてください!
VHDL