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
まず実験:reg と書けばフリップフロップ?
「reg は記憶する型、wire は線」と覚えると、あとで困ります。同じ式を3通りで書いて、どれが値を覚えるのかを動かして確かめます。
3通りの書き方と自動判定テストベンチをダウンロード(ZIP)
並べているもの
| 出力 | 宣言と書き方 | できるもの |
|---|---|---|
y_assign | wire + assign | 組合せ回路 |
y_comb | reg + always @* | 組合せ回路(記憶しない) |
y_ff | reg + 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にあるよ。
型を取り違えたら、コンパイルで止まる?
「型を間違えても通ってしまうのか」も確かめました。結果は使う標準の版によって変わります。
| 書き方 | -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以降で可能)。
[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日。
わざと壊して、テストの働きも確かめる
| 壊した場所 | 結果 |
|---|---|
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行で
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は直前の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;
❌ 誤った接続(レジスタ衝突)のイメージ
ただし標準の版によって結果が変わります。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省略 | ポート信号は省略可だが複数ビット接続では必ず宣言する |
