Verilog基本文法(前編)

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

1. モジュール構造

もふねこ

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

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

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
E4 図1:BUFMEMモジュールの図。入力: WRが内部回路に入り、内部回路から出力: EMPが出る。入出力: DATA 16bitも内部回路につながる。
E4 図1:BUFMEMモジュールの図。横に長い図はスクロールして確認できます。

まず実験:reg と書けばフリップフロップ?

「reg は記憶する型、wire は線」と覚えると、あとで困ります。同じ式を3通りで書いて、どれが値を覚えるのかを動かして確かめます。

3通りの書き方と自動判定テストベンチをダウンロード(ZIP)

並べているもの

同じ式 a & b を3通りで書く
出力宣言と書き方できるもの
y_assignwire + assign組合せ回路
y_combreg + always @*組合せ回路(記憶しない)
y_ffreg + always @(posedge clk)フリップフロップ

後ろの2つは、どちらも reg と宣言しています。違うのは代入の仕方だけです。

回路本体 reg_vs_wire.v を見る
`timescale 1ns/1ps
`default_nettype none
// The same expression written three ways. Two of them are combinational
// even though one of those is declared as reg.
module reg_vs_wire (
    input  wire clk,
    input  wire a,
    input  wire b,
    output wire y_assign,   // continuous assignment on a wire
    output reg  y_comb,     // reg assigned in always @*  -> still combinational
    output reg  y_ff        // reg assigned at a clock edge -> a flip-flop
);
    assign y_assign = a & b;

    always @* begin
        y_comb = a & b;
    end

    always @(posedge clk) begin
        y_ff <= a & b;
    end
endmodule
`default_nettype wire
テストベンチ tb_reg_vs_wire.v を見る
`timescale 1ns/1ps
`default_nettype none
// Changes the inputs between clock edges and watches which outputs follow.
module tb_reg_vs_wire;
    reg clk = 0;
    reg a, b;
    wire y_assign;
    wire y_comb, y_ff;
    reg expected_ff;
    integer checks = 0;
    integer i;

    reg_vs_wire dut(.clk(clk), .a(a), .b(b),
                    .y_assign(y_assign), .y_comb(y_comb), .y_ff(y_ff));
    always #5 clk = ~clk;

    task check;
        input actual, want;
        input [8*12-1:0] label;
        begin
            checks = checks + 1;
            if (actual !== want)
                $fatal(1, "FAIL: check=%0d %0s a=%b b=%b actual=%b want=%b time=%0t",
                       checks, label, a, b, actual, want, $time);
        end
    endtask

    initial begin
        $dumpfile("reg_vs_wire.vcd");
        $dumpvars(0, tb_reg_vs_wire);
        a = 0; b = 0;
        @(negedge clk);
        @(posedge clk); #1;
        expected_ff = 1'b0;

        // Four input combinations, each applied between two clock edges.
        for (i = 0; i < 4; i = i + 1) begin
            @(negedge clk);
            {a, b} = i[1:0];
            #1;
            // The two combinational outputs follow at once.
            check(y_assign, a & b, "assign now");
            check(y_comb,   a & b, "reg comb now");
            // The flip-flop still holds what it sampled at the last edge.
            check(y_ff, expected_ff, "ff holds");
            @(posedge clk); #1;
            expected_ff = a & b;
            check(y_ff, expected_ff, "ff updated");
        end

        // Change the inputs twice inside one clock period: only the edge counts.
        @(negedge clk);
        a = 1; b = 1; #1;
        check(y_comb, 1'b1, "comb follows");
        a = 1; b = 0; #1;
        check(y_comb, 1'b0, "comb follows");
        $display("  mid-cycle: a=%b b=%b -> assign=%b comb=%b ff=%b (held)",
                 a, b, y_assign, y_comb, y_ff);
        @(posedge clk); #1;
        check(y_ff, 1'b0, "ff sampled");

        $display("PASS: reg vs wire (%0d checks)", checks);
        $finish;
    end

    initial begin
        #2000;
        $fatal(1, "FAIL: testbench timeout");
    end
endmodule
`default_nettype wire

実行方法

iverilog -g2012 -Wall -s tb_reg_vs_wire -o sim.vvp reg_vs_wire.v tb_reg_vs_wire.v
vvp sim.vvp

テストベンチはクロックの合間に入力を変えて、どれが即座に追従してどれが保持するかを見ます。Windows / Icarus Verilog 13.0で2026年10月27日に実行した結果です。

  mid-cycle: a=1 b=0 -> assign=0 comb=0 ff=1 (held)
PASS: reg vs wire (19 checks)

reg と宣言した y_comb も、assign と同じようにその場で0へ変わりました。覚えているのは y_ff だけです。

reg は「手続き代入の左辺に書ける型」という意味であって、記憶素子になるかどうかは代入の仕方で決まるんだ。always @* で全部の経路に代入していれば組合せ回路、クロックのエッジで代入すればフリップフロップになるよ。代入漏れがあると意図しないラッチになる話は応用実験5にあるよ。

型を取り違えたら、コンパイルで止まる?

「型を間違えても通ってしまうのか」も確かめました。結果は使う標準の版によって変わります。

2026年10月27日 Icarus Verilog 13.0での実行結果
書き方-g2001 / -g2005-g2012
reg を assign で駆動エラー
Variable 'y_comb' cannot be driven by a continuous assignment
通る
wire を always の中で代入エラー
'y_assign' is not a valid l-value for a procedural assignment
エラー

上の行が版によって変わるのは、SystemVerilogでは変数への連続代入が認められているからです。だから「reg に assign は絶対に書けない」とは言い切れません。下の行はどちらの版でもエラーになります。使っているツールと標準の指定を確かめてね。

エッジの後だけ見ると、区別がつかない

always @* を always @(posedge clk) に書き換えた回路に、クロックの立ち上がり後だけを見るテストベンチを実行すると、こうなります。

PASS: partial check (sampled after clock edges only)

合格表示です。エッジの後だけを見ていると、組合せ回路とフリップフロップは同じに見えるんだ。クロックの合間に入力を変えて観測すると、違いがはっきりします。※この簡易テストベンチは、サイト側の検証スクリプトで実行したもので、配布ZIPには入っていません。

確認問題

always @* の中で y = a & b; と書いたとき、合成されるとフリップフロップは作られるでしょうか。

答え

作られません。always @* の中で、どの経路でも y に代入していれば、入力から出力までが組合せ回路になります。reg と宣言していても同じです。ただし、条件によって代入しない経路があると、前の値を保つ必要が生じて意図しないラッチになります(応用実験5で実験できます)。最終的に何が作られたかは、合成レポートでFFの数を見て確認してね。

3. ネット型とレジスタ型

記述の中で用いられる信号はすべて型を宣言する必要があります。回路記述ではネット型とレジスタ型の2種類を使います。

種類意味主なキーワード回路での用途
ネット型配線wire、tri、wand、trior などブロック間配線・論理素子接続。主にwireを使用
レジスタ型手続き代入の左辺に書ける変数reg、integer などラッチ・FF等の値保持信号。主にregを使用。組み合わせ回路でも使用可
💡 ポイント always文を用いた組み合わせ回路では出力をreg宣言し、always文の中で代入します。regは「記憶素子」という名前ですが、値を保持しない組み合わせ回路の出力にも使えます。

4. ポート信号の型

モジュールと外部を接続するポート信号にも型があります。

ポート方向使用可能な型備考
input(入力)ネット型のみレジスタ型で宣言することはできない
output(出力)ネット型 または レジスタ型assign で駆動するならネット型、always の中で代入するならレジスタ型。組合せ回路か順序回路かではなく、代入の書き方で決まる
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(ネット)✅ 可❌ 通常使わない※1✅ 可
reg(レジスタ)✅ 可✅ 可(2次元まで)✅ 可※2

※1:Verilog-2001以降は文法上ネットの配列も可能ですが、実務ではほぼ使いません。
※2:通常のベクタ(例:q[3])のビット選択は可能です。ただし2次元配列(メモリ)の要素は、いったんワード単位で読み出してから使うのが基本です(mem[addr][bit] の直接ビット選択はVerilog-2001以降で可能)。

⚠️ MSB/LSBの順序 通常は [7:0] のようにMSB側を大きな数値にします。逆の [0:7] と宣言すると一般的な数値表現と異なってしまうため注意が必要です。

実験:ビット幅と符号は、黙って値を変える

エラーにも警告にもならないまま、値だけが変わってしまう場面が2つあります。どちらも、動かして全入力を比べれば見つかります。

2つの場面を並べた回路と自動判定テストベンチをダウンロード(ZIP)

1. 計算の幅は、代入先の幅で決まる

assign sum_narrow = a + b;   // 4ビットで受ける
assign sum_wide   = a + b;   // 5ビットで受ける

右辺はまったく同じです。それでも受ける変数の幅が違うと、結果が変わります。

  15 + 1 -> narrow=0 (0000)  wide=16 (10000)

4ビットで受けると桁上げが捨てられて0になり、5ビットで受けると16が残ります。テストベンチは4ビット同士の256通りすべてを確かめています。

2. unsigned が1つ混ざると、比較全体が unsigned になる

assign gt_mixed  = (sa > ub);            // ub が unsigned
assign gt_signed = (sa > $signed(ub));   // 両方 signed

sa は signed と宣言しています。それでも相手が unsigned なら、比較は unsigned で行われます。

  sa=1111 (-1) vs ub=0 -> mixed=1 signed=0
  mixed and signed disagree on 8 of 16 values
PASS: width and sign (544 checks)

-1 > 0 が成り立ってしまうのは、1111 を15として比べているからです。16通りのうち8通り、つまり負の数すべてで結果が食い違います。

回路本体 width_and_sign.v を見る
`timescale 1ns/1ps
`default_nettype none
// Two quiet traps: the width of the assignment target decides the width of
// the calculation, and one unsigned operand makes the whole comparison unsigned.
module width_and_sign (
    input  wire        [3:0] a,
    input  wire        [3:0] b,
    input  wire signed [3:0] sa,
    input  wire        [3:0] ub,
    output wire        [3:0] sum_narrow,
    output wire        [4:0] sum_wide,
    output wire              gt_mixed,
    output wire              gt_signed
);
    assign sum_narrow = a + b;              // 4-bit target: the carry is dropped
    assign sum_wide   = a + b;              // 5-bit target: the carry is kept

    assign gt_mixed  = (sa > ub);           // ub is unsigned -> unsigned compare
    assign gt_signed = (sa > $signed(ub));  // both signed -> signed compare
endmodule
`default_nettype wire
テストベンチ tb_width_and_sign.v を見る
`timescale 1ns/1ps
`default_nettype none
// 256 combinations for the width half, 16 for the sign half.
module tb_width_and_sign;
    reg [3:0] a, b;
    reg signed [3:0] sa;
    reg [3:0] ub;
    wire [3:0] sum_narrow;
    wire [4:0] sum_wide;
    wire gt_mixed, gt_signed;
    reg [4:0] want_wide;
    integer checks = 0;
    integer differ_sign = 0;
    integer i, j;

    width_and_sign dut(.a(a), .b(b), .sa(sa), .ub(ub),
                       .sum_narrow(sum_narrow), .sum_wide(sum_wide),
                       .gt_mixed(gt_mixed), .gt_signed(gt_signed));

    task check;
        input [4:0] actual, expected;
        input [8*12-1:0] label;
        begin
            checks = checks + 1;
            if (actual !== expected)
                $fatal(1, "FAIL: check=%0d %0s a=%b b=%b sa=%b actual=%b expected=%b",
                       checks, label, a, b, sa, actual, expected);
        end
    endtask

    initial begin
        $dumpfile("width_and_sign.vcd");
        $dumpvars(0, tb_width_and_sign);
        sa = 4'd0; ub = 4'd0;

        // Width: the same expression, two different target widths.
        for (i = 0; i < 16; i = i + 1) begin
            for (j = 0; j < 16; j = j + 1) begin
                a = i[3:0]; b = j[3:0];
                #1;
                want_wide = i + j;
                check({1'b0, sum_narrow}, {1'b0, want_wide[3:0]}, "narrow");
                check(sum_wide, want_wide, "wide");
                #1;
            end
        end
        a = 4'd15; b = 4'd1; #1;
        $display("  15 + 1 -> narrow=%0d (%b)  wide=%0d (%b)",
                 sum_narrow, sum_narrow, sum_wide, sum_wide);

        // Sign: the same bits compared two ways, against zero.
        ub = 4'd0;
        for (i = 0; i < 16; i = i + 1) begin
            sa = i[3:0];
            #1;
            check({4'd0, gt_mixed},  {4'd0, (i[3:0] > 4'd0)},        "mixed");
            check({4'd0, gt_signed}, {4'd0, ($signed(i[3:0]) > 0)},  "signed");
            if (gt_mixed !== gt_signed) differ_sign = differ_sign + 1;
            #1;
        end
        sa = -4'sd1; ub = 4'd0; #1;
        $display("  sa=%b (-1) vs ub=0 -> mixed=%b signed=%b", sa, gt_mixed, gt_signed);
        $display("  mixed and signed disagree on %0d of 16 values", differ_sign);

        $display("PASS: width and sign (%0d checks)", checks);
        $finish;
    end

    initial begin
        #20000;
        $fatal(1, "FAIL: testbench timeout");
    end
endmodule
`default_nettype wire

実行方法

iverilog -g2012 -Wall -s tb_width_and_sign -o sim.vvp width_and_sign.v tb_width_and_sign.v
vvp sim.vvp

Windows / Icarus Verilog 13.0で2026年10月28日に544件の判定を確認しました。

見逃していたのは判定ではなく、試した値の範囲

上の2つの誤りを両方とも入れた回路に、判定の中身は変えないまま、与える値の範囲だけを変えたテストベンチを実行しました。期待値はテストベンチの中で計算していて、回路の出力どうしを見比べてはいません。

a,b < 8、sa < 8(桁あふれなし・負の数なし)
  PASS: 136 checks with a,b < 8 and sa < 8

a,b を 16 まで広げただけ(桁あふれが入る)
  FAIL: check=64 wide a=0001 b=1111 sa=0000 actual=00000 expected=10000

sa を 16 まで広げただけ(負の数が入る)
  FAIL: check=137 signed a=0111 b=0111 sa=1000 actual=00001 expected=00000

桁あふれを1つ入れれば幅の誤りが、負の数を1つ入れれば符号の誤りが、同じ判定のままその場で見つかります。つまり、最初の実行が合格したのはテストベンチの判定が甘かったからではなく、試した値の範囲が狭かったからなんだ。境界と符号を試験値に入れることが必要だよ。※この範囲を変えるテストベンチは、サイト側の検証スクリプト(tools/check-e4b.py)で実行したもので、配布ZIPには入っていません。Windows / Icarus Verilog 13.0、2026年10月29日。

わざと壊して、テストの働きも確かめる

2026年10月28日 Windows / Icarus Verilog 13.0での実行結果
壊した場所結果
sum_wide を {1'b0, a + b} と書く(先に4ビットで計算)check=64 wide a=0001 b=1111 actual=00000 expected=10000
$signed() を外して比較を unsigned にするcheck=530 signed sa=1000 actual=00001 expected=00000

確認問題

8ビット同士を足した結果を9ビットの変数で受けたいとき、どう書けばよいでしょうか。

答え

受ける変数を9ビットで宣言すれば足ります。wire [8:0] s = a + b; のように、代入先が9ビットなら計算も9ビットで行われます。先に {1'b0, a + b} と書くと、括弧の中が8ビットで計算されてから広げられるので、桁上げは失われます。上の実験で検出した誤りがこれです。符号付きの場合は、両辺の符号をそろえることも忘れないでね。桁あふれの扱いはC3の加算器の実習でも確かめられます。

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に与える値
呼び出し位置式として扱われるため、代入文の右辺や入力ポートへの信号として使用可
戻り値のビット幅function [7:0] sum; のように宣言で決まる。戻り型と幅をどちらも省略すると1ビットになり、上位ビットは捨てられる
⚠️
幅の書き忘れはエラーにならない 戻り型と幅を省略した function はコンパイルも通り、警告も出ないことがあります。値だけが静かに狂うので、上位ビットが動く試験値で確かめてね。実際に動かして見られるのは文法実験5の実習だよ。

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;

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

E4 図2:TOP階層とSUBBLK u1の図。内部ドライバがDOUTポートにつながり、DOUTポートがレジスタ FF2に直接接続される。FF2のドライバもFF2につながり、DOUT出力とFF2の代入が衝突して更新元が複数になると注記されている。
E4 図2:TOP階層とSUBBLK u1の図。横に長い図はスクロールして確認できます。
⚠️ なぜエラーになるか? ドライバが衝突しているからではありません。変数(レジスタ型)をモジュールの出力で駆動できない、という型の規則によるものです。そのため、ほかに駆動元が無くてもエラーになります。モジュール出力を受ける信号にはネット型(wire)を使ってください。
ただし標準の版によって結果が変わります。Icarus Verilog 13.0で2026年10月28日に実測した結果は次のとおりです。-g2001 と -g2005 は error: Variable 'r' cannot be driven by a continuous assignment/module. でエラー、-g2012(SystemVerilog)ではコンパイルが通って動作しました。SystemVerilogでは変数への連続代入が認められているためです。実際に動かして確かめるならこのページの実習をどうぞ。
もふねこ

この『モジュール出力を reg で受ける』ミスは初心者がよくやるんだ🐾
「ショートするから」ではなく「型の規則だから」なんだ。しかも標準の版によって通ったり通らなかったりするから、エラーが出なくても型は合わせておこうね!

実習で確かめる、つまずきの補足

型・ビット幅・符号をセットで確認する

Verilogの wire は接続を表すネット、reg は手続き代入に使う変数だよ。always @* 内で全経路を代入する reg は組合せ回路にもなる。「regと書いたからFF」という判断はできないんだ。

狭い代入先には上位ビットが入りきらない。広い代入先への拡張は式の幅と符号によってゼロ拡張か符号拡張になるよ。意図して捨てるなら out = in[7:0] のように選択範囲を示そう。加算の桁上がりを保存したいなら入力を先に拡張する。

wire [8:0] sum = {1'b0, a} + {1'b0, b}; // a,bは各8ビット

8ビットsignedのaが-1、8ビットunsignedのbが0のとき、a > b は符号なし比較となり真になるよ。bの0〜255も保持して符号付きで比較するなら、幅を9ビットへ揃えてから比較する。

wire signed [8:0] a_ext = {a[7], a};
wire signed [8:0] b_ext = {1'b0, b};
wire greater = a_ext > b_ext;

$signed(b) だけでは幅は増えないため、8'hffを255として扱いたい場面では-1へ解釈が変わってしまう。0、小さい値だけでなく、最大値、最小値、符号境界、桁上がりを試そう。

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