Verilog基本文法(前編)

📌 このページについて HDLは言語なので記述のための約束(文法)があります。いきなり全部は理解できないと思いますので、まずは回路記述に必要な部分だけ覚えましょう。

1. モジュール構造

もふねこ

ここからが文法の本番!「モジュール」は回路の部品みたいなものだよ🐾
まずはこのモジュールの書き方と、線の種類(wireとreg)をしっかり押さえよう!

回路を記述する基本構造がモジュールです。モジュールは予約語の moduleendmodule で囲まれ、回路表現からシミュレーション用の入力まですべてがこの中で記述されます。

module モジュール名 (ポートリスト);
    // ポート宣言
    // ネット宣言 / レジスタ宣言
    // パラメータ宣言
    // 回路記述(assign文、function、always文、別モジュール接続など)
endmodule
構成要素内容記述順序
モジュール名英数字とアンダースコアが使用可(大小文字区別あり)module直後
ポートリスト入出力端子名の列挙モジュール名の直後
ポート宣言ポート信号の方向・ビット幅自由
ネット/レジスタ宣言モジュール内部で使う信号の型自由
パラメータ宣言モジュール内部で使う定数自由
回路記述assign文、always文、function、別モジュール接続自由
⚠️ 注意 宣言していない信号やパラメータが先に使われると文法エラーになります。記述順序は自由ですが、参照前に宣言が必要です。

2. ポート宣言

モジュールの入力・出力の方向とビット幅を定義するのがポート宣言です。ポートの方向はモジュールを基準とします。

方向キーワード書式説明
入力inputinput [ビット幅] 信号名;モジュールへの入力。同一ビット幅なら複数信号をまとめて宣言可
出力outputoutput [ビット幅] 信号名;モジュールからの出力
双方向inoutinout [ビット幅] 信号名;入出力両方向の信号

記述例:BUFMEMモジュール

メモリ回路を想定したBUFMEMの例です。

module BUFMEM (WR, EMP, DATA);
    input        WR;    // 書き込み信号(1ビット入力)
    output       EMP;   // メモリ状態信号(1ビット出力)
    inout [15:0] DATA;  // データ信号(16ビット入出力)
endmodule
graph LR subgraph BUFMEMモジュール CORE[内部回路] end WR(入力: WR) --> CORE CORE --> EMP(出力: EMP) DATA((入出力: DATA
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次元まで)❌ レジスタ単位のみ
⚠️ MSB/LSBの順序 通常は [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行で
📌 assign文の特徴 assign文は「継続的代入文」とも呼ばれます。右辺の値が変化するたびに自動的に左辺に反映されます(always文内のレジスタ代入とは異なります)。

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問題 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;

❌ 誤った接続(レジスタ衝突)のイメージ

graph LR subgraph SUBBLK u1 D1[内部ドライバ] --> DOUT((DOUTポート)) end subgraph TOP階層 DOUT -->|直接接続| FF2[レジスタ FF2] D2[FF2のドライバ] -.-> FF2 end note(DOUT出力と
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;
⚠️ なぜエラーになるか? 下位モジュールの出力(DOUTドライバ)とレジスタ(FF2ドライバ)が同一信号線上で衝突(ショート)するためエラーになります。モジュール出力を受ける信号には必ずネット型(wire)を使用してください。
もふねこ

この『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省略ポート信号は省略可だが複数ビット接続では必ず宣言する