振る舞いVerilogの主役
assignは単一式の組み合わせロジックを記述します。if/else、case、メモリが必要になると、alwaysに手を伸ばします。alwaysブロックは特定のsignalが変わるたびに再実行される手続きコードです。再実行のトリガとなるsignalが 感応リスト です。
最もよく見る2つのalwaysの形:
always @(*)- ブロック内で読まれる 任意の signalが変わるたびに再実行。組み合わせロジックを構築。always @(posedge clk)-clkの立ち上がりエッジのみで再実行。クロック同期の順序ロジック(flip-flop)を構築。
他の形(@(a or b)、@(negedge clk)、@(posedge clk or negedge reset_n))もありますが、上の2つが書くことになる合成可能ブロックのほとんどを占めます。
組み合わせalways @(*)
3つの注目点:
outはreg。always内で代入されるものはすべてregでなければなりません。このキーワードは「これはflip-flop」を意味するわけではなく、ここでは単に「手続きブロックから書き込まれる」を意味するだけです。always @(*)。*は「読まれるものが変わったら起きろ」と言います。シミュレータが感応リストを自動的に把握します。リストを手書きできます(always @(sel))が、@(*)の方が安全です。signalを見落とすことが古典的なバグ源だからです。- クロックなし。 このブロックは組み合わせロジックを記述します。合成器は
selから直接outを計算するロジックを生成します。flip-flopもクロックピンも不要です。
defaultケースは構文上はオプションですが、意図上はオプションではありません。省略すると、未指定の入力値でoutが前の値を保持することになり、意図しない ラッチ が合成されます。常にdefaultを入れてください。
順序always @(posedge clk)
組み合わせ版との重要な違い:
always @(posedge clk)。 ブロックはclkの立ち上がりエッジでのみ再実行します。エッジの間は何も起きません。- 非blocking代入
<=。 クロック同期ブロック内では、これが正しい演算子です。「countをtime stepの終わりに新しい値にスケジュールする」という意味で、flip-flopの振る舞いそのものです。理由と代替はBlocking vs Non-blockingで扱います。 defaultは不要。ifは両方の分岐(resetとnot-reset)をカバーします。ラッチのリスクなし。
合成器はこの形(クロック同期、非blocking代入)を見て、4ビットregister(4個のflip-flop)に加えて、count + 1を計算する組み合わせロジックと、resetかインクリメントかを選ぶmuxを生成します。
合成上の違い
同じmoduleソースが、alwaysブロックの形によってまったく異なる2種類のハードウェアを記述できます:
| ブロック | ハードウェア |
|---|---|
always @(*) y = expr; | 純粋な組み合わせロジック。メモリなし。 |
always @(posedge clk) y <= expr; | flip-flop。クロックサイクル毎にexprを捕捉。 |
always @(*) if (en) y = expr; | ラッチ - 通常はバグ。「else」のケースで古い値を保つ。 |
always @(posedge clk) if (en) y <= expr; | イネーブル付きflip-flop。enが高のときだけ捕捉。 |
3つ目がラッチの罠です。ラッチは入力がアサートされていないときに出力を保持する透過型のメモリセルで、特定の設計では有用ですが、偶然作ると大抵バグです。ほとんどの合成ツールは、要求していないラッチを推論したときに大きく警告します。警告はエラーとして扱ってください。
感応リストのバリエーション
あまり一般的でない感応リストもいくつか目にします:
always @(a or b or c)- 明示的なリスト。Verilog-2001は,セパレータを追加:always @(a, b, c)。どちらも動きます。always @(posedge clk or negedge reset_n)- 非同期reset。ブロックはクロック立ち上がりエッジ または resetの立ち下がりエッジで動きます。resetを即時に効かせる必要があるとき、次のクロックを待たないために使います。always @(negedge clk)- 立ち下がりエッジクロック。稀。立ち上がりではなく立ち下がりで捕捉する「立ち下がりトリガ」flip-flop用に一部の設計が使います。
新しい設計では、組み合わせにはalways @(*)、順序にはalways @(posedge clk)を優先してください。非同期resetは、設計が本当に必要とするときだけ使います。
2つのブロックは2つのハードウェア
同じmodule内の複数のalwaysブロックは独立で、各々が自分のハードウェアになります:
クロック同期ブロックはflip-flopレジスタを生成します。組み合わせブロックはXORゲートを生成します。横に並んで暮らし、互いを知りません。2つの出力はまったく異なるスケジュールで変化します。
alwaysブロックができないこと
魅力的に見えるが許されないこと:
wireへの代入:ターゲットはregでなければなりません。コンパイラが強制します。- 2つの別の
alwaysブロックから同じregへの代入:シミュレーションで未定義動作になり、合成もされません。1つのsignalに1つのドライバ。 - 同じ組み合わせブロック内でフィードバックループを作る形で同じsignalを読み書き:
always @(*) x = x + 1;はシミュレータが解決できない遅延0ループです。
最初の2つはコンパイラがキャッチします。3つ目はシミュレーション時にハングとしてだけ現れることがあります。
次に読むもの
次のドキュメントInitial Blockでは、alwaysの兄弟、シミュレーション開始時に1度だけ実行されるブロックを扱います。testbenchの主役です。その後、クロック同期ブロックが意図通りに動くかどうかを決めるblocking vs 非blocking代入のルールを扱います。
よくある質問
Verilogのalways blockとは?
alwaysは感応リスト内のsignalが変わるたびに再実行される手続きブロックを導入します。2つのフレーバーがあります:always @(*)は組み合わせロジックを構築し(読まれた入力が変わるたびに再実行)、always @(posedge clk)は順序ロジックを構築します(clkの立ち上がりエッジごとに再実行)。alwaysブロックのbodyはif、case、for、手続き的代入を含めます。
always @(*)とalways @(posedge clk)の違いは?
always @(*)はブロック内で読まれる任意のsignalに感応し、メモリなしの組み合わせロジックを生成します。always @(posedge clk)はclkの立ち上がりエッジのみに感応し、クロックサイクル毎に1回stateを捕捉するflip-flopを生成します。前者はクロックもregisterもなく、後者は両方を持ちます。
Verilogの感応リストとは?
@の後に置く、alwaysブロックがいつ再実行されるかを決めるsignalのリストです。@(*)は「ブロック内で読まれるすべてのsignal」の略です。@(posedge clk)はclkの立ち上がりエッジのみで実行されます。@(posedge clk or negedge reset_n)はどちらかのイベントで実行され、非同期resetに使います。感応リストを間違えるのは、シミュレーション/合成の不一致の最も一般的な原因の1つです。
always block内でwireに代入できますか?
いいえ。alwaysブロックはreg(SystemVerilogではlogic)にしか代入できません。コンパイラがこれを強制します。手続きロジックの出力としてwireを使いたい場合は、中間regを宣言し、always内で駆動し、外側でassignでwireをそれから駆動するか、wireをregに変えてください。