テストベンチ専用の文法を学ぶ
- ・テストベンチを記述する上で最低限必要な文法を理解する
- ・信号強度や遅延,各種ステートメントなどについて理解する
以下の項目を理解し説明できる
- ・モジュールアイテムとステートメント
- ・プリミティブゲート
- ・信号強度
- ・タイミング制御と遅延
- ・各種ステートメント(for,while,force,release,fork〜join)
- ・階層アクセス
今までも文法のことは学びましたが、テストベンチを書くにはもう少し勉強が必要です。回路記述にはverilogHDLの一部しか使えませんでしたが、テストベンチは何を使ってもかまいません。色々覚えていく方が後で絶対に役にたちます。
- ・各種宣言
- ・プリミティブ・ゲート接続
- ・下位モジュール接続
- ・継続的代入(assign文による代入)
- ・function
- ・task
- ・initial文
- ・always文
- など
- ・if文
- ・case文
- ・for文
- ・while文
- ・force文
- ・release文
- ・begin〜endブロック
- ・fork〜joinブロック
- ほか
回路記述やテストベンチはモジュール構造で記述します。
モジュール内に直接記述できる要素をモジュールアイテムと呼びます。
モジュールアイテムにはinputやoutputの宣言やregやwireの宣言、ANDやORなどのプリミティブゲート接続、下位モジュールの接続、assign文による継続的代入があります。
さらにfunctinや、taskの定義部分や、initial文、always文もモジュールアイテムです。
モジュールアイテムはモジュール内での順序は自由です。また、複数記述することもできます。
回路記述でも用いたif文やcase文は文法的にステートメントと呼ばれます。ステートメントはモジュールアイテムではありませんので、モジュール内に直接記述することはできません。
つまりassign文などと並べて、if文やcase文を書くことはできないのです。
ステートメントはfunction、task、initial文、always文の中だけで記述できます。
ステートメントには条件分岐のif文、case文、ループ構造のfor文、while文、強制代入のforce文、release文などがあります。
また複数のステートメントをまとめてひとつの文とするbegin~endとfork~joinがあります。
ステートメントの紹介はあとのページで行います。
| ゲート | スリーステート | スイッチ | プルアップ,プルダウン |
|---|---|---|---|
|
and nand nor or xor xnor buf not |
bufif0 bufif1 notif0 notif1 |
nmos
pmos cmos rnmos rpmos rcmos
tran
tranif0 tranif1 rtran rtranif0 rtranif1 |
pullup pulldown |
verilogHDLには基本的なゲート回路が組み込まれています。これらは宣言や定義をしなくても使うことが出来ます。プリミティブゲートはシミュレーションのためのライブラリなどに使用します。
ゲートやスリーステートは多くの論理合成ツールで合成できますので、回路記述に利用することが出来ます。
ゲート回路
nor NR1( E, ANOUT, C, D );
ゲート回路
ゲート回路の接続は、ゲート名、インスタンス名、出力信号、入力信号の順に記述します。
入力信号を多数記述することで多入力のゲート回路を記述できます。
プリミティブゲートの接続ではインスタンス名を省略することができます。
これはANDゲートとNORゲートを接続した回路です。入力A、B、C、Dがそれぞれのゲートの入力に接続され、出力EがNORゲートの出力に接続されています。ANDゲートAN1の出力ANOUTがNORゲートNR1の入力に接続されています。
スリーステート
スリーステート
スリーステートとは1、0、ハイインピーダンスの3値をもつゲート回路です。
スリーステートの接続では、ポートリストの3番目にコントロール信号を記述します。
コントロール信号とはゲートのON、OFFを制御する信号です。
スリーステートのbufif1では、コントロール信号EN=1のとき、ゲートがONし、EN=0のとき、ゲートがOFFします。ゲートがOFFのときの出力はハイインピーダンスです。どこにもつながっていない状態と同じです。ゲートがONのときは入力の値がそのまま出力に伝わります。
スリーステートにはコントロール信号が0でゲートがONするbufif0、出力が反転しているnotif1、notif0があります。
スイッチ
スイッチ
これはトランジスタレベルのスイッチ回路です。スイッチの接続ではスリーステートと同様にポートリストの3番目にコントロール信号を記述します。動作もスリーステートとほぼ同様です。
ここではnmosトランジスタを記述しています。
コントロース信号EN=1のときゲートがONし、EN=0のときゲートがOFFします。ゲートがOFFのとき出力はハイインピーダンスです。
スイッチにはこのほかにpmos、cmosなどがあります。
プルアップ,プルダウン
プルアップ、プルダウン
これはプルアップ抵抗やプルダウン抵抗を表現したものです。出力だけなので、ポートリストは1つだけです。プルアップでは出力は1に、プルダウンでは出力が0に固定されます。さらに信号強度がプルレベルになります。信号強度については次のページで紹介します。
| supply0 | supply1 |
| strong0 | strong1 |
| pull0 | pull1 |
| large0 | large1 |
| weak0 | weak1 |
| medium0 | medium1 |
| small0 | small1 |
| highz0 | highz1 |
信号値の1や0に対し、8段階の信号強度をもつことが出来ます。
表に示したようにsupply、strong、pullなどの強度があり、それぞれ信号値0のときの強度、1のときの強度を個別に設定できます。
信号強度は呼び出したプリミティブゲートの出力に設定したり、assign文による代入先に付加することが出来ます。信号強度を指定しない場合、strongの強度が付加されます。
複数の信号の出力同士が重なっているとき、信号強度が強い方の出力を優先します。
buf (strong0, strong1) A_BUF( DOUT, A ); buf (pull0, pull1) B_BUF( DOUT, B ); assign (strong0, strong1) DOUT = A; assign (pull0, pull1) DOUT = B;
例えば図のように二つのバッファ回路の出力が共通になっているときを考えてみます。出力DOUTの信号強度は片方がstrong、もう片方がpullとします。
プリミティブゲートとassign文で記述した例を示します。
このとき入力Aの値が1で、入力Bが0のとき、strong側の出力が優先して、出力DOUTは1となります。
仮に両方のバッファとも同じ信号強度の場合、出力は1と0が衝突して不定となります。
信号強度は論理合成できません。
付加しても論理合成ツールでエラーになるか無視されてしまいます。
レジスタ型
| レジスタ名 | 機能 |
|---|---|
| reg integer time real |
符号なし,任意ビット数 符号付き,32ビット 符号なし,64ビット 実数 |
reg [31:0] A, B, C; // 32ビット符号なし integer I, J, K; // 32ビット符号付き
ネット型
| ネット名 | 機能 | |
|---|---|---|
|
wire wor wand tri0 supply0 trireg |
tri trior triand tri1 supply1 |
通常のネット,wireとtriは同じ意味 ワイアードORネット,worとtriorは同じ意味 ワイアードANDネット,wandとtriandは同じ意味 プルアップ,プルダウンされたネット 電源ネット 電荷蓄積ネット |
回路記述ではregとwireの二つの型しか使いませんでしたが、verilogHDLには多くのデータタイプが用意されています。
いろいろなテストベンチを記述する上ではここで紹介するデータタイプを利用することもあります。
まず最初にレジスタ型です。レジスタ型の中にはreg、integer、time、realの4つの型があります。テストベンチで多く利用するのはregとintegerです。integerは32ビットの符号つきの型です。テストベンチの中で変数として使用されます。32ビットで宣言したregとの違いは符号の有無です。
ネット型にはさまざまな型があります。ネット名は異なっても同じ意味のネット信号もあります。wireとtri、worとtrior、wandとtriandは同じ意味です。
テストベンチでもこれらのいろいろなネット型を使うことはほとんどありません。多くの場合wireだけ用います。
always文内で使用
// クロックの記述
always begin
CLK=0; #(STEP/2);
CLK=1; #(STEP/2);
end
// 組み合わせ回路
always @( A or B ) begin
Q <= A & B;
end
initial文内で使用
initial begin // 1周期ごとに入力を変化 #STEP RES=1; #STEP RES=0; // CLKの立ち下がりを待つ @( negedge CLK ) ENABLE=1; @( negedge CLK ) ENABLE=0; end
#や@を用いた記述はタイミング制御と呼ばれ、遅延をつくるための記述です。いずれもテストベンチの記述ではかかせません。遅延を直接表現するのが#による遅延制御です。#に続いて定数式を記述し、この定数式の示す期間処理の実行が遅れます。
一方@は、つづいて記述されたイベント式のイベントが発生するまで処理の実行を遅らせることが出来ます。この記述をイベント制御と呼びます。イベントとは信号の変化と考えてもよいでしょう。
イベント式の中では立ち上がりを示すposedge、立下りを示すnegedgeなどエッジを限定する予約語をつけることができます。これらをつけなければ両エッジとなります。また、イベント同士の論理和演算にORを用いることが可能です。
タイミング制御をalways文やinitial文の中で用いた例をしまします。
always文ではクロックの周期の記述や、組み合わせ回路の入力に使っています。
initial文の中では入力を順次変化させる場合の遅延に用いています。クロックCLKの立下りをまちそれぞれENABLE信号をON、OFFしています。タイミング制御はシミュレーションだけで使うものと考えてください。
つまり#の遅延制御は論理合成の対象外です。記述しても論理合成ツールで無視されます。さらに@のイベント制御もalwaysとともに利用した組み合わせ回路と順序回路の記述だけが論理合成できます。
(1) ネットに対する遅延
#を使った遅延制御は遅延を与える対象によって、記述の仕方が変わります。
まずネットに対する遅延です。
assign文の代入記述に遅延を付加すると、代入先の変化を遅延できます。
この場合加算回路の入力が変化してから出力が変化するまでの遅延が100ユニットとなります。つまりゲート遅延が付加されたことになります。
また信号を宣言したときに付加した遅延はブロック間の伝搬遅延になります。
ブロックAとブロックBを接続するdbus信号には20ユニットの遅延が付加されています。
(2) レジスタに対する遅延
initial begin
#STEP RES = 1;
#STEP RES = 0;
end
always @( posedge CLK )
cnt <= #1 cnt + 8'h1;
- ・代入文の「代入処理」だけが遅延
- ・「右辺の評価」は遅延しない
次にレジスタに付加した遅延です。
代入文の先頭に付加した遅延はすべてが遅延しますが、代入文の右辺につけた遅延は代入処理だけが遅延します。つまり右辺の式の評価は遅延しません。
このためcnt+1を先に演算してから1ユニット後にcntに代入されます。
(3) 立ち上がり/立ち下がり遅延と min / max 遅延
信号の立ち上がり、立下りに対して別の遅延量を与えることが出来ます
#のあとの括弧内に二つの値をコンマで区切れば、それぞれ立ち上がり、立ち下りの遅延量になります。
3つの値を記述すると3つめはトライステイト出力などのハイインピーダンスに変化する時間となります。
さらにmin、typ、maxの三種の遅延を与えシミュレーションの実行時に選択することもできます。
<ステートメント>
integer i;
parameter STEP = 1000;
reg [15:0] mem[0:255];
initial begin
// レジスタ配列の初期化
for ( i=0; i<256; i=i+1 )
mem[i] = 16'h0000;
...
// 入力に印加
for ( i=0; i<256; i=i+1 )
#STEP din = mem[i];
end
いずれもループをつくるための制御構造です。forに続いて括弧の中に代入文、式、さらにもう一つの代入文を記述します。
代入文1はfor文の実行時に一回だけ実行されます。
式はステートメントを実行するかどうかを判断する条件判別式です。真ならステートメントを実行し、偽なら何も実行せずにfor文を終了します。
代入文2はステートメントのあとに実行する代入文です。慣例的にfor文では代入文1でループ用の変数の初期化を行い、条件判別式でループ回数を判断し、代入文2ではループ変数のカウントアップを行います。
for文の利用例を二つ示します。どちらもループ変数にintegerのiを用いた256回のループを構成しています。
最初のループではレジスタ配列memの値をすべて0に初期化してます。
このループでは遅延を与えていませんので、シミュレーション時刻0のまま実行します。
二番目のループではレジスタ配列memの内容をdinに与えています。
このループでは遅延を与えていますので、1ステップごとにdinに入力を与えていることになります。
integer i;
parameter STEP = 1000;
reg [15:0] mem[0:255];
wire BUSY;
initial begin
// for文の置き換え
i = 0;
while ( i<256 ) begin
mem[i] = 16'h0000;
i = i + 1;
end
...
// BUSYが0になるのを待つ
while ( BUSY==1'b1 ) #STEP;
end
while文はwhileに続いて、括弧の中に式を記述します。
この式はステートメントを実行するかどうかを判断する条件判別式です。真ならステートメントを実行し、偽なら何も実行せずにwhile文を終了します。
while文の利用例を示します。
最初の例はfor文の例をそのままwhileに置き換えた例です。二つの代入文の記述場所が適切ならそのまま置き換え可能です。
次の例では、回路動作の処理終了を待っています。BUSY信号が1の間遅延だけを実行しています。BUSY信号が0になれば、このループを抜け出します。
wire ENABLE;
initial begin
...
...
force ENABLE = 1;
...
...
release ENABLE;
...
end
force文は強制的に値を代入できる公文です。ネット信号や回路の出力信号に値を代入することができます。実際の回路では実現できないような状態をつくりだすことで、検証を効果的に行える場合があります。
ここではwire宣言した信号ENABLEに対し、force文で1を代入しています。
initial文内ではネット信号への代入を記述すると文法エラーですが、force文を使えば花王です。
force文により強制代入した値をもとに戻すのが、release文です。release文を実行した時点で、元から駆動されていた信号に戻ります。
initial fork
#0 A = 0; B = 1; C = 0;
#50 A = 1;
#200 ;
#150 B = 0;
#100 A = 0;
#100 C = 1;
join
いくつかの文をひとつの文としてまとめる複合文としてbegin~endがありました。
begin~endは順次処理ブロックと呼ばれ、記述の順番に処理を行う複合文です。
これに対応して、fork~join があります。fork~joinは並列処理ブロックとよばれ、個々の記述が並列に処理されるブロックです。並列処理の具体例を紹介します。
ここではfork~joinの並列処理ブロックを1つ含んだinitial文があります。
fork~joinの中には遅延とともに代入文がいくつかあります。
begin~endで囲われた代入文ならば、この遅延は前の文を実行してからの相対的な遅延になりますが、fork~joinで囲われていますので、実行する時刻をあらわす絶対的な遅延になります。
したがってここで記述している時刻は時刻0からのシミュレーション時刻になります。
実行される順序は遅延量の少ない順になりますので、記述の順番通りにはなりません。
end
join
initial fork
#0 A = 0; B = 1; C = 0;
#50 A = 1;
#200 $finish;
#150 B = 0;
#100 A = 0;
#100 C = 1;
join
begin~end と fork~join
複数のステートメントをまとめてひとつの文とするのがbegin~endとfork~joinです。これをブロックステートメントと呼びます。
begin~endはステートメントを記述順に実行し、fork~joinは並列に実行します。
fork~joinの中では、それぞれの行の遅延時間はinitial文が起動した時刻0からの遅延となります。したがって、実行される順序は記述順ではなく時間の短い順になります。
テストベンチ
assign SIG_B = CHIP.BLK_B.B;
assign SIG_C = CHIP.C;
テストベンチの記述の中で検証対象の内部信号を参照するにはどうしたらよいでしょうか。たとえば検証対象の回路、チップの内部信号A、B、Cをテストベンチの記述の中で参照するとします。
検証対象の入力出力信号は直接参照できますが、内部信号を参照するためには特別な記述を必要とします。
内部信号を参照するためには、最上位階層からみた経路でインスタンス名をドットで結んで経路を記述します。モジュール名ではなく、インスタンス名であることに注意してください。
ブロックAの信号を参照するにはCHIP.BLK_A.A
チップ下位層の信号Cを参照するにはCHIP.Cのように記述します。
ステートメントの区別
次の記述のうち,文法的に
誤っている行はどれか
(完全解答)
DFF D1( CK, RES, D, Q, .QB(QB) );
→ポート接続には順番と名前によるものがありますが、両者を混在できません。
always @ ( posedge CK ) Q <= D;
→always文はモジュールアイテムです。initial文の中には記述できません。
if ( RES == 1 ) Q = 0;
→if文はステートメントです。モジュールアイテムではありませんので、ここには記述できません。
QB = 1
→QBはワイヤ宣言した信号ですので、ここでは代入できません。
QB = 0
→QBはワイヤ宣言した信号ですので、ここでは代入できません。
各入力が以下の値のとき,出力はどうなるか。(0, 1, x, z で解答する)
bufif1は、コントロール信号が0の時、出力はハイインピーダンス(Z)となる。
supplyとpullの信号が衝突したとき、信号強度が強いsupply側の値となる。
上側のバッファだけONしている。
supplyとpullの信号が衝突したとき、信号強度が強いsupply側の値となる。
(14)修了判定3
プログラミング
コメントに示した信号を発生するように,記述を完成させる。
parameter STEP=1000;
integer i;
reg CK, RES, TRIG;
wire [7:0] Q;
always begin // クロックの作成
CK=0; #(STEP/2);
CK=1; #(STEP/2);
end
DUT D1( CK, RES, TRIG, Q );
initial begin
// 1周期分RESをON/OFF
RES = 1;
#STEP RES = 0;
// TRIGのON/OFFを5回繰り返す
( i=
; i<5; i=i+
) begin
#STEP TRIG = 1;
#STEP TRIG = 0;
end
// 出力Qが12になるのを待つ
(
8'd12 ) #STEP;
$finish;
end
お疲れさま!テストベンチ特有の文法、少し難しかったかな?🐾
遅延制御やシステムタスクなど、シミュレーションのための強力な武器を手に入れたね!
