gantt title initial文 と always文 の実行タイミング dateFormat s axisFormat %S section シミュレーション開始 時刻0で一斉にスタート : milestone, 0, 0s section initial文 1回だけ実行して終了 : active, 0, 3s section always文 無限に繰り返し実行 : 0, 10s

テストベンチ専用の文法を学ぶ

(1) ここで学ぶ内容
概要
  • ・テストベンチを記述する上で最低限必要な文法を理解する
  • ・信号強度や遅延,各種ステートメントなどについて理解する
目標

以下の項目を理解し説明できる

  • ・モジュールアイテムとステートメント
  • ・プリミティブゲート
  • ・信号強度
  • ・タイミング制御と遅延
  • ・各種ステートメント(for,while,force,release,fork〜join)
  • ・階層アクセス
修了判定1
モジュールアイテムとステートメントの区別を行う
修了判定2
信号強度の異なるプリミティブゲートの出力値を求める
修了判定3
for文とwhile文を使ってテストベンチを記述する

今までも文法のことは学びましたが、テストベンチを書くにはもう少し勉強が必要です。回路記述にはverilogHDLの一部しか使えませんでしたが、テストベンチは何を使ってもかまいません。色々覚えていく方が後で絶対に役にたちます。

(2) モジュールアイテムとステートメント
module モジュール名(ポートリスト);
モジュール・アイテム
  • ・各種宣言
  • ・プリミティブ・ゲート接続
  • ・下位モジュール接続
  • ・継続的代入(assign文による代入)
  • ・function
  • ・task
  • ・initial文
  • ・always文
  •  など
endmodule
ステートメント
  • ・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があります。

ステートメントの紹介はあとのページで行います。

(3) プリミティブ・ゲート
ゲート スリーステート スイッチ プルアップ,プルダウン
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には基本的なゲート回路が組み込まれています。これらは宣言や定義をしなくても使うことが出来ます。プリミティブゲートはシミュレーションのためのライブラリなどに使用します。

ゲートやスリーステートは多くの論理合成ツールで合成できますので、回路記述に利用することが出来ます。

ゲート回路

A
B
AN1
ANOUT
C
D
NR1
E
and AN1( ANOUT, A, B );
nor NR1( E, ANOUT, C, D );
ゲート名 インスタンス名 (出力,入力,入力,・・);

ゲート回路

ゲート回路の接続は、ゲート名、インスタンス名、出力信号、入力信号の順に記述します。

入力信号を多数記述することで多入力のゲート回路を記述できます。

プリミティブゲートの接続ではインスタンス名を省略することができます。

これはANDゲートとNORゲートを接続した回路です。入力A、B、C、Dがそれぞれのゲートの入力に接続され、出力EがNORゲートの出力に接続されています。ANDゲートAN1の出力ANOUTがNORゲートNR1の入力に接続されています。

スリーステート

A
BF1
EN
X
EN=1
EN=0
Hi-Z
bufif1 BF1( X, A, EN );
ゲート名 インスタンス名 (出力,入力,コントロール);
bufif0
notif1
notif0

スリーステート

スリーステートとは1、0、ハイインピーダンスの3値をもつゲート回路です。

スリーステートの接続では、ポートリストの3番目にコントロール信号を記述します。

コントロール信号とはゲートのON、OFFを制御する信号です。

スリーステートのbufif1では、コントロール信号EN=1のとき、ゲートがONし、EN=0のとき、ゲートがOFFします。ゲートがOFFのときの出力はハイインピーダンスです。どこにもつながっていない状態と同じです。ゲートがONのときは入力の値がそのまま出力に伝わります。

スリーステートにはコントロール信号が0でゲートがONするbufif0、出力が反転しているnotif1、notif0があります。

スイッチ

EN
X
A
N
EN=1
EN=0
nmos P1( X, A, EN );
ゲート名 インスタンス名 (出力,入力,コントロール);
P
pmos
N
P
cmos

スイッチ

これはトランジスタレベルのスイッチ回路です。スイッチの接続ではスリーステートと同様にポートリストの3番目にコントロール信号を記述します。動作もスリーステートとほぼ同様です。

ここではnmosトランジスタを記述しています。

コントロース信号EN=1のときゲートがONし、EN=0のときゲートがOFFします。ゲートがOFFのとき出力はハイインピーダンスです。

スイッチにはこのほかにpmos、cmosなどがあります。

プルアップ,プルダウン

X
Y
pullup U1( X );
pulldown U2( Y );
ゲート名 インスタンス名 (出力);

プルアップ、プルダウン

これはプルアップ抵抗やプルダウン抵抗を表現したものです。出力だけなので、ポートリストは1つだけです。プルアップでは出力は1に、プルダウンでは出力が0に固定されます。さらに信号強度がプルレベルになります。信号強度については次のページで紹介します。

(4) 信号強度
supply0supply1
strong0strong1
pull0pull1
large0large1
weak0weak1
medium0medium1
small0small1
highz0highz1
<ゲート名><信号強度><遅延><インスタンス名>(<信号名>,<信号名>,・・);
assign <信号強度><遅延><信号名>=<式>,<信号名>=<式>,・・ ;

信号値の1や0に対し、8段階の信号強度をもつことが出来ます。

表に示したようにsupply、strong、pullなどの強度があり、それぞれ信号値0のときの強度、1のときの強度を個別に設定できます。

信号強度は呼び出したプリミティブゲートの出力に設定したり、assign文による代入先に付加することが出来ます。信号強度を指定しない場合、strongの強度が付加されます。

複数の信号の出力同士が重なっているとき、信号強度が強い方の出力を優先します。

1
(strong)
0
(pull)
1
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が衝突して不定となります。

信号強度は論理合成できません。

付加しても論理合成ツールでエラーになるか無視されてしまいます。

(5) データタイプ

レジスタ型

レジスタ名 機能
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だけ用います。

(6) タイミング制御
# <定数式>
<定数式> の示す期間遅延 (遅延制御)
@ <イベント式>
<イベント式> のイベント発生まで遅延 (イベント制御)

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とともに利用した組み合わせ回路と順序回路の記述だけが論理合成できます。

(7) さまざまな遅延

(1) ネットに対する遅延

assign #100 SUM = A + B; // ゲート遅延
A
B
SUM
#100
wire [15:0] #20 dbus; // 伝搬遅延
BLKA
dbus
BLKB
#20

#を使った遅延制御は遅延を与える対象によって、記述の仕方が変わります。

まずネットに対する遅延です。

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 遅延

#( rise, fall )
#( rise, fall, turn-off )
#min:typ:max

信号の立ち上がり、立下りに対して別の遅延量を与えることが出来ます

#のあとの括弧内に二つの値をコンマで区切れば、それぞれ立ち上がり、立ち下りの遅延量になります。

3つの値を記述すると3つめはトライステイト出力などのハイインピーダンスに変化する時間となります。

さらにmin、typ、maxの三種の遅延を与えシミュレーションの実行時に選択することもできます。

(8) for と while
for ( <代入文1>; <式>; <代入文2> )
    <ステートメント>
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
ステートメント
代入文2

いずれもループをつくるための制御構造です。forに続いて括弧の中に代入文、式、さらにもう一つの代入文を記述します。

代入文1はfor文の実行時に一回だけ実行されます。

式はステートメントを実行するかどうかを判断する条件判別式です。真ならステートメントを実行し、偽なら何も実行せずにfor文を終了します。

代入文2はステートメントのあとに実行する代入文です。慣例的にfor文では代入文1でループ用の変数の初期化を行い、条件判別式でループ回数を判断し、代入文2ではループ変数のカウントアップを行います。

for文の利用例を二つ示します。どちらもループ変数にintegerのiを用いた256回のループを構成しています。

最初のループではレジスタ配列memの値をすべて0に初期化してます。

このループでは遅延を与えていませんので、シミュレーション時刻0のまま実行します。

二番目のループではレジスタ配列memの内容をdinに与えています。

このループでは遅延を与えていますので、1ステップごとにdinに入力を与えていることになります。

while ( <式> ) <ステートメント>
while文
ステートメント
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になれば、このループを抜け出します。

(9) force と release
force <代入文>
強制代入
release <信号名>
解除
wire ENABLE;

initial begin
    ...
    ...
    force ENABLE = 1;
    ...
    ...
    release ENABLE;
    ...
end

force文は強制的に値を代入できる公文です。ネット信号や回路の出力信号に値を代入することができます。実際の回路では実現できないような状態をつくりだすことで、検証を効果的に行える場合があります。

ここではwire宣言した信号ENABLEに対し、force文で1を代入しています。

initial文内ではネット信号への代入を記述すると文法エラーですが、force文を使えば花王です。

force文により強制代入した値をもとに戻すのが、release文です。release文を実行した時点で、元から駆動されていた信号に戻ります。

(10) begin〜end と fork〜join
begin 〜 end
・・ 順次処理ブロック
initial begin
end
記述順に実行
fork 〜 join
・・ 並列処理ブロック
initial fork
join
並列に実行
initial fork
    #0    A = 0; B = 1; C = 0;
    #50   A = 1;
    #200  ;
    #150  B = 0;
    #100  A = 0;
    #100  C = 1;
join
0 50 100 150 200
A
B
C

いくつかの文をひとつの文としてまとめる複合文としてbegin~endがありました。

begin~endは順次処理ブロックと呼ばれ、記述の順番に処理を行う複合文です。

これに対応して、fork~join があります。fork~joinは並列処理ブロックとよばれ、個々の記述が並列に処理されるブロックです。並列処理の具体例を紹介します。

ここではfork~joinの並列処理ブロックを1つ含んだinitial文があります。

fork~joinの中には遅延とともに代入文がいくつかあります。

begin~endで囲われた代入文ならば、この遅延は前の文を実行してからの相対的な遅延になりますが、fork~joinで囲われていますので、実行する時刻をあらわす絶対的な遅延になります。

したがってここで記述している時刻は時刻0からのシミュレーション時刻になります。

実行される順序は遅延量の少ない順になりますので、記述の順番通りにはなりません。

(10) begin~end と fork~join
begin ~ end  ・・  順次処理ブロック
initial begin
end
記述順に実行
fork ~ join  ・・  並列処理ブロック
initial fork
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
0
50
100
150
200
A
B
C

begin~end と fork~join

複数のステートメントをまとめてひとつの文とするのがbegin~endとfork~joinです。これをブロックステートメントと呼びます。

begin~endはステートメントを記述順に実行し、fork~joinは並列に実行します。

fork~joinの中では、それぞれの行の遅延時間はinitial文が起動した時刻0からの遅延となります。したがって、実行される順序は記述順ではなく時間の短い順になります。

(11) 階層アクセス

テストベンチ

CHIP(検証対象)
入力印加
BLK_A
A
BLK_B
B
C
出力観測
参照
内部信号の参照
インスタンス名.インスタンス名.・・信号名
assign SIG_A = CHIP.BLK_A.A;
assign SIG_B = CHIP.BLK_B.B;
assign SIG_C = CHIP.C;

テストベンチの記述の中で検証対象の内部信号を参照するにはどうしたらよいでしょうか。たとえば検証対象の回路、チップの内部信号A、B、Cをテストベンチの記述の中で参照するとします。

検証対象の入力出力信号は直接参照できますが、内部信号を参照するためには特別な記述を必要とします。

内部信号を参照するためには、最上位階層からみた経路でインスタンス名をドットで結んで経路を記述します。モジュール名ではなく、インスタンス名であることに注意してください。

ブロックAの信号を参照するにはCHIP.BLK_A.A

チップ下位層の信号Cを参照するにはCHIP.Cのように記述します。

(12) 修了判定1
設問
モジュールアイテムと
ステートメントの区別

次の記述のうち,文法的に
誤っている行はどれか

(完全解答)

module DFF_test;
reg CK, RES, D;
wire Q, QB;
DFF D1( CK, RES, D, Q, .QB(QB) );
initial begin
always @( posedge CK ) Q <= D;
while ( Q!=0 ) @( posedge CK );
end
if ( RES==1 ) Q = 0;
initial begin
CK = 0;
if ( Q==0 )
QB = 1;
else
QB = 0;
end
endmodule

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はワイヤ宣言した信号ですので、ここでは代入できません。

(13) 修了判定2
設問
プリミティブゲートと信号強度
出力の信号強度が異なるスリーステート・バッファ(bufif1)が以下のように接続されている。
各入力が以下の値のとき,出力はどうなるか。(0, 1, x, z で解答する)
0
0
(pull)
1
0
(supply)
1
1
(pull)
0
1
(supply)
1
1
(pull)
0
0
(supply)
0
1
(pull)
1
1
(supply)

bufif1は、コントロール信号が0の時、出力はハイインピーダンス(Z)となる。

supplyとpullの信号が衝突したとき、信号強度が強いsupply側の値となる。

上側のバッファだけONしている。

supplyとpullの信号が衝突したとき、信号強度が強いsupply側の値となる。

(14)修了判定3

設問
for文と while 文を使って
プログラミング

コメントに示した信号を発生するように,記述を完成させる。

テストベンチ
DUT
CK
RES
TRIG
Q
8
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
もふねこ

お疲れさま!テストベンチ特有の文法、少し難しかったかな?🐾
遅延制御やシステムタスクなど、シミュレーションのための強力な武器を手に入れたね!