Verilog基本文法(前編)
1. モジュール構造
ここからが文法の本番!「モジュール」は回路の部品みたいなものだよ🐾
まずはこのモジュールの書き方と、線の種類(wireとreg)をしっかり押さえよう!
回路を記述する基本構造がモジュールです。モジュールは予約語の module と endmodule で囲まれ、回路表現からシミュレーション用の入力まですべてがこの中で記述されます。
module モジュール名 (ポートリスト);
// ポート宣言
// ネット宣言 / レジスタ宣言
// パラメータ宣言
// 回路記述(assign文、function、always文、別モジュール接続など)
endmodule
| 構成要素 | 内容 | 記述順序 |
|---|---|---|
| モジュール名 | 英数字とアンダースコアが使用可(大小文字区別あり) | module直後 |
| ポートリスト | 入出力端子名の列挙 | モジュール名の直後 |
| ポート宣言 | ポート信号の方向・ビット幅 | 自由 |
| ネット/レジスタ宣言 | モジュール内部で使う信号の型 | 自由 |
| パラメータ宣言 | モジュール内部で使う定数 | 自由 |
| 回路記述 | assign文、always文、function、別モジュール接続 | 自由 |
2. ポート宣言
モジュールの入力・出力の方向とビット幅を定義するのがポート宣言です。ポートの方向はモジュールを基準とします。
| 方向 | キーワード | 書式 | 説明 |
|---|---|---|---|
| 入力 | input | input [ビット幅] 信号名; | モジュールへの入力。同一ビット幅なら複数信号をまとめて宣言可 |
| 出力 | output | output [ビット幅] 信号名; | モジュールからの出力 |
| 双方向 | inout | inout [ビット幅] 信号名; | 入出力両方向の信号 |
記述例:BUFMEMモジュール
メモリ回路を想定したBUFMEMの例です。
module BUFMEM (WR, EMP, DATA);
input WR; // 書き込み信号(1ビット入力)
output EMP; // メモリ状態信号(1ビット出力)
inout [15:0] DATA; // データ信号(16ビット入出力)
endmodule
16bit)) <--> CORE style CORE fill:#fff3e0,stroke:#f57c00,stroke-width:2px; style WR fill:#e1f5fe,stroke:#03a9f4; style EMP fill:#e8f5e9,stroke:#4caf50; style DATA fill:#f3e5f5,stroke:#9c27b0;
3. ネット型とレジスタ型
記述の中で用いられる信号はすべて型を宣言する必要があります。回路記述ではネット型とレジスタ型の2種類を使います。
| 種類 | 意味 | 主なキーワード | 回路での用途 |
|---|---|---|---|
| ネット型 | 配線 | wire、tri、wand、trior など | ブロック間配線・論理素子接続。主にwireを使用 |
| レジスタ型 | 記憶素子 | reg、integer など | ラッチ・FF等の値保持信号。主にregを使用。組み合わせ回路でも使用可 |
always文を用いた組み合わせ回路では出力をreg宣言し、always文の中で代入します。regは「記憶素子」という名前ですが、値を保持しない組み合わせ回路の出力にも使えます。
4. ポート信号の型
モジュールと外部を接続するポート信号にも型があります。
| ポート方向 | 使用可能な型 | 備考 |
|---|---|---|
| input(入力) | ネット型のみ | レジスタ型で宣言することはできない |
| output(出力) | ネット型 または レジスタ型 | 組み合わせ回路→ネット型、順序回路→レジスタ型 |
| inout(双方向) | ネット型のみ | レジスタ型で宣言することはできない |
wire 宣言と同じになります。
接続例
// ブロック間接続はネット型
wire DBUS; // DOUTとDINを接続する信号(レジスタ型は使用不可)
// 順序回路出力の例
output DOUT;
reg FF1;
assign DOUT = FF1; // assign文でレジスタFF1をポートDOUTに接続
// ポートと同名のレジスタ宣言も可
output DOUT;
reg DOUT; // ポート名と同名のregも使用可能
5. パラメータ宣言
パラメータ宣言は定数の宣言です。記述で使う数値に意味のある名前をつけられます。
parameter パラメータ名 = 定数式;
parameter パラメータ名1 = 定数式1, パラメータ名2 = 定数式2; // 複数宣言可
使用例
| 用途 | 記述例 | 説明 |
|---|---|---|
| クロック周期 | parameter STEP = 10; | 1クロック周期。クロック生成で #(STEP/2) のように使用 |
| ステートマシン | parameter HALT=2'b00, INIT=2'b01; | 数値でなく名前で状態遷移を記述できる |
| メモリサイズ | parameter MEMSIZE = 32; | 16ビット幅×32ワードのメモリ宣言に利用 |
6. 複数ビット信号の宣言
複数ビット信号の宣言は大括弧 [MSB:LSB] を使います。
wire [7:0] dbs; // 8ビットネット信号(MSB=bit7, LSB=bit0)
reg [15:0] addr; // 16ビットレジスタ信号
reg [15:0] mem [0:31]; // 16ビット×32ワードの2次元レジスタ配列(メモリ)
// ビット選択
wire msb = dbs[7]; // 1ビット選択
wire [3:0] Hi_Digit = addr[15:12]; // 範囲選択(15〜12の4ビット)
// レジスタ配列の参照(ビット単位不可・レジスタ単位)
M15 = mem[15]; // 15番地の16ビットを一度に参照
| 型 | 1次元配列 | 2次元配列 | ビット選択 |
|---|---|---|---|
| wire(ネット) | ✅ 可 | ❌ 不可 | ✅ 可 |
| reg(レジスタ) | ✅ 可 | ✅ 可(2次元まで) | ❌ レジスタ単位のみ |
[7:0] のようにMSB側を大きな数値にします。逆の [0:7] と宣言すると一般的な数値表現と異なってしまうため注意が必要です。
7. assign文(継続的代入文)
信号の接続を表現するのがassign文です。assignではじまり、接続先への代入文の形で記述します。コンマで区切れば複数の代入を一つのassign文で記述できます。
// 基本形
assign 信号名 = 式;
assign 信号A = 式1, 信号B = 式2; // 複数まとめて記述可
// 記述例
assign NAND_out = ~(A & B); // 2入力NAND
assign SEL_out = SEL ? D0 : D1; // セレクタ
assign {Cout, Sum} = A + B + Cin; // 桁上がり回路
assign result = a + b; // 加算回路
assign DATA = OE ? dout : 8'bz; // 双方向出力(ハイインピーダンス)
// wire宣言とassign文をまとめて1行で記述
wire [7:0] dout = din + 8'h01; // 宣言と代入を1行で
8. function(関数)
functionはその名の通り関数です。入力を与え値を返す仕組みです。定義部と呼び出し部から構成されます。
// 定義部:8ビット加算回路
function [7:0] sum; // 戻り値ビット幅とfunction名
input [7:0] a; // 仮引数(function内だけで有効)
input [7:0] b;
sum = a + b; // function名への代入が戻り値
endfunction
// 呼び出し部(式として扱われる)
wire [7:0] result = sum(in0, in1); // 実引数を指定して呼び出し
| 要素 | 説明 |
|---|---|
| 仮引数 | functionの中だけで有効な引数。モジュール内の同名信号とは区別される |
| 実引数 | 呼び出し時に実際にfunctionに与える値 |
| 呼び出し位置 | 式として扱われるため、代入文の右辺や入力ポートへの信号として使用可 |
9. always文
always文は繰り返しを記述する構文です。組み合わせ回路・順序回路・クロック生成など幅広い用途に使えます。
// 組み合わせ回路(センシティビティリストにすべての入力を列挙)
always @(a or b) begin
sum = a + b; // sumはreg宣言が必要
end
// 順序回路(クロック立ち上がりエッジ)
always @(posedge CLK) begin
Q <= D;
end
wireなどのネット宣言した信号は always文の中で代入できません。always文内で代入する信号は必ず reg宣言が必要です。
10. if文・case文
条件分岐は if文(2方向)と case文(多方向)で表現します。function や always の中にのみ記述可能(モジュール直下には記述不可)。
if文
if (条件式) begin
// 条件が真のとき実行
end else begin
// 条件が偽のとき実行(elseは省略可)
end
// 例:セレクタ回路
function SOUT;
input SEL, D0, D1;
if (SEL) SOUT = D0;
else SOUT = D1;
endfunction
case文
case (分岐対象の式)
比較値1: 処理1;
比較値2: 処理2;
default: デフォルト処理; // 一致する項目がない場合
endcase
// 例:2入力セレクタ
function SOUT;
input [1:0] SEL;
input D0, D1;
case (SEL)
2'b00: SOUT = D0;
2'b01: SOUT = D1;
default: SOUT = 1'bx; // 不定値・ハイインピーダンス時
endcase
endfunction
11. begin〜end(順次処理ブロック)
begin〜endでいくつかの文をくくると一つの文として扱うことができます。これを順次処理ブロックと呼びます。
always @(posedge CLK) begin
a_reg <= a; // 2つの文をbegin~endで囲って1つの文として扱う
b_reg <= b;
end
elseは直前のifに対応します。段をつけて記述しても対応が誤ることがあります。文が1つでもbegin〜endで囲う習慣をつけることで誤りを防げます(文法エラーにならないため発見が困難)。
// ❌ 危険な記述(elseはb判別に対応している)
if (a)
if (b) X = 1;
else X = 0; // このelseはa判別ではなくb判別に対応!
// ✅ 安全な記述(begin~endで明示)
if (a) begin
if (b) X = 1;
end else begin
X = 0; // elseが確実にa判別に対応する
end
12. wire省略とwire/regの使い分け
ネット宣言を省略できる場合
| ケース | 省略可否 | 説明 |
|---|---|---|
| inputやoutputで宣言されたポート信号 | ✅ 省略可 | デフォルトでwire(ネット型)として扱われる |
| プリミティブゲートやモジュール接続時の1ビット信号 | ✅ 省略可 | 宣言なしでも文法エラーにならない |
| 複数ビット信号の接続 | ❌ 省略不可 | 宣言を忘れると下位1ビットしか接続されないため必ず宣言する |
wireとregの使い分け(接続時の注意)
// ❌ エラー:下位モジュールの出力にレジスタ信号を直接接続
reg FF2;
SUBBLK u1 (.DOUT(FF2)); // DOUTはモジュール出力→レジスタ接続でエラー
// ✅ 正しい:wireで受けてからレジスタに取り込む
wire dout_wire;
SUBBLK u1 (.DOUT(dout_wire));
always @(posedge CLK) FF2 <= dout_wire;
❌ 誤った接続(レジスタ衝突)のイメージ
FF2の代入が衝突!
ショート状態になる) note -.-> DOUT style D1 fill:#bbdefb,stroke:#1976d2; style FF2 fill:#ffcdd2,stroke:#d32f2f; style D2 fill:#ffcdd2,stroke:#d32f2f; style note fill:#ffebee,stroke:#d32f2f,color:#d32f2f;
この『wireとregの衝突エラー』は初心者が一番よくやるミスなんだ🐾
「出力を直接regにつなぐとショートする!」って覚えておいてね!
📝 E4 まとめ:基本文法1 チェックリスト
| 項目 | キーポイント |
|---|---|
| モジュール構造 | module〜endmodule で囲む。内部の記述順序は自由だが未宣言信号の参照はエラー |
| ポート宣言 | input/output/inout で方向とビット幅を宣言 |
| ネット型 vs レジスタ型 | wire=配線、reg=値保持。always文内の代入先は必ずreg |
| ポート信号の型制約 | input・inout はネット型のみ。output はネット型またはレジスタ型 |
| パラメータ宣言 | 数値に意味ある名前をつける。ステート名・クロック周期・メモリサイズなどに使用 |
| 複数ビット信号 | [MSB:LSB]で宣言。wireは1次元のみ、regは2次元まで配列可 |
| assign文 | 継続的代入文。右辺変化で自動反映。モジュール直下に記述可 |
| function | 入力を与えて値を返す。仮引数と実引数を区別する |
| always文 | 繰り返し構文。内部代入信号はreg必須 |
| if/case文 | function・always内のみ記述可。caseのdefaultを忘れずに |
| begin〜end | 複数文を1文にまとめる。ダングリングelse防止に常に使う習慣を |
| wire省略 | ポート信号は省略可だが複数ビット接続では必ず宣言する |
