VHDLテストベンチの始め方

VHDLテストベンチの始め方

 テストベンチの検索が多いので、基本的な1つの回路を1つのテストベンチで検査する。という方法を詳しく解説いたします。

 テストベンチは次の図のように、自分が作った回路(黄色部分)が思い描いたように動いているのか? という検査をするための”回路”になります。図では緑で示した部分がテストベンチです。

 テストベンチを先に作る人はあまり居ないと思います。先にshift.vhdの部分を作ります。作るにあたり、何らかの資料がある。または自分の考えがあるはずです。そのために、入力信号・出力信号があります。ここでは、clock,nreset,indataが入力信号で、shiftoutが出力信号になっています。この部分はshift.vhdのentity部分に記載されます。shift.vhdのソースコード全文を以下に示します。

--  TITLE:					"shift.vhd"
--  MODULE NAME:			
--  PROJECT CODE:			
--  AUTHOR:					 (****@nakaharagiken.com)
--  CREATION DATE:			2020.10.17
--  SOURCE:            		
--  LICENSE:           		Copyright (c) 2020 nakaharagiken All rights reserved. 
--  DESCRIPTION:            シフトレジスタ
--  NOTES TO USER:			
--  SOURCE CONTROL:			
--  REVISION HISTORY:  	    v0.1	2020.10.17	First edition
--
--
library IEEE;												-- ライブラリの宣言
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
use IEEE.std_logic_arith.all;

entity shift is												-- ここからend entity shift;まで入出力信号の宣言
port (
	clock:				in	std_logic;						-- 入力信号:system clock
	nreset:				in	std_logic;						-- 入力信号:asynchronous reset 				____reset___|~~~normal~~~
	indata:				in	std_logic;						-- 入力信号
	shiftout:			out	std_logic						-- 出力信号
);
end entity shift;

architecture rtl of shift is								-- ここからend architecture rtl;までが一つの回路

---------------------------------------------------------------
-- SIGNAL宣言
---------------------------------------------------------------
signal	rDFF:			std_logic_vector(7 downto 0);		-- rDFF(0)~rDFF(7)までを用意。バスと呼ぶ
															-- 用意しただけで、フリップフロップかワイヤーなのかは不明
begin

------------------------------------------
-- 8bit シフトレジスタ:順序回路
-- process文の中でrDFFに入力することで、rDFFがフリップフロップになる
------------------------------------------
process (clock,nreset) begin								-- clockとnresetに変化があった時にend process;までを実行
	if nreset = '0' then									-- clockとnresetに変化があり、かつnreset = '0'のときのみelsifまでを実行
		rDFF <= (others => '0');							-- rDFF(0)~rDFF(7)を全部'0'にする
	elsif clock' event and clock = '1' then					-- clockとnresetに変化があり、かつclock 立ち上がりの時のみ、end if;まで実行
		rDFF(0) <= indata;									-- rDFF(0)にindataを入力する
		rDFF(1) <= rDFF(0);									-- rDFF(1)にrDFF(0)に入力する
		rDFF(2) <= rDFF(1);									-- rDFF(1)にrDFF(0)に入力する
		rDFF(3) <= rDFF(2);									-- rDFF(1)にrDFF(0)に入力する
		rDFF(4) <= rDFF(3);									-- rDFF(1)にrDFF(0)に入力する
		rDFF(5) <= rDFF(4);									-- rDFF(1)にrDFF(0)に入力する
		rDFF(6) <= rDFF(5);									-- rDFF(1)にrDFF(0)に入力する
		rDFF(7) <= rDFF(6);									-- rDFF(1)にrDFF(0)に入力する
	end if;
end process;

--->>> ここから組み合わせ回路
shiftout <= rDFF(7);										-- shiftoutにrDFF(7)を接続
---<<< ここまで組み合わせ回路
end architecture rtl;

 今回はテストベンチの回路解説なので、被テスト回路の解説は省きます。なるべく多くのコメントを記載しますので参照してください。

 自分で作った回路の入力信号と出力信号に対して、テストベンチでは接続する信号を作ります。上図の赤文字の部分です。そして、シミュレーションのソフトウェアにもよりますが、だいたいはこの赤文字の信号を解析するようになっているので、接続がわかるように作ってください。

 具体的には、黄色いブロックの被テスト回路の入力信号はclockですが、この信号の入力テストパターンを作る必要があります。上図の赤文字のi_clockです。つまり、clock <= i_clock というように接続します。シミュレーションでは、i_clockの信号を見ながら出力信号である、o_shiftoutを観察することになります。

 このように、実際に見るのはi_clockであり、o_shiftout信号です。clockやshiftout信号を見れない訳ではありませんが、ひと手間必要になります。したがって、i_clockやo_shiftoutといったように、その接続先がclockやshiftoutだとすぐにわかるようなネーミングにすることが勧められます。

 テストベンチでは、被テスト回路(shift.vhd)の入力信号パターンを作るわけですが、この時入力信号を2つのパターンに分けると良いと思います。

 一つはクロック信号のような連続信号。もう一つは、非連続信号です。このように明確に分ける事で、テストベンチの中で何処でパターンを作れば良いのか。という事が自然に決まってます。

 テストベンチのソースコードを掲載いたします。

--  TITLE:					"TB_shift.vhd"
--  MODULE NAME:			TB_sshift
--  PROJECT CODE:			
--  AUTHOR:					 (****@nakaharagiken.com)
--  CREATION DATE:			2020.10.17
--  SOURCE:            		
--  LICENSE:           		Copyright (c) 2020 nakaharagiken All rights reserved. 
--  DESCRIPTION:            shift.vhdのテストベンチ
--  NOTES TO USER:			
--  SOURCE CONTROL:			
--  REVISION HISTORY:  	    v0.1	2020.10.17	First edition
--
--
--
library IEEE;												-- ライブラリの宣言
use IEEE.std_logic_1164.all;
use IEEE.std_logic_unsigned.all;
use IEEE.std_logic_arith.all;

entity TB_shift is											-- ここからend entity shift;まで入出力信号の宣言
end TB_shift;												-- テストベンチに入出力信号が無いので、無記名

architecture sim of TB_shift is								-- ここからend architecture rtl;までが一つの回路(テストベンチ)

component shift is											-- component宣言:このモジュールを子モジュールとして使う宣言
port (
	clock:				in	std_logic;						-- 入力信号:system clock
	nreset:				in	std_logic;						-- 入力信号:asynchronous reset 				____reset___|~~~normal~~~
	indata:				in	std_logic;						-- 入力信号
	shiftout:			out	std_logic						-- 出力信号
);
end component;

---------------------------------------------------------------
-- SIGNAL宣言
---------------------------------------------------------------
constant SYSCLK_PERIOD : time := 100 ns; 				-- constantは定数宣言:time := 100 ns をSYSCLK_PERIODに置き換え(10MHz)
signal	i_clock:		std_logic;						-- i_clockを用意	system clockに使うつもり
signal	i_nreset:		std_logic;						-- i_nresetを用意 asynchronous resetに使うつもり
signal	i_indata:		std_logic;						-- i_indataを用意 入力信号に使うつもり
signal	o_shiftout:		std_logic;						-- o_shiftoutを用意 出力信号に使うつもり

begin

inst_shift : shift										-- インスタンス宣言:モジュールに信号を接続する
port map(												-- =>の向きに注意:入力も出力も同じ向き
	clock			=>	i_clock,						-- clockにi_clockを接続する
	nreset			=>	i_nreset,						-- nresetにi_nresetを接続する
	indata			=>	i_indata,						-- indataにi_indataを接続する
	shiftout		=>	o_shiftout						-- shiftoutにo_shiftoutを接続する
);

------------------------------------
-- Clock Driver
------------------------------------
process begin											-- 繰り返し信号の生成
	i_clock <= '1';										-- i_clock を'1'にする
	wait for SYSCLK_PERIOD / 2;							-- (time := 100 ns)/2 時間待つ
	i_clock <= '0';										-- i_clock を'0'にする
	wait for SYSCLK_PERIOD / 2;							-- (time := 100 ns)/2 時間待つ
end process;											-- 以上を繰り返す


------------------------------------
-- 信号作成
------------------------------------
process begin
	i_nreset <= '0';									-- i_nreset を'0'にする
	i_indata <= '0';									-- i_indata を'0'にする

	wait for (SYSCLK_PERIOD / 10);						-- (time := 100 ns)/10 時間待つ
	wait for (SYSCLK_PERIOD * 5);						-- (time := 100 ns)*5 時間待つ

	i_nreset <= '1';									-- i_nreset を'1'にする
	wait for (SYSCLK_PERIOD * 10);						-- (time := 100 ns)*10 時間待つ

	i_indata <= '1';									-- i_indata を'1'にする
	wait for (SYSCLK_PERIOD * 1);						-- (time := 100 ns)*1 時間待つ
	i_indata <= '0';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '0';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '1';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '0';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '1';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '0';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '1';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '1';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '1';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '0';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '0';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '1';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '0';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '0';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '1';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '0';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '1';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '1';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '1';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '1';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '0';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '1';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '0';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '1';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '0';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '1';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '0';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '1';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '0';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '0';
	wait for (SYSCLK_PERIOD * 1);
	i_indata <= '0';
	wait;											-- 時間無指定で待つ=ずっと待つ=終わり
end process;
end sim;

 このテストベンチのソースコードでは、56行目~61行目が連続信号である、クロック信号のパターンを生成しています。wait for 時間 でその間待つ。という指示になりますので、wait for SYSCLK_PERIOD / 2; という書き方が難しければ、wait for 50ns; でもOKです。このように連続する信号は一つのprocessで一つの信号だけ(この場合i_clockだけ)記載するのが分かりやすいと思います。いくつも連続信号があれば、その数ぶんprocessで作ります。

 67行目から非連続信号の記述になります。同じprocess文なので、記述方法に区別はありませんが、process文の最後でwait;を記載して停止していますので、連続波形にはなりません。逆に言えば、最後のwait;を消せば連続してシミュレートします。

 非連続波形はwaitを巧みに使って入力波形を生成しています。ここでは、i_nresetとi_indataを生成しています。このようにクロック以外の信号は非連続信号として一つのprocess文にまとめて書く方が全体の時間配分が分かりやすいと思います。

 waitの時間をSYSCLK_PERIODで定数化したのは、時間よりもクロックを単位にして信号を作る方が分かりやすいからです。300nsecウェイトするよりも、3クロックのウェイト。という方がクロック同期設計をする際に考えやすいからです。

 このテストベンチは67行目以降のwaitを使ってひたすら、信号を’1’にしたり’0’にしたりを繰り返してi_indataのHi/Lowパターンを作っています。結構、複雑な回路になると、これはかなり大変な作業になりますが、慣れればすぐに書けるようになります。

 とは言っても、被テスト回路のハンドシェイク回路やバッファー操作など、出力信号によって、入力信号を変化させなくてはならない場合が出ていきます。このような時には、wait命令を時間制限ではなく、以下のように操作することができます。

wait until o_shiftout’ event and o_shiftout=’1′; — o_shiftout の立ち上がりを待つ

wait on o_shiftout; — o_shiftoutの変化まで待つ

wait on o_shiftout for 300ns; — o_shiftoutの変化か、300nsec待つ

wait until o_shiftout’ event and o_shiftout=’1′ for 300ns; — o_shiftoutの立ち上がりか300nsec待つ

 以上のような遅延(wait)を使って時間や事象を検出して信号の変化するきっかけを記載していけばテストベンチを作ることができます。

 もし、出力信号を使わないのであれば、o_shiftoutのように出力信号に名前をつけて、インスタンスで接続するだけで完了です。

 本テストベンチの実行結果はアイキャッチ画像のようになっています。このように1つの回路(vhdlファイル)を1つのテストベンチファイルで検査するのは基本的なテストベンチの方法です。連続波形とそでない波形をはっきりと分けて記載することで、テストベンチは簡単になります。もし、連続波形が2つや3つあるのであれば、その分process文を増やせば良いだけです。ただし、同期はスタートからの時間が必要になるので、工夫はいるかも知れません。