【Verilog入門】always文を完全解説|構文・使い分け・代入方法・SystemVerilog対応まで網羅!

目次

1. はじめに

Verilogにおけるalways文の役割とは?

デジタル回路の設計で広く使われているハードウェア記述言語「Verilog HDL」では、always文が非常に重要な役割を果たします。Verilogでは、ハードウェアの動作をソフトウェアのように記述するのではなく、「どのような条件下で信号がどのように変化するか」を定義する形で回路を表現します。その中で、always文は一定の条件が発生したときに特定の動作を行うという処理を記述するための基本的な構文です。

always文はなぜ必要なのか?

Verilogには、主に2種類の回路の動作を記述する方法があります。

  • 組み合わせ回路:入力が変化すると即座に出力も変化する回路
  • 順序回路:クロック信号などのタイミングに合わせて出力が変化する回路

これらを記述する際、単なるassign文だけでは複雑な条件分岐や状態の記憶を記述することができません。ここでalways文が登場します。

たとえば、複数の条件分岐がある論理や、フリップフロップを用いた記憶動作を記述するには、always文を使って制御構造(if文やcase文)を記述する必要があります。

よく使われるalways文のパターン

always文にはいくつかの代表的な使い方があり、それぞれ設計したい回路の種類によって使い分けられます。

  • always @(*)
     → 組み合わせ回路の記述に使われる
  • always @(posedge clk)
     → クロックの立ち上がりに同期する順序回路の記述
  • always @(posedge clk or negedge rst)
     → 非同期リセット付きの順序回路など、より複雑な制御構造

このように、Verilogの中核をなす構文であるalwaysを理解することは、ハードウェア設計者としての第一歩と言っても過言ではありません。

本記事の目的

この記事では、Verilogにおけるalways文について、構文の基礎から応用的な使い方、注意すべき落とし穴、SystemVerilogにおける拡張までを幅広く解説していきます。

  • always文の正しい書き方が知りたい
  • 論理合成でエラーになる原因がわからない
  • =<=の使い分けに困っている
  • よくある初学者のミスを防ぎたい

このような疑問や悩みを持つ方にとって、実用的で理解しやすいガイドとなることを目指します。

2. always文の基本構文と種類

always文の基本構文

Verilogのalways文は、特定の条件(感知リスト)に基づいて処理を繰り返し実行するための記述方式です。基本的な構文は以下の通りです。

always @(感知リスト)
begin
  // 実行する処理
end

この構文で重要なのは、「感知リスト」と呼ばれる部分です。これは、「どの信号が変化したときにこのブロックを実行するか」を定義する場所です。

always @(*) の使い方(組み合わせ回路)

組み合わせ回路では、入力が変わるたびに出力も即座に変わる必要があります。このような場合には、感知リストとして @(*) を使用します。

always @(*) begin
  if (a == 1'b1)
    y = b;
  else
    y = c;
end

このように記述することで、a, b, c のいずれかが変化すると always ブロックが実行され、出力 y が再計算されます。

@(*) を使うメリット

  • 全ての入力信号を自動で感知リストに含めてくれる
  • 感知リストの書き忘れによる論理シミュレーションと合成結果の不一致を防げる

always @(posedge clk) の使い方(順序回路)

順序回路では、クロック信号に同期して状態が変化します。このときは、感知リストに posedge clk を指定します。

always @(posedge clk) begin
  q <= d;
end

この場合、クロックの立ち上がり(posedge)に合わせて、d の値が q にラッチされます。<= はノンブロッキング代入で、順序回路では一般的にこの形式が使われます。

posedgenegedge

  • posedge:立ち上がりエッジで動作
  • negedge:立ち下がりエッジで動作

用途に応じて適切なエッジを選びましょう。

always @(posedge clk or negedge rst)(非同期リセット付き)

複雑な回路では、リセット機能が必要なことがよくあります。非同期リセット付きの記述は以下のようになります。

always @(posedge clk or negedge rst) begin
  if (!rst)
    q <= 1'b0;
  else
    q <= d;
end

このように記述すると、リセット信号が「0」になると即座に q がリセットされ、それ以外のときはクロックに同期して d を保持します。

組み合わせ回路と順序回路の使い分け

回路の種類使用するalways特徴
組み合わせ回路always @(*)入力に応じて即座に出力が変化
順序回路always @(posedge clk)クロックに同期して動作する

3. always文における代入の種類

Verilogには2種類の代入方法がある

Verilogのalways文内では、2つの異なる代入演算子が使われます。

  • =:ブロッキング代入(blocking assignment)
  • <=:ノンブロッキング代入(non-blocking assignment)

この違いを理解しないままコーディングを進めると、意図しない動作シミュレーション結果と合成結果の不一致につながるため、非常に重要なポイントです。

ブロッキング代入(=)とは?

ブロッキング代入は、1つのステートメントが完了してから次のステートメントに進むという「順番に処理される」代入方式です。ソフトウェア的な制御に近いイメージです。

always @(*) begin
  a = b;
  c = a;
end

この場合、a = b が先に実行され、その結果を使って c = a が実行されます。変数の代入順がロジックに直接影響するため、順番に気をつける必要があります。

主な用途

  • 組み合わせ回路での制御構造(if, case)内
  • 状態を保持しない処理

ノンブロッキング代入(<=)とは?

ノンブロッキング代入は、すべてのステートメントが同時に評価され、同時に反映されるという「並行的な動作」を表現する代入方式です。ハードウェアの並列性を意識した代入となります。

always @(posedge clk) begin
  a <= b;
  c <= a;
end

この場合、a <= bc <= a同時に評価され、クロックエッジ後に一括して反映されます。そのため、c には前のクロック周期のaの値が入ります。

主な用途

  • 順序回路(レジスタ、フリップフロップ)
  • 複数の状態を正確に保持・伝搬したい場合

ブロッキングとノンブロッキングの違いまとめ

特徴ブロッキング代入 (=)ノンブロッキング代入 (<=)
実行順序上から順に処理全体を評価し、同時に反映
主な使用場面組み合わせ回路順序回路
代入結果の反映タイミングすぐに反映されるクロックエッジ後に反映
よくあるミス意図しないラッチの生成値が更新されない・伝搬されない

混在させるとどうなる?

=<= を同一ブロックや同一信号に対して混在させることは原則避けるべきです。以下のような記述は、シミュレーションでは正しく見えても、合成後のハードウェアではバグの原因になります。

always @(posedge clk) begin
  a = b;
  a <= c;
end

この例では、a に対して2回代入しており、順序も代入方法も混在しています。これにより、どの値が最終的に記録されるか不明確になります。

使い分けの指針

  • 組み合わせ回路では = を使う(always @(*) の中)
  • 順序回路(クロック同期)では <= を使う(always @(posedge clk) の中)

このルールに従って記述するだけでも、多くのミスを防ぐことができます。

4. always文を使用する際の注意点とよくあるミス

センシティビティリストの記述ミス

感知すべき信号を正しく記述しないとバグの温床に

Verilogでは、always文の感知リスト(@(...))にどの信号の変化に反応するかを明記する必要があります。以下はセンシティビティリストに一部の信号しか書かれていない例です。

always @(a) begin
  if (b)
    y = 1'b1;
  else
    y = 1'b0;
end

このコードでは、bの変化には反応しません。そのため、bが変わっても出力yが更新されないというバグが発生します。

解決策:@(*) を使う

センシティビティリストの書き忘れや漏れを防ぐためには、以下のように @(*) を使うのが推奨されます。

always @(*) begin
  if (b)
    y = 1'b1;
  else
    y = 1'b0;
end

@(*)は、文中で参照しているすべての信号を自動的に感知リストに含めてくれるため、保守性・安全性の面でも優れています。

意図しないラッチの生成

if文・case文の記述漏れがラッチを生む

以下のように、条件分岐の中ですべてのケースに代入が行われないと、合成ツールは「値を保持する必要がある」と判断し、ラッチ(Latch)が生成されます。

always @(*) begin
  if (enable)
    y = d; // enableが0のとき、yの値が未定義のまま
end

このコードは一見正しそうに見えますが、enable0のときにyの値が更新されないため、前回の値を保持するラッチが自動的に挿入されてしまいます

解決策:すべての条件で代入を行う

always @(*) begin
  if (enable)
    y = d;
  else
    y = 1'b0; // 必ず代入される
end

このようにどの条件でもyに明示的に値を与えることで、ラッチの生成を防ぐことができます。

条件分岐が複雑すぎる

複雑なif文やcase文を使って出力信号を制御している場合、条件が網羅されていないと未定義動作や論理抜けが発生する可能性があります。

よくあるケース:case文でdefaultがない

always @(*) begin
  case(sel)
    2'b00: y = a;
    2'b01: y = b;
    2'b10: y = c;
    // 2'b11 の場合に値が未定義になる可能性
  endcase
end

このように、case文で全ての条件を網羅していないと、意図しない値が出力されることがあります。

解決策:default節の追加

always @(*) begin
  case(sel)
    2'b00: y = a;
    2'b01: y = b;
    2'b10: y = c;
    default: y = 1'b0; // セーフティネットとして必須
  endcase
end

default節を用意することで、どのような入力が来ても出力が定義されるようになり、設計の安全性が向上します。

複数の信号を同時に制御するときの注意

1つのalways文で複数の信号を制御する場合、代入の順序や記述漏れにより、意図しない依存関係が生まれることがあります。複雑な回路では、制御対象を明確に分けるために複数のalwaysブロックに分割することも検討すべきです。

よくある落とし穴のまとめ

問題原因解決策
出力が更新されない感知リストに必要な信号が含まれていない@(*) を使って自動感知にする
ラッチが生成される一部の条件で代入が行われていないelsedefaultで必ず値を代入する
未定義動作が起こるcase文で全条件を網羅していないdefaultを必ず記述する
制御が複雑になりすぎる複数の信号を1つのブロックで同時に扱っている信号ごとにalwaysブロックを分けるなど整理

5. SystemVerilogにおけるalways文の拡張

always_comb:組み合わせ回路専用

概要

always_combは、従来の always @(*) とほぼ同じ動作をしますが、明示的に「組み合わせ論理である」ことを示す構文です。

always_comb begin
  y = a & b;
end

主なメリット

  • 感知リストを自動生成
  • 意図しないラッチ生成時にツールが警告を出してくれる
  • 以前に定義された同名変数との干渉を防ぐ

使用例(Verilogとの比較)

// Verilog
always @(*) begin
  y = a | b;
end

// SystemVerilog
always_comb begin
  y = a | b;
end

always_ff:順序回路専用(フリップフロップ)

概要

always_ffは、クロック駆動の順序回路を記述するための構文で、posedge clknegedge rst のようなトリガ条件を必須とします

always_ff @(posedge clk or negedge rst_n) begin
  if (!rst_n)
    q <= 1'b0;
  else
    q <= d;
end

主なメリット

  • <= ノンブロッキング代入のみ許可(=はエラー)
  • センシティビティリストが正しいかツールがチェック
  • 順序回路であることが一目でわかるため、保守性が高い

always_latch:ラッチ回路専用

概要

always_latchは、意図的にラッチ(レベルトリガ)を記述する場合に使う構文です。ただし、意図しないラッチ生成を防ぐためにも、使用は最小限にとどめるべきです。

always_latch begin
  if (enable)
    q = d;
end

注意点

  • 条件分岐によって代入がスキップされるとき、ラッチ生成が明示される
  • 設計上どうしても必要なとき以外は、極力使用を避ける方がよい

SystemVerilog構文の使い分け

構文用途Verilogでの対応特徴
always_comb組み合わせ回路always @(*)感知リスト自動生成、ラッチ検出が可能
always_ffフリップフロップalways @(posedge clk)クロック同期、代入ミスを防ぐ
always_latchラッチalways @(*)(条件分岐不備)意図的なラッチ記述に限定、誤用検出が可能

これからの開発ではSystemVerilogの利用が主流に

近年の開発現場では、可読性や安全性の観点からSystemVerilog構文が推奨されるケースが増えています。ツールによる構文解析機能も進化しており、always_ffalways_combを使うことで「書いたつもりだけど動かない」ミスを未然に防げるようになっています。

特に、チーム開発や大規模な設計案件では、構文によって回路の意図が明確になるため、コードレビューや保守の効率が飛躍的に高まります。

6. FAQ:always文に関するよくある質問と回答

この章では、VerilogやSystemVerilogでのalways文の使用に関して、実際によくある疑問や検索されやすいポイントに対して、簡潔かつ正確に答えていきます。初心者から中級者まで、設計現場で「あるある」な悩みをカバーします。

Q1. always文内でif文とcase文、どちらを使えばいいの?

A. 基本的には、条件分岐のパターン数や複雑さによって使い分けます。

  • 条件が2~3個程度 → if文が簡潔で読みやすい
  • 明確に分岐する複数の状態がある場合 → case文の方が見やすく、意図も伝わりやすい

また、case文を使うとすべてのケースを網羅することが前提となるため、設計ミスを減らす効果もあります。

Q2. センシティビティリストを省略すると何が起きるの?

A. 感知リストを省略、または一部の信号しか含まれていない場合、一部の信号が変化してもalways文が実行されず、出力が更新されない可能性があります。

これにより、シミュレーションと実機で動作が異なるという厄介な問題を引き起こします。
これを防ぐには、@(*) または SystemVerilog の always_comb を使うのがベストです。

Q3. always文で意図しないラッチが生成されるのはなぜ?

A. 条件分岐(ifcase)の中ですべての条件で出力変数に代入していない場合、合成ツールは「値を保持する必要がある」と判断し、自動的にラッチを生成します。

例(NG):

always @(*) begin
  if (en)
    y = d; // en==0のとき、yは保持される
end

解決策:

always @(*) begin
  if (en)
    y = d;
  else
    y = 1'b0; // 必ず代入される
end

Q4. =<=は混在させてはいけないの?

A. 基本的には同一のalwaysブロック内で混在させるべきではありません

  • 組み合わせ回路 → =(ブロッキング代入)
  • 順序回路 → <=(ノンブロッキング代入)

特に、同じ信号に対して=<=の両方を使うとシミュレーションでは動くが、実機では予期せぬ動作をすることがあります。

原則:

1つの信号に対しては、一貫した代入方式を使うこと

Q5. always_ffと従来のalways @(posedge clk)の違いは?

A. 動作的にはほぼ同じですが、コードの安全性と可読性の面でalways_ffが優れています

比較項目always @(posedge clk)always_ff
センシティビティ自分で明示する必要あり自動でチェックされる
誤った代入(=など)コンパイルは通る可能性もエラーとして検出される
可読性回路の意図が曖昧になる「順序回路」と明確に分かる

Q6. always文で複数の信号を制御しても大丈夫?

A. 原則としては可能ですが、制御対象が多くなると保守性やバグの発見が難しくなるため、必要に応じて複数のalwaysブロックに分けるのが望ましいです。

分割の目安:

  • 出力ごとに動作が独立している場合
  • 同期制御と非同期制御が混在している場合

Q7. 組み合わせ回路なのに<=を使うとどうなる?

A. 通常はシミュレーション上では動作しますが、論理合成時に想定外の回路が生成されることがあります。基本的に、組み合わせ回路ではブロッキング代入(=)を使いましょう。

7. まとめ

always文はVerilog設計の基礎にして最重要構文

Verilogによるハードウェア設計において、always文は組み合わせ回路と順序回路の両方を記述できる強力なツールです。設計の幅を広げるだけでなく、制御の流れやタイミングを明確に記述できるため、初心者からプロフェッショナルまで必須の知識と言えるでしょう。

本記事では、以下のポイントを中心に詳しく解説しました。

本記事の振り返り

  • always @(*)always @(posedge clk) の違いと使い分け
  • =(ブロッキング代入)と <=(ノンブロッキング代入)の違いと正しい使用シーン
  • センシティビティリストの書き方やラッチ生成の回避など、よくあるミスへの対処法
  • SystemVerilogで導入された always_combalways_ffalways_latch による安全な設計スタイル
  • 実際の設計現場でよくある疑問をFAQ形式で整理し、実用的な回答を提示

記述の精度が品質を決める

ハードウェア記述は「書いた通りに回路ができる」ため、コード上の些細なミスがそのまま物理的な不具合に直結する可能性があります。特にalways文は動作の中心を担う構文なので、記述の正確性、代入方法、条件分岐の網羅性など、細かい部分にまで注意を払うことが大切です。

今後のステップ:より高レベルな設計へ

always文を正しく使えるようになれば、次は以下のようなステップに進むことができます。

  • 状態遷移回路(FSM:Finite State Machine)の設計
  • パイプライン構造やストリーミング処理の実装
  • IPコアの作成FPGAへの実装

また、SystemVerilogやVHDLといった他のHDL言語にも対応することで、より広い設計現場でも通用するスキルが身につきます。

最後に:設計者としての心構え

回路設計は「動けばOK」ではなく、「正確に動作し、将来の拡張や変更にも耐えられる構造」が求められます。
本記事を通して、always文に関する基本的な知識だけでなく、安全で堅牢な設計のための考え方も感じ取っていただけたなら幸いです。