Verilogの記述スタイルを学ぶ
📌 このページについて
論理回路は組合せ回路と順序回路に分かれますが、HDL設計でもこの二つを区別して記述します。E4・E5の基本文法を踏まえて、4つの基本スタイルをしっかり身につけましょう。
1. 4つの基本スタイル
文法を覚えたら、次はこの「4つの書き方の型(スタイル)」をマスターしよう🐾
どんなに複雑な回路も、結局はこの4つの組み合わせでできているんだよ!
VerilogHDLによる回路記述には以下の4つの基本スタイルがあります。数百ゲートでも数十万ゲートでもこれらをベースにしています。
| # | スタイル | 用途 | 特徴 |
|---|---|---|---|
| 1 | assign文による組合せ回路 | シンプルな論理式・算術式 | 1行で記述できる単純な回路 |
| 2 | functionによる組合せ回路 | 条件分岐を含む複雑な回路 | 複数行・always文でも同様に記述可 |
| 3 | always文による順序回路 | ラッチ・フリップフロップを含む回路 | posedge/negedgeでクロックエッジを指定 |
| 4 | 下位モジュール接続 | 階層設計・モジュールの組み合わせ | インスタンス名で個別識別 |
2. スタイル1: assign文による組合せ回路
1行の論理式や算術式で記述できる回路は assign 文を用います。assign ではじまり ;(セミコロン)で終わります。代入記号 = の左辺に代入先、右辺に式を記述します。フリーフォーマットなので見かけ上2行以上になっても構いません。
記述例
// 2入力NAND(ビット演算子で論理式表現)
assign nand_out = ~(a & b);
// セレクタ2to1(sel=0ならd0、sel=1ならd1を選択)
assign dout = sel ? d1 : d0;
// 桁上がり信号(cntoutが9のときcarryに1を出力)
assign carry = (cntout == 4'd9) ? 1'b1 : 1'b0;
// 加算回路(aとbを足してqに出力)
assign q = a + b;
💡 ビット幅の省略
セレクタや加算の例のように、代入先・代入元のビット幅は宣言部で定義済みであれば記述を省略できます。
3. スタイル2: functionによる組合せ回路
条件分岐などを含む複雑な回路は function を使って記述します。functionには定義部と呼び出し部があります。
例: 2入力4出力デコーダ(2→4デコード回路)
入力 din で指定したビットだけが1になり dout に出力されます(ワンホット出力)。
// ─── 定義部 ───────────────────────────────────────────
function [3:0] dec; // 戻り値4ビット、function名はdec
input [1:0] din; // 入力引数の宣言
begin
case (din)
2'b00: dec = 4'b0001;
2'b01: dec = 4'b0010;
2'b10: dec = 4'b0100;
2'b11: dec = 4'b1000;
default: dec = 4'bxxxx;
endcase
end
endfunction
// ─── 呼び出し部(代入文の右辺として使用)─────────────
assign dout = dec(din); // 引数dinを渡してfunction decを呼び出す
| dinの値 | doutの値 |
|---|---|
| 2'b00 (0) | 4'b0001 |
| 2'b01 (1) | 4'b0010 |
| 2'b10 (2) | 4'b0100 |
| 2'b11 (3) | 4'b1000 |
💡 begin〜endについて
case文が1つだけならbegin〜endは不要ですが、慣例として囲む方が可読性が高くなります。複数の文を記述する場合は必須です。
4. スタイル3a: always文による組合せ回路
組合せ回路は always 文でも記述できます。functionとほぼ同一の記述が可能です。
例: 同じ2→4デコーダをalways文で記述
reg [3:0] dout; // 出力をレジスタ宣言
always @(din) begin // dinに変化があれば実行(センシティビティリスト)
case (din)
2'b00: dout <= 4'b0001;
2'b01: dout <= 4'b0010;
2'b10: dout <= 4'b0100;
2'b11: dout <= 4'b1000;
default: dout <= 4'bxxxx;
endcase
end
📌 functionとalways文の使い分け
どちらで記述しても同じ回路が生成されます。詳細なメリット・デメリットはO6ユニットで解説します。
5. スタイル3b: always文による順序回路
順序回路はレジスタ信号と always 文を使って記述します。
| 要素 | 内容 |
|---|---|
| 出力信号の宣言 | 必ず reg(レジスタ型)で宣言する |
| クロックエッジ指定 | posedge=立ち上がり、negedge=立ち下がり |
| always内の代入 | ネット型(wire)には代入不可。regのみ |
| 代入記号 | ノンブロッキング代入 <= を使用する |
| センシティビティリスト | クロックと非同期リセット/セット信号のみ記述 |
例1: Dフリップフロップ(最もシンプルな順序回路)
module DFF (CK, D, Q);
input CK, D;
output Q;
reg Q;
always @(posedge CK) begin // CKの立ち上がりごとに実行(無限ループ)
Q <= D; // DフリップフロップDの値をQに記録
end
endmodule
graph LR
D[入力 D] --> FF[D-FF
► CK] CK[クロック CK] --> FF FF --> Q[出力 Q] style FF fill:#e3f2fd,stroke:#1976d2,stroke-width:2px;
► CK] CK[クロック CK] --> FF FF --> Q[出力 Q] style FF fill:#e3f2fd,stroke:#1976d2,stroke-width:2px;
💡 always文は無限ループ
always @(posedge CK) はCKの立ち上がりを待ち続け、立ち上がるたびにブロック内を実行します。これを無限に繰り返すことでフリップフロップの動作を表現します。
例2: 非同期リセット付きカウンタ
reg [3:0] cnt;
// センシティビティリスト: クロックckと非同期リセットresのエッジを監視
always @(posedge ck or posedge res) begin
if (res) begin
cnt <= 4'b0000; // res=1なら非同期リセット(0クリア)
end else begin
cnt <= cnt + 1; // res=0ならカウントアップ
end
end
📌 非同期リセットの書き方
順序回路のalways文のセンシティビティリストには、クロックと非同期リセット/セット信号のみを記述します。その他の入力信号を追加すると論理合成で意図しない回路が生成される場合があります。
6. スタイル4: 下位モジュール接続
下位モジュールの接続は以下の書式で記述します。
モジュール名 インスタンス名 (ポートリスト);
// 例:DFFを4つ接続して4ビットシフトレジスタを作成
DFF DFF0 (.CK(ck), .D(d), .Q(q0));
DFF DFF1 (.CK(ck), .D(q0), .Q(q1));
DFF DFF2 (.CK(ck), .D(q1), .Q(q2));
DFF DFF3 (.CK(ck), .D(q2), .Q(q3));
4ビットシフトレジスタの構造
graph LR
d --> FF0[DFF0
►] FF0 -->|q0| FF1[DFF1
►] FF1 -->|q1| FF2[DFF2
►] FF2 -->|q2| FF3[DFF3
►] FF3 --> q3 ck -.-> FF0 ck -.-> FF1 ck -.-> FF2 ck -.-> FF3 style FF0 fill:#e8f5e9,stroke:#388e3c,stroke-width:2px; style FF1 fill:#e8f5e9,stroke:#388e3c,stroke-width:2px; style FF2 fill:#e8f5e9,stroke:#388e3c,stroke-width:2px; style FF3 fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
►] FF0 -->|q0| FF1[DFF1
►] FF1 -->|q1| FF2[DFF2
►] FF2 -->|q2| FF3[DFF3
►] FF3 --> q3 ck -.-> FF0 ck -.-> FF1 ck -.-> FF2 ck -.-> FF3 style FF0 fill:#e8f5e9,stroke:#388e3c,stroke-width:2px; style FF1 fill:#e8f5e9,stroke:#388e3c,stroke-width:2px; style FF2 fill:#e8f5e9,stroke:#388e3c,stroke-width:2px; style FF3 fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
💡 インスタンス名は固有でなければならない
同じモジュール(DFF)を複数使う場合、インスタンス名(DFF0, DFF1…)はそれぞれ固有の名前にする必要があります。
ポート接続の2つの方法
❌ 順番による接続(非推奨)
// ポートリストの順番に接続
DFF DFF0 (ck, d, q0);
DFF DFF1 (ck, q0, q1);
信号を追加・変更した際にポート定義順と一致しないと誤接続が起きる。
✅ 名前による接続(推奨)
// .ポート名(接続信号名) で明示
DFF DFF0 (.CK(ck), .D(d), .Q(q0));
DFF DFF1 (.CK(ck), .D(q0), .Q(q1));
信号を追加・順番を変更しても誤接続が起きない。デバッグ時の変更に強い。
⚠️ 接続方法の混在は不可
一つのモジュール接続の中で、順番による接続と名前による接続を混在させることはできません。どちらか一方に統一してください。
これでEシリーズ(文法ノート)は全部クリアだね、すごい!🐾
次はいよいよ、これらの文法を使って『実際の論理回路』を組み立てる「Cシリーズ(回路ノート)」に挑戦しよう!
7. 記述上の注意事項
| 注意点 | 内容 |
|---|---|
| 記述量 ≠ 回路規模 | 1行のassign文でも +,-,* などを含むと回路規模が大きくなる場合がある。逆に記述が複雑でも回路規模が小さい場合もある。 |
| case文のdefault | defaultがなくても文法エラーにはならないが、ラッチ生成を防ぎ回路規模・デバッグの両面で有利なため必ず記述する。 |
| モジュール接続は名前接続を使う | デバッグ中に信号を追加することはよくある。順番接続だと誤接続のリスクがあるため、名前接続を使う習慣をつける。 |
| 順序回路の代入は <= を使う | always文内の順序回路でははノンブロッキング代入(<=)を使う。ブロッキング代入(=)との違いはO2ユニットで解説。 |
⚠️ 演算子を含む回路の規模に注意
assign q = a * b; のような乗算は、論理合成すると大規模な乗算回路が生成されます。1行でも回路規模が大きくなることを意識しましょう。
📝 E6 まとめ:4つの基本スタイル チェックリスト
| スタイル | キーポイント |
|---|---|
| assign文(組合せ) | 1行の論理式・算術式。assign〜; の形式 |
| function(組合せ) | 定義部 + 呼び出し部。case文・if文が使える |
| always(組合せ) | センシティビティリストに全入力を列挙。出力はreg宣言 |
| always(順序) | posedge/negedgeでエッジ指定。代入は<=(ノンブロッキング) |
| 下位モジュール接続 | モジュール名 + インスタンス名 + ポートリスト。名前接続を推奨 |
| case文のdefault | 省略可だが必ず記述すること |
| 接続方法の混在 | 順番接続と名前接続は同一モジュール内で混在不可 |
