always文、for文、genvar
always_ff文
これまで値を割り当てる際に”="を使用して代入していました。この代入をブロッキング代入と呼びます。ブロッキング代入は記述上時間の概念のない信号の伝播を記述したいときに使用します。したがって一般に組み合わせ回路を記述する際にassignと併せて使用します。
レジスタのようなクロックをタイミングとして代入したい場合はノンブロッキング代入”<="を用います。例えばクロック"clock"を代入タイミング(イベント)としてオブジェクトr_regにin_aの値を代入する場合、
always_ff @( posedge clock ) begin
r_reg <= in_a;
end
として記述します。
この例ではオブジェクトclockの立ち上がり(posedge)をイベントとしてオブジェクトin_aの値をr_regへ代入しています。always_ffは論理合成時にフリップフロップとして扱われることを指示しており@()
の中にイベントの対象となるオブジェクト名を記述して明示します。オブジェクト名は複数記述することができ、","で区切り、リストにします。また、リストにワイルドカード”*"を使用することができ、この場合は参照されるオブジェクトの値が変化した時をイベントとすることになります。信号の立ち下がりを指定する場合は”negedge"を使用します。さらに立ち上がりや立ち下がりといった信号のエッジではなく信号の入力自体をイベント、つまりレベルをイベントとしたい場合はオブジェクト名を記述するだけです。
また、ブロッキング代入を使用して
always_ff @( posedge clock ) begin
r_reg0 = in_a;
r_reg1 = r_reg0;
end
の様に記述すると上の行から下の行へ代入が行われていることを暗黙の前提にしているので注意してください。従ってクロックをイベントとしてreg_1にはin_aの値が代入され、その経路はin_a→r_reg0→r_reg1となります。これはSystemVerilogも含めたHDLは単にハードウェア記述言語であり、リスト(センシティビティリストと呼びます)内で指定されたイベントを基に例えばシミュレーション上でそのリストに従ってイベントを発生させて代入させることに起因しています。論理合成はHDL記述を解釈しRTLコードを生成させる別の系です。
配線や組合せ回路上、閉路の記述はSystemVerilogではサポートされていませんし、論理合成時にワーニング或いはエラーとなります。例えばシミュレーション時に一度このループの処理が始まるとイベントが指定されていないため無限にこの閉路についてシミュレーションし続けます。シミュレーションツールはこれを避けるためコンパイル時にオブジェクトにカウンタを設け、カウンタが閾値を超えた時にエラーとして停止するようになっています(停止できない時もあります)。この閉路、特に組合せ回路上に内在する閉路を組合せループと呼び、論理合成時にこのループを解決する必要があります。大抵の場合、ループは設計上の誤りであり、タイミングについての検討が甘い時或いは回路自体の振る舞いレベルでの設計が誤りの時に発生しやすいです。例えばクロックに同期するオブジェクト(例えばレジスタ)を途中に挟むようにタイミングを調整すれば解決できます。
always_comb文
組合せ回路を効率よく記述したい場合、always_combを利用できます。例えば同じ組合せ回路を複数記述するのは冗長的であり、個数の増減に伴い他のオブジェクトも含めて修正・更新する必要が発生して煩雑になります。always_combとfor文を組み合わせることで簡易に記述できるようになります。
例えば単ビット演算の
/* Sum */
assign d = a ^ b ^ ci;
/* Carry-Out */
assign co = ( a & b & ~ci ) | ( ~a & b & ci ) | ( a & ~b & ci );
について
localparam WIDTH = 8;
logic [WIDTH-1:0] d, a, b, ci, co;
for (int i=0; i<WIDTH; ++i) begin
/* Sum */
assign d[i] = a[i] ^ b[i] ^ ci[i];
/* Carry-Out */
assign co[i] = ( a[i] & b[i] & ~ci[i] ) | ( ~a[i] & b[i] & ci[i] ) | ( a[i] & ~b[i] & ci[i] );
if ( i > 0 ) begin
assign ci[i] = co[i-1];
end
else begin
assign ci[i] = 1'b0;
end
end
のように特定のパターンを持つ全加算回路を組み合わせ回路記述(always_comb)を使用して記述することができます。always_comb内ではassignを使用して割り当てるので三項演算子も使用できます。
genvar
また、あらかじめ単ビットの全加算回路モジュールがあるのであればそれを再利用したいでしょう。この場合はgenerateという機能を使用して冗長記述できます。
例えば、
localparam WIDTH = 8;
logic [WIDTH-1:0] w_d, w_a, w_b, w_c;
for (genvar i=1; i<WIDTH; ++i) begin
if ( i == 0 ) begin
object_name full_adder (
.d( w_d[0] ),
.a( w_a[0] ),
.ci( 1'b0 ),
.b( w_b[0] ),
.co( w_c[0] )
);
end
else begin
object_name full_adder (
.c( w_d[i] ),
.a( w_a[i] ),
.ci( w_c[i-1] ),
.b( w_b[i] ),
.co( w_c[i] )。
);
end
end
のようにfor文でgenvarで定義した整数iを使用して代入を行い記述できます。genvarはこれで宣言している変数について暗黙に整数型を使用します。この記述はあくまで代入に使用できるものなのでassign文のような組合せ回路記述はできません。例えば三項演算子は使用できません(少なくともVivadoではエラーになります)。この例の場合、論理合成やシミュレーションにおいてオブジェクト名”full_adder"に固有のIDが付与され各モジュールを識別できるようになります。例えばVivadoではPostfixの番号が付与されます。