4.1 カウンターのシミュレーション
4.1 カウンターのシミュレーション
8bitカウンターを例にテストベンチについて説明します。
テストする回路
検証するカウンターは下の8bitカウンターです。
/* 8-bit conter */ module COUNTER( CLK , // in : System clock RST , // in : System reset CE , // in : Clock enable Q // out : Count[7:0] ); //-------- Input/Output ------------- input CLK ; input RST ; input CE ; output [7:0] Q ; //-------- 8-bit counter ------------- reg [7:0] Q ; always@ (posedge CLK) begin if(RST)begin Q[7:0] <= 8'd0; end else begin if(CE)begin Q[7:0] <= Q[7:0] + 8'd1; end end end endmodule
このモジュールのポートは下の部分です。
CLK , // in : System clock RST , // in : System reset CE , // in : Clock enable Q // out : Count[7:0]
CLK, RST, CEを与えないとこの回路は動きません。そこで、テストベンチでこれらの信号を生成する必要があります。
テストベンチ
必要最低限必要な機能に注意してテストベンチを書いてみました。
/******************************************************************************* * * * カウンターのテストベンチです * * * *******************************************************************************/ `timescale 1ps/1ps `include "COUNTER.V" module COUNTER_TB; reg OSC50M ; reg CLKENB ; reg RST ; wire [7:0] Q ; //------------------------------------------------------------------------------ // DUT //------------------------------------------------------------------------------ COUNTER DUT( // System .CLK (OSC50M ), // in : System clock .RST (RST ), // in : System reset .CE (CLKENB ), // in : Clock enable .Q (Q[7:0] ) // out : Count[7:0] ); //------------------------------------------------------------------------------ // Clock generator //------------------------------------------------------------------------------ parameter OSC50M_PERIOD = 20000; // ps initial begin OSC50M = 1'b0; end always #(OSC50M_PERIOD/2) begin OSC50M <= ~OSC50M; end reg [1:0] clkEnbCntr ; initial begin CLKENB = 1'b0; clkEnbCntr = 2'b0; end always@ (posedge OSC50M) begin clkEnbCntr[1:0] <= clkEnbCntr[1:0] + 2'd1; CLKENB <= (clkEnbCntr[1:0]==2'd1); end //------------------------------------------------------------------------------ // Reset generator //------------------------------------------------------------------------------ initial begin RST = 1; repeat(20) @(negedge OSC50M); RST = 0; end //------------------------------------------------------------------------------ // Test vector //------------------------------------------------------------------------------ initial begin @(negedge OSC50M); while(RST) @(negedge OSC50M); repeat(100) @(negedge OSC50M); $finish; end //------------------------------------------------------------------------------ endmodule
初めて登場した記述が幾つかあると思います。上から順に説明します。
環境設定
テストベンチでもモジュールの範囲はmoduleからendmoduleまでです。キーワードmoduleの前の文は環境設定です。この例ではシミュレーションを行う時間の単位、設計した回路の読み込みを指定しています。
`timescale 1ps/1ps
これはシミュレーションを行う時間単位を指定しています。`timescaleがキーワードです。シミュレーションは連続時間で行うのではなく離散時間で行います。設計した回路に合わせて設定します。キーワードに続く1ps/1psが単位時間と丸め精度を表しています。通常は同じ値に設定します。
- Xilinx FPGA
- Xilinx社から提供されている回路ライブラリは`timescale 1ps/1psが仮定されているものが多いようです。Xilinx FPGAを使用する時はライブラリを使用しなくても1ps/1ps設定が無難です。
`include "COUNTER.V"
設計した回路の読み出しです。シミュレータによっては`include文を使用しないでシミュレータのプロジェクトファイルで設定しないと誤動作するものがあります。自分の環境に合わせて設定してください。ここではVeritak使用を仮定して話を進めます。
モジュール宣言と内部信号宣言
module COUNTER_TB; reg OSC50M ; reg CLKENB ; reg RST ; wire [7:0] Q ;
今まで説明した回路と同様テストベンチも一つのモジュールですのでmoduleからendmoduleまでがモジュール内動作の記述です。
設計する回路と異なりポートリストがありません。これは、開発した回路を動作させる環境を記述した回路なのでこのモジュールが最上層モジュールであり閉じていなければいけないからです。
次は内部信号の記述です。テストするカウンター回路に入力する信号はクロック、クロックイネーブル、リセットでした。それらの信号を与えるので記憶素子として宣言してあります。記憶素子として宣言する理由を説明します。信号を与えるには時間と値を指定します。例えば時間0ではクロックはL、10ns時にH、20ns時にLなど。変化点の時間と値を指定し変化点以外では値を保持するようにします。これは、値の設定方法が論理回路ではありませんが値を記憶する記憶素子です。直接値を設定できるテストベンチ用記憶素子だと考えください。最後にカウンターの出力信号の為のwire宣言があります。
テストする回路の組み込み
テストする回路の事をDevice Under Test(DUT)と呼びます。
組み込み方法は通常の回路設計時と同じです。
//------------------------------------------------------------------------------ // DUT //------------------------------------------------------------------------------ COUNTER DUT( // System .CLK (OSC50M ), // in : System clock .RST (RST ), // in : System reset .CE (CLKENB ), // in : Clock enable .Q (Q[7:0] ) // out : Count[7:0] );
クロック生成回路
クロック生成回路には良く使われる記述方法があります。クロック生成は下の部分です。
//------------------------------------------------------------------------------ // Clock generator //------------------------------------------------------------------------------ parameter OSC50M_PERIOD = 20000; // ps initial begin OSC50M = 1'b0; end always #(OSC50M_PERIOD/2) begin OSC50M <= ~OSC50M; end
クロック周期の設定
parameter OSC50M_PERIOD = 20000; // ps
キーワードparameterは定数宣言です。OSC50M_PERIODは20000と定義されています。シミュレーション環境は1psを時間単位と設定さているので20nsに相当します。
下のalways文で使用する時に直接数字を使用しても良いのですがコードの読みやすさを考慮して定数を使用しています。この様にすると50M発振器の周期だとすぐにわかります。
クロック信号の初期化
initial begin OSC50M = 1'b0; end
initial文で初期化しています。initial文はシミュレータ実行時に一度だけ実行される命令でテストベンチで良く使用されます。ここでは''OSC50M = 1'b0;''の一行だけですので、シミュレーション開始時に0に設定されます。最初0に設定されたOSC50Mは記憶素子出力なので何もしなければこの後は0のままです。
- 回路合成時の注意
- initial文を回路記述に使用しないでください。使用しなくても問題ないはずですが、どうしても使用したい時は合成ツールのマニュアルを良く読んでから使用するようにしてください。
クロック生成
always #(OSC50M_PERIOD/2) begin OSC50M <= ~OSC50M; end
このalwaysは以前登場したものと少し違います。以前@だった文字が#へ変わっています。
@は条件式が成立した時に実行しますが、#は#の次に書かれている値だけ待ちます、ここではOSC50M_PERIOD/2。#の値だけ待つことを毎回alwaysで実行するので10ns毎に実行します。実行する内容はOSC50Mの出力を反転させた値を次の出力に設定です。
結果として初期値0、その後は10ns毎に値を反転させる動作になりますのでクロックが生成された事になります。
クロック・イネーブル生成回路
クロックイネーブルの回路としてOSC50Mで数えて4クロック毎に1クロック周期Hになる回路を作りました。
reg [1:0] clkEnbCntr ; initial begin CLKENB = 1'b0; clkEnbCntr = 2'b0; end always@ (posedge OSC50M) begin clkEnbCntr[1:0] <= clkEnbCntr[1:0] + 2'd1; CLKENB <= (clkEnbCntr[1:0]==2'd1); end
クロック生成の時と同じです。
最初にクロックイネーブルを生成する為の分周用カウンター信号を宣言しています。
その後initial文で初期化を行い、通常の回路と同じ記述方法でalways文を使用しています。
クロック生成回路でも書きましたが、設計するモジュールの回路記述でinitial文で初期化しalways文で回路動作記述する方法は使わないでください。
リセット生成回路
リセット生成をテストベクタの中に記述する事も出来ますが、ここではテストベクタと分けて独立に記述してみました。
//------------------------------------------------------------------------------ // Reset generator //------------------------------------------------------------------------------ initial begin RST = 1; repeat(20) @(negedge OSC50M); RST = 0; end
initial文ですから、シミュレータ起動時に文中の命令を上から下へ一度だけ実行します。
最初RST = 1;によりRST信号はHに設定されます。
repeat(20) @(negedge OSC50M);
キーワードrepeat(20)は繰り返し命令です。repeat()の次の命令@(negedge OSC50M)を括弧中の数字20回繰り返します。この分は@(negedge OSC50M)を20回繰り返します。
@(negedge OSC50M)はOSC50Mの立下り時のみ動作する条件ですから、この文はOSC50Mの立下りを20回見つけるまで繰り返す、すなわち、OSC50Mの20クロックだけ待つ、命令になります。
次にRST = 0でRST信号がLに設定されています。
以上の記述でシミュレータ起動時からOSC50Mで20クロック時間リセットをかける回路を模しています。
テストベクタ
//------------------------------------------------------------------------------ // Test vector //------------------------------------------------------------------------------ initial begin @(negedge OSC50M); while(RST) @(negedge OSC50M); repeat(100) @(negedge OSC50M); $finish; end //------------------------------------------------------------------------------ endmodule
今までの説明に登場していないのはwhileと$finishを説明します。
whileは括弧の中の条件式が成立している間、次に書かれている命令@(negedge OSC50M)を実行し続けます。RST=Hの時は立下りエッヂを待ち続けるのでRST=Lになった直後の立下りエッヂで次の行を実行します。
$finishはシミュレーション終了命令です。Veritakの場合、この命令が無いと走り続けます、止まりません。
テストベクタの動作をまとめます。シミュレータが起動するとシステムクロックの立下りを待ちます。その後リセットが解除されるまで待ち、100クロック時間後にシミュレーションを停止します。
シミュレーション結果
Veritakでのシミュレーション結果は下の様になり、正しくカウンタ動作している事が分かります。