6.代入文
代入文は、次の3種類があります。
手続き代入文(always,initialで使われる代入文)
ブロッキング 代入文(Blocking Assignment) =>以下BAと略
ノンブロッキング代入文(Nonblocking Assignment) =>以下NBAと略
継続的代入文
Continous Assignment
継続代入文の左辺は、下のように制限があります。
LHS | |||
継続代入文 | a[3:0] | OK | aはNET |
a[i] | NG | aはNET | |
a[i +:4] | NG | aはNET | |
a[i -:4] | NG | aはNET | |
a | OK | aはNET | |
a[i:j] | NG | aはNET | |
手続き代入文 | b[3:0] | OK | bは,outputvariable type(reg/integer/time) |
b[i:k] | NG | bは,outputvariable type(reg/integer/time) | |
b[i] | OK | bは,outputvariable type(reg/integer/time) | |
b[i +:4] | OK | bは,outputvariable type(reg/integer/time) | |
b[i -:4] | OK | bは,outputvariable type(reg/integer/time) | |
b[j][i +:4] | OK | bは,outputvariable type(reg/integer/time)array | |
b[j][i -:4] | OK | bは,outputvariable type(reg/integer/time)array | |
b[i][j] | OK | bは,outputvariable type(reg/integer/time)array | |
b[i][j] | OK | bは、variable type array | |
b | OK | bは、varaible type |
6.1手続き代入文
BAは、次の2種類があります。
<blocking_assignment>
::= <lvalue> = <expression>
||= <lvalue> = <delay_or_event_control> <expression> ;
一方NBAも全く同じ記述形式です。
<non_blocking_assignment>
::= <lvalue> <= <expression>
||= <lvalue> = <delay_or_event_control> <expression> ;
ところが、意味が全然ちがいます。まずは、BAからです。
1) stage1 =stage2;
2) stage1 =#10 stage2;
3) stage1 =@(a) stage2;
ところで、2)3)は、次と等価です。
2) temp=stage2;
#10 stage1 =temp; //=> 2)`
ところで、2)`は次と等価です。
#10; //10単位時間待って
stage1 = temp;//代入を実施する。
3) temp=stage2;
@(a);//aが変化したら
stage1 = temp;//代入を実施する。
仮に
stage2= stage3;
stage3= stage4;
が後に続いているとするとこれらは、Cと同じく順番に代入動作が行われます。
つまり、シフトレジスタ的な動作をさせようとすると順番を意識して書かなければいけません。
NBAでは、それぞれ
4) stage1 <=stage2;
5) stage1 <=#10 stage2;
6) stage2 <=@(a) stage3;
と書けます。
4)は、RHS(右辺)の値を求めますが、すぐには、LHS(左辺)には代入されません。一旦RHSを求めた値は、イベントキュ-(NBA専用)に入ります。この後にRHSが変わったとしてもNBAキューで保持している値は不変です。イベントキNBAキュ-は、同時刻イベントでも最後に
配置されますので、BAより先に代入がおこることはありません。
5)は、RHSの値を求めた後、#10時間後にNBAされます。
6)は、RHSの値を求めた後、@(a)イベント後にNBAされます。
この動作により記述の順番依存性がなくなります。
stage1 <= stage2;
stage2 <= stage3;
stage3 <= stage4;
は、
stage3 <= stage4;
stage2 <= stage3;
stage1 <= stage2;
と書いても同じシフトレジスタ動作になります。BAでは、まったく違う動作になってしまうのとは対象的です。
実用上、FFの記述は、全てNBAを使います。組み合わせ回路をAlwaysで記述するときは、BAを使うと覚えてください。
この辺の動作を肌で感じるのは、Veritakで、Step動作させてEditor上で値を表示させてみると
わかります。BAは、代入を実施するとすぐにLHS=RHSとなりますが、NBAは、代入を実施してもLHSは元のLHS値のままです。時刻が変わった後に初めてRHS値が変わったことがわかるでしょう。(実際には、時刻が変わる直前に代入が行われる。)
なお、いい忘れましたが、手続き代入文のLHSは、reg型でないといけません。reg型は、reg、real,realtime,integer,timeのどれかで宣言されたtypeです。
6.2継続代入文
<continuous_assign>
::= assign <drive_strength>? <delay>? <list_of_assignments> ;
||= <NETTYPE> <drive_strength>? <expandrange>? <delay>? <list_of_assignments> ;
綺羅星のごとくoptionが並んでいますが、実際によく使う記述は、次のような記述です。継続代入文では、簡単な組み合わせ回路の記述に使います。こちらのほうは<=はな=しかありません。
wire w;
wire [1:0] w1;
reg [3:0] r1;
assign w=a &b;
assign w1=r1[3:2];
なお、継続代入文のLHSは、Net宣言されていないといけません。また、reg型は、左辺に置けません。手続き型との違いは、regの場合は、最後に代入されている値を記憶するのに対し、Net型は、継続的にドライブされている必要があります。つまり組み合わせ回路しか記述できません。物理的には、”線”の意味に捉えてよいかと思います。たとえば、w1に実態はありません。あるのは、r1[3:2]への参照です。HW上存在しませんし、シミュレータ上でもメモリを占有していません。a&bという右辺は、アンド回路ロジックという実態を持ちますがLHSはその参照でしかありません。従って特殊な文を除いてNetの値を明示的に設定することはできません。
継続的代入文のRHSのすべての信号は、その変化をモニタしています。変化したらすぐに再計算してLHSに値を設定するためです。
おそらく、大部分のVerilogシミュレータの内部インプリメンテーションでは、Netと手続き構文と二つの違うエンジンを持っています。スレッド駆動型エンジン(C言語型)と、NET駆動型テーブルルックアップ方式(従来の回路シミュレータ)による方式です。Veritakもそうなっています。
筆者がVerilogを覚えたの2年間、納得できない現象がありました。
次のソースを見てください。最初の$displayで、q=1は文句ないところですが、pは0でしょうかそれとも1でしょうか?答えは、全部のVerilogシミュレータで、xになるはずです。
なぜ、q=1になったところで、インバート動作で、0にならないのでしょうか?
また、#0の後、どうして0になるのでしょうか?
その答えは、同時刻のイベントのキューイングの仕方にあります。前の章で述べた通り、q=1となってもスレッドの実行は継続しています。#0になって初めて、制御が他のイベントの伝播に振り分けられます。つまり伝播イベントより$displayの方が早かったためにこのような現象になります。従って継続代入文のモニタをするときは、注意が必要です。
ところで、最後の$strobeではどうなるでしょうか?答えは、1になる筈です。というのは、q<=0が効いて、同時刻の終わりでq=0となり、そのイベントが伝わってp=1になります。$strobeは、
NBAよりも後のもう同時刻のイベントがなくなって次の時刻に行こうとする直前の最終値をモニタするのがVerilogの仕様ですので同時刻の最終値であるp=1が得られたという訳です。
同時刻のキューイングはこのように複雑ですので、別項でもう一度整理してみたいと思います。
//Mar.20.2004 //Demo for verilog race condition module sample5; reg q; wire p; assign p = ~q; initial begin q = 1; q <=0; $display ("p=%b q=%b",p,q);// #0 $display ("p=%b q=%b",p,q);//p should be 0,however some simulator fails. $strobe ("p=%b q=%b",p,q);//Absolutely p should be 1 end endmodule |
6.1 Transport とInertial Delay
Verilogでは、様々な、Delayの指定形態がありますので、ここでまとめて見てみたいと思います。
下記はサンプルソースからです。
alwaysの中から見ていきましょう。
上でみたとおり、
#10 ba1 =a+b; は、
#10;
ba1=a+b;
に分解されます。(Always/Initial中、左側にDelayがあるのは、全部、この形に分解できます。)
#10単位時間経つまでba1=a+b;は、実行されません。従って、Alwaysの頭に戻るのは、#10単位時間たった後です。その間に、a,b信号に変化があったとしても、@待ちにはなっていないので、検出されません。
また、 ba2 =#10 a+b;は、
temp=a+b;
#10;
ba2=temp;
に分解されます。always @(a,b)で、変化があったら、その内容をtempに一旦Saveされますが、#10単位時間待てになりますので、次の文には#10単位時間進みません。従って、この間@(a,b)のところに進めないので、入力変化を検出することができないのは、上と同じです。
一方、 NBA構文、 nb1 <=#10 a+b;は、a+bを即評価します。評価した値をa+b=>cとでもしておきましょう。cの値は、#10単位時間後に、
nb1<=c;
とするイベントをイベントキューに貯めこみます。つまり、LHSの評価は、即行われますが、代入予約だけが、現在時間に行われ、次の文に休むことなく進みます。次の文は、@(a,b)なので、ここでまた、a,bの変化を検出するまで待て、になり、別のスレッドの実行に移ります。従って、a,bの検出変化出来ない期間は、なかったことに注意してください。上の二つが#10単位時間、変化を検出できないのとは対称的です。このことは、a,bの全ての変化は、#10単位時間遅れることを意味します。つまり、単なるDelay Lineと同じ動作となります。このような伝播遅延、Transport Delayと呼んでいます。
Transport Delayに対し、慣性遅延(Inertial Delay)があります。(VHDLでは、DefaultがInertial DelayでTransport Delayは、オプションです。)慣性遅延は、ロジックででるハザードなどを無視させるのに実際的に用いられます。これを、Verilogでは、Wire構文で実現できます。たとえば、
#10 cw=a+b;
です。 a,b が変化してから#10単位時間後にcwにその結果が現れることを意味しています。言葉ですと、なかなかイメージがつかみにくいので、実際にシミュレータで、様子を確認してみるのがよいでしょう。
なお、wire宣言の頭に、
wire [3:0] #5 cw2;
とありますが、これは、#5 NET Delayと呼ばれておりwire assign のDelayとは別物です。(wire
のinline 宣言はNET Delayではなく wire assign のDelayになります。 NET Delayはトランスポートディレイ、wire のassign はイナーシャディレイになります。そのほかにゲートのディレイ構文もあります。)
様々なDelay構文のまとめ | ||||
例 | モード | |||
手続き構文 | #10 a=b; |
- | 次の文に等価 つまりa=b;の実行が10単位時間ブロックされる #10; a=b; |
|
#10 a<=b; | - | #10;//10単位時間ブロックされる a<=b; におなじ |
||
a<=#10 b; a<=#20 c; |
トランスポート | ブロックされずに実行順にキューイングされる。 temp_b=b;//キューイング temp_c=c;//キューイング ここまでは、0時間経過 #10単位時間後に、a<=temp_b; #20単位時間後に a<=temp_c; の動作となる。つまりトランスポートDelayになる |
||
NET構文 | assign #10 a=b; | イナーシャ | ||
wire #10 a=b; | イナーシャ | 上と等価な表現 | ||
wire #10 a; | トランスポート | NET Delay(wire Delayを模擬する) | ||
#10 buf (a,b); | イナーシャ | ゲートDelay |
wire [1:0] #6 aw; wire [1:0] #7 bw;
assign #8 {aw,bw}=a+b;
では結果として、aw=>#(8+6) 時間、bw=>#(8+7)時間の遅延時間になります。
//Mar.26.2004 module various_delay; reg [3:0] a,b; reg [3:0] ba1,ba2; reg [3:0] nb1; wire [3:0] cw,cw_no_delay; wire [3:0] #5 cw2; wire [1:0] #6 aw; wire [1:0] #7 bw; assign #10 cw=a+b; assign cw_no_delay=a+b; assign cw2=a+b; assign #8 {aw,bw}=a+b; always @(a or b) begin #10 ba1 =a+b; end always @(a or b) begin ba2 =#10 a+b; end always @(a or b) begin nb1 <=#10 a+b; end initial begin $dumpfile ("delay_vcd.vcd"); $dumpvars;//dump variables in the design $monitor("aw=%b bw=%b cw2=%b time=%d",aw,bw,cw2,$time); a=0; b=0; #30 a=4'hc; #30 b=4'h2; #5 a=4'h5; #4 b=4'h3; #30 a=4'h0; b=0; #15 a=4'hc; #15 a=1; #100 ; $dumpflush; end endmodule |
イベントの始まりはなんでしょうか?手続き構文代入文がトリガとなります。
reg a=1;
reg b=1;
wire c=a & b;
wire d=a | b;
wire e=a ^b;
という文があった場合、aや、bの変化x=>1は、 wire 構文の再計算をTriggerします。これは、VerilogHDLでは、illegal
な文になりますが、意味的には、
always @* begin
c=a & b;//illegal
d=a | b;//illegal
e=a ^b; //illegal
end
と同じことです。 これは、別プロセスと考えることもできます。イベント制御文や、時間制御文に出会わないと、そのプロセス(スレッド)は継続されました。従って、$dispalyが、wireの値を即反映しなかったことも説明ができます。
シミュレータの視点からも、動作的にもその方が高速です。Veritakの初期Versionは、変化を受けて、即再計算していました。しかし、a=1,b=1..というように、変化があったぞ!と、Triggerをかけておいて、別プロセスで、変化が落ち着いた後、まとめて再計算をしてほうが高速なことは、明らかです。(特に算術演算系)従って、一部のシミュレータを除いて、後者のアルゴリズムになっています。
これらのイベントは、全て、下図の一番上のActiveイベントになります。Activeイベント内の実行順序は、実装依存になっています。つまり、「wire計算即行う/後で行う」のどちらでもよいことになります。すべてのイベントが尽きると、下のInactive Eventつまり#0に行きます。従って、#0は、Active Eventの最後に行われます。しかし、そこで、Eventが発生すると、またActive Eventに戻ることに注意してください。Sutherland氏の記述指針を見ると#0の記述は、お勧めしていません。(とりあえず、スレッドの順番をSKIPする意味はあるのですが、状況を複雑にするだけなので、意味がないということなのでしょう。)
大事なことは、<=は、その後に実行されることです。つまり、すべてのActiveイベントが尽きた後に予約された、(スケジュールされた)<=が実行されます。全ての予約イベントが実行された後、、またActiveイベントが生まれれば、また、一番上から実行されます。Active イベントがなければ、さらに下に行き、$monitor、や、$strobeが実行されます。このとき、もう上に戻る事はありません。つまり、同時刻の最終確定値が得られる訳です。VeritakのWaveformViewは、この最終値を表示しています。