2つの演算子
alwaysとinitialブロック内で、Verilogには2つの代入演算子があります:
=は blocking 代入。次の文に移る前に、左辺を今更新する。<=は 非blocking 代入。右辺を今評価し、左辺を 現在のtime stepの終わり に更新するようスケジュールする。
手続きブロックの外側(assign内)では=だけが存在します。assign a <= bは構文エラーです。手続きブロックの内側では両方が合法で、正しい方を選ぶことが初心者Verilogで最も重要な決定です。
blockingがすること
blockingはソフトウェア言語から期待するもの:1行ずつ、上から下へ。文Nは文N+1が始まる前に完了します:
これは純粋に順次です。各文は上の文の効果を見ます。ソフトウェアの直感と一致します。
非blockingがすること
非blockingはハードウェア形状です。すべての右辺はtime stepの 開始時の値 を使って評価されます。すべての左辺は 終了時 に更新されます。ソース内の文の順序はsignal間の依存関係を変えません:
このスニペットはa → b → c → aの3方向ローテーションを1ステップで実装します。blockingでは、どれかを壊さないようにテンポラリ変数が必要です。非blockingでは、すべての右辺が ステップ前 の値を読むので、スワップがアトミックに起こります。
これがまさに3つのflip-flopがクロックエッジで振る舞う方法です。それぞれの依存関係に関わらず、すべてが同じ瞬間に入力を捕捉します。
あなたを救うルール
クロック同期ブロックでは<=を使う。組み合わせブロックでは=を使う。
それだけです。暗記してください。考えずに書いてください。ほとんどの競合状態、シミュレーション/合成の不一致、「自分の環境では動くが合成では動かない」バグはこのルール違反から来ます。
ルールにはハードウェア上の理由があります。クロック同期ブロックは入力を同時にサンプルするflip-flopをモデルし、組み合わせブロックはできる限り速く伝播するロジックをモデルします。代入演算子の意味論はこれらの振る舞いに一致します。
各<=はソースregisterの 現在の 値を読み、time stepの終わりにその値を取るよう宛先registerをスケジュールします。正味の効果はハードウェアshift registerが行うことそのものです。すべてのflip-flopが同じクロックエッジで隣の値を捕捉し、競合なしです。
ここでblocking代入を使うとどうなるか考えてみましょう:
// 間違い - これはshift registerではない!
always @(posedge clk) begin
out[3] = out[2]; // out[3]はout[2]になる
out[2] = out[1]; // out[2]はout[1]になる、上で設定したばかり
out[1] = out[0];
out[0] = in;
end
各文が次の文が読む前にソースを上書きしてしまいます。1クロックサイクルで、inがout[3]まで全部伝播してしまいます。各行が上の行の書き立ての値を見るからです。実ハードウェア(非blocking意味論を使う)での振る舞いは、シミュレータが表示したものとまったく異なるでしょう。
組み合わせブロック:blockingが正しい
always @(*)ではblocking代入が正しいです。flip-flopなし、同時捕捉ルールなし、中間変数が有用:
sumが最初に計算され、resultが計算したての値を使います。組み合わせロジックは1つのハードウェアにフラット化されます:result = ~(a + b)。クロックがないのでflip-flopは現れません。
ここで<=を使っても、シミュレータは依然としてresultがそれに対して評価される前にsumを更新します(両方の更新がstepの終わりに起こるから)が、順序は微妙に異なり、多くの合成ツールが文句を言います。混在させない。ブロック型に一致する演算子を選んでください。
最も痛い間違い
これです:blocking代入のクロック同期ブロック。
// バグ: 起こるのを待っている競合状態
always @(posedge clk) begin
a = b;
b = c;
c = a;
end
シミュレーションでは、シミュレータは最初にa、次にb、次にcを与えるかもしれず、ある値の組を生成します。ハードウェアは別の組を生成します。実際のflip-flopは同時に捕捉するからです。2つは静かに分岐し、バグを見つけるのに1日無駄にすることになります。クロック同期ブロックには<=を使ってください。
なぜ2つの演算子が存在するか
Verilogの設計者は1つの代入意味論を選んで貫けたかもしれません。そうしなかったのは、言語が2つの異なるハードウェア振る舞いをモデル化する必要があるからです:
- 組み合わせロジック:signalが連続的に伝播し、依存関係が重要で、「このgateは何を計算するか」が意味をなす。
- 順序ロジック:クロックエッジが起き、すべてのflip-flopが同時に捕捉し、flip-flop入力と出力の依存関係が分離される。
blockingは前者用。非blockingは後者用。演算子が意味論を選び、シミュレータが残りを行います。
次に読むもの
これで任意の手続きブロックを正しく書くルールが揃いました。次の章は個々のブロックから一歩進んで、その中に入る制御フロー構造、if/else、case、forループに進みます。blocking vs 非blockingのルールは、それらすべての内側でも引き続き適用されます。
よくある質問
Verilogのblockingと非blocking代入の違いは?
blocking(=)はターゲットを即座に、次の文が走る前に更新します。順次実行をモデルします。非blocking(<=)は更新を現在のtime stepの終わりにスケジュールします。すべての非blockingの右辺はすべてのsignalの古い値で評価され、その後すべての左辺が1つの調整されたステップで更新されます。非blockingは実際のflip-flopの振る舞いと一致します。
Verilogでいつ=と<=を使うべき?
ルール:クロック同期always @(posedge clk)ブロックでは<=、組み合わせalways @(*)ブロックでは=を使ってください。この1つのルールが、混在する代入が生むあらゆる競合状態を防ぎます。testbenchのinitialブロック内では=が通常の選択で、<=はそこではまれです。
Verilogで非blocking代入が必要な理由は?
ハードウェアのflip-flopはすべてクロックエッジで同時に入力を捕捉するからです。クロック同期コードで=(blocking)を使うと、ファイル内の文の順序が、どのsignalが他のsignalの新しい値を見るかを変えてしまい、シミュレーションと実ハードウェアの間で競合状態が生じます。<=はすべての右辺をまず評価し、その後すべての更新を行うことで、ハードウェアの振る舞いに一致させます。
同じVerilog always block内で=と<=を混在させると何が起きる?
競合状態が発生します。混在は、その振る舞いがシミュレータの内部(イベントスケジュール順序)に依存するハードウェアを生み、さらに悪いことに、シミュレーションが合成結果と一致しないかもしれません。ほとんどのlintツールはこれをエラーとしてフラグします。修正はブロックの役割に基づいてどちらか1つにコミットすることです。クロック同期には非blocking、組み合わせにはblocking。