Verilog基本文法(後編)
1. 論理値と定数表現
ソフトウェアと違って、ハードウェアの信号には「不定値(X)」と「ハイインピーダンス(Z)」があるんだ🐾
この4つの状態と、ちょっと変わった「数字の書き方」を覚えよう!
VerilogHDLで扱える論理値は以下の4値のみです。
| 値 | 意味 | 用途 |
|---|---|---|
0 | 論理0(Low) | 通常の論理値 |
1 | 論理1(High) | 通常の論理値 |
x | 不定値 | 初期化前・競合状態 |
z | ハイインピーダンス | トライステートバッファ出力 |
定数の書式
定数は <ビット幅>'<基数><数値> の形式で表現します。
| 要素 | 内容 | 省略時のデフォルト |
|---|---|---|
| ビット幅 | 10進数でビット幅を指定 | 32ビット |
| 基数 | b/B=2進、o/O=8進、d/D=10進、h/H=16進 | 10進(d) |
| 数値 | 基数に対応した値。xとzも使用可(10進を除く) | — |
4'b1010 // 4ビット2進数: 1010
8'hFF // 8ビット16進数: 255
8'b1111_1111 // アンダースコアで区切り可(読みやすく)
8'hxx // 不定値
1'bz // 1ビットハイインピーダンス
255 // ビット幅指定なし→32ビット10進数
2. 演算子一覧
| 種類 | 演算子 | 説明 |
|---|---|---|
| 算術演算 | + - * / % | 加減乗除・余り |
| ビット演算 | & | ~ ^ ~^ | AND/OR/NOT/XOR/XNOR(ビット単位) |
| リダクション演算 | &A |A ^A | 全ビットに作用→1ビット出力 |
| 等号演算 | == != === !== | 等値・不等値比較(===はx/zも比較) |
| 関係演算 | < > <= >= | 大小比較 |
| 論理演算 | && || ! | AND/OR/NOT(条件判別・1ビット出力) |
| シフト演算 | << >> | 左シフト・右シフト |
| 条件演算 | 式1 ? 式2 : 式3 | 2方向分岐(セレクタ) |
| 連接演算 | {A, B} | 信号を結合して複数ビット信号を作成 |
3. 算術演算
assign SUM = A + B; // 8ビット加算
assign DIFF = A - B; // 減算
assign PROD = A * B; // 乗算
/(除算)と %(余り)は多くの論理合成ツールが対応していません。シミュレーション専用と考えてください。
4. ビット演算
対応するビット同士で演算します。演算結果のビット幅は入力と同じです。
assign AND_out = A & B; // ビットAND(bit0同士、bit1同士…と演算)
assign OR_out = A | B; // ビットOR
assign NOT_out = ~A; // ビットNOT(反転)
assign XOR_out = A ^ B; // ビットXOR(排他的論理和)
assign XNOR_out= A ~^ B; // ビットXNOR
5. リダクション演算
複数ビット信号の全ビットに演算を作用させ、1ビットの結果を得る演算です。演算子を信号の前に置きます。
reg [7:0] cnt;
wire all_one = &cnt; // cntの全ビットがAND → 全部1のとき1
wire any_one = |cnt; // cntの全ビットがOR → 1つでも1なら1
wire parity = ^cnt; // cntの全ビットがXOR → パリティビット生成
6. 等号演算・関係演算
演算結果は常に1ビット(成立=1、不成立=0)です。
| 演算子 | 意味 | x/z の扱い |
|---|---|---|
== | 等値 | x/zが含まれると不成立(0) |
!= | 不等値 | x/zが含まれると不成立(0) |
=== | 完全等値(case等値) | x/zも比較対象(論理合成不可) |
!== | 完全不等値 | x/zも比較対象(論理合成不可) |
< > <= >= | 大小比較 | x/zが含まれると不成立(0) |
7. 論理演算
複数の条件判別を組み合わせる演算です。演算対象を1ビットの値として扱い、結果も1ビットです。主にif文などの条件判別に使います。
// 例:AがFFhでかつBが0でない場合に処理を実行
if ((A == 8'hFF) && (B != 8'h00)) begin
// 両方の条件が成立したときに実行
end else begin
// いずれか不成立のとき
end
&はビット単位演算(結果はNビット)、&&は条件判別(結果は1ビット)です。詳細は「ビット演算 vs 論理演算」の節を参照。
8. シフト演算
assign Q = A << SFT; // Aを SFT ビット分だけ左シフト
assign R = A >> SFT; // Aを SFT ビット分だけ右シフト
| 方向 | 空いたビット | 溢れたビット |
|---|---|---|
| 左シフト(<<) | LSB側に0を挿入 | MSB側が欠落 |
| 右シフト(>>) | MSB側に0を挿入 | LSB側が欠落 |
9. 条件演算(? :)
1行で2方向分岐を記述できる演算です。式1 ? 式2 : 式3 の形式で、式1が成立なら式2を、不成立なら式3を選びます。
// sel=1ならd1、0ならd0を選ぶ
assign dout = sel ? d1 : d0;
reg dout;
always @(*) begin
if (sel) dout = d1;
else dout = d0;
end
10. 連接演算({})
複数の信号を {} で囲んで結合し、複数ビットの信号を作る演算です。左側が上位ビットになります。
// 右辺での使用:2つの8ビット信号を連接して16ビットに
wire [15:0] addr_bus = {addr_hi, addr_lo}; // 上位8bit=addr_hi, 下位8bit=addr_lo
// 左辺での使用:16ビットの演算結果を2つの8ビット信号に分配
{addr_hi, addr_lo} = addr_bus + 1;
// 繰り返し連接:enable信号を8回繰り返して8ビット信号を生成
wire [7:0] dout = din & {8{enable}}; // dinの全ビットにenableをAND
enable 信号をそのまま8ビット信号にANDすると、下位1ビットにしか影響しません。{8{enable}} で8ビットに拡張してからANDすることで全ビットに作用します。
11. ビット演算 vs 論理演算
| 項目 | ビット演算(&) | 論理演算(&&) |
|---|---|---|
| 演算対象 | 対応するビット同士 | 値全体を1ビットとして扱う |
| 結果のビット幅 | 入力と同じNビット | 常に1ビット |
| 主な用途 | 回路記述(信号のマスク等) | 条件判別(if文等) |
wire [7:0] A = 8'hAA; // 1010_1010
wire [7:0] B = 8'h0F; // 0000_1111
wire [7:0] bit_and = A & B; // ビット演算 → 8'h0A (0000_1010)
wire log_and = A && B; // 論理演算 → 1'b1 (AもBも0でないため)
「&」と「&&」の使い間違いは、コンパイルエラーにならないからバグ探しが大変なんだ🐾
条件分岐(if)には『2つ重ねる(&&)』ってルールにしておくと安全だよ!
12. 演算の優先順位
| 優先度 | 演算子 | 種類 |
|---|---|---|
| 1(最高) | ! ~ & | ^(単項) | 論理NOT・ビットNOT・リダクション演算・符号 |
| 2 | * / % | 乗除算 |
| 3 | + - | 加減算 |
| 4 | << >> | シフト演算 |
| 5 | < > <= >= | 関係演算 |
| 6 | == != === !== | 等号演算 |
| 7 | & | ビットAND |
| 8 | ^ ~^ | ビットXOR/XNOR |
| 9 | | | ビットOR |
| 10 | && | 論理AND |
| 11 | || | 論理OR |
| 12(最低) | ? : | 条件演算 |
優先順位の例
// 例1:* は + より優先
assign Y = A * B + C; // (A*B) + C として演算
assign Y = A * (B + C); // +を優先させたければ括弧で囲む
// 例2:+ は << より優先
assign Y = A << B + C; // A << (B+C) として演算 ← 意図と異なる可能性
assign Y = (A << B) + C; // <<を優先させたければ括弧で囲む
// 例3:同優先度は左から
assign Y = a ^ b & c; // (a^b) & c (左側が優先)
assign Y = a ^ (b & c); // &を優先させたければ括弧で囲む
13. 演算の注意事項
実習:コンパイルは通るのに、値が合わない2つの罠
文法としては正しいのにエラーが出ず、値だけが静かに狂う書き方があります。下の補足で挙げている2つを、実際に動かして確かめます。
2つの罠を並べた回路と自動判定テストベンチをダウンロード(ZIP)
並べているもの
| 出力 | 記述 |
|---|---|
y_wide | function [1:0] select_wide; 戻り値は2ビット |
y_narrow | function select_narrow; 幅を書いていないので戻り値は1ビット |
y_gate | and g_ok(y_gate, a_int, b_int); 組込みゲートは第1引数が出力 |
回路本体 syntax_traps.v を見る
`timescale 1ns/1ps
`default_nettype none
// Two traps that compile without an error.
// select_narrow : a function with no width returns one bit
// select_wide : the same body with an explicit 2-bit return
// and g_ok : the first port of a gate primitive is its output
module syntax_traps (
input wire sel,
input wire [1:0] d0,
input wire [1:0] d1,
input wire ga,
input wire gb,
output wire [1:0] y_narrow,
output wire [1:0] y_wide,
output wire y_gate
);
function select_narrow;
input s;
input [1:0] a, b;
begin
select_narrow = s ? b : a;
end
endfunction
function [1:0] select_wide;
input s;
input [1:0] a, b;
begin
select_wide = s ? b : a;
end
endfunction
assign y_narrow = select_narrow(sel, d0, d1);
assign y_wide = select_wide(sel, d0, d1);
wire a_int, b_int;
assign a_int = ga;
assign b_int = gb;
and g_ok(y_gate, a_int, b_int);
endmodule
`default_nettype wire
テストベンチ tb_syntax_traps.v を見る
`timescale 1ns/1ps
`default_nettype none
// sel 2 x d0 4 x d1 4 = 32 patterns for the two functions, then 4 for the gate.
module tb_syntax_traps;
reg sel;
reg [1:0] d0, d1;
reg ga, gb;
wire [1:0] y_narrow, y_wide;
wire y_gate;
reg [1:0] want;
integer checks = 0;
integer i;
syntax_traps dut(.sel(sel), .d0(d0), .d1(d1), .ga(ga), .gb(gb),
.y_narrow(y_narrow), .y_wide(y_wide), .y_gate(y_gate));
task check;
input [1:0] actual, expected;
input [8*8-1:0] label;
begin
checks = checks + 1;
if (actual !== expected)
$fatal(1, "FAIL: check=%0d %0s sel=%b d0=%b d1=%b actual=%b expected=%b",
checks, label, sel, d0, d1, actual, expected);
end
endtask
initial begin
$dumpfile("syntax_traps.vcd");
$dumpvars(0, tb_syntax_traps);
ga = 0; gb = 0;
for (i = 0; i < 32; i = i + 1) begin
{sel, d0, d1} = i[4:0];
#1;
want = sel ? d1 : d0;
check(y_wide, want, "wide");
// The narrow function keeps only the low bit; the upper bit is lost.
check(y_narrow, {1'b0, want[0]}, "narrow");
if (sel == 1'b1 && d0 == 2'b01 && d1 == 2'b10)
$display(" sel=%b d0=%b d1=%b -> wide=%b narrow=%b (want %b)",
sel, d0, d1, y_wide, y_narrow, want);
#1;
end
for (i = 0; i < 4; i = i + 1) begin
{ga, gb} = i[1:0];
#1;
check({1'b0, y_gate}, {1'b0, ga & gb}, "gate");
#1;
end
$display("PASS: syntax traps (%0d checks)", checks);
$finish;
end
initial begin
#2000;
$fatal(1, "FAIL: testbench timeout");
end
endmodule
`default_nettype wire
実行方法
iverilog -g2012 -Wall -s tb_syntax_traps -o sim.vvp syntax_traps.v tb_syntax_traps.v
vvp sim.vvp
sel×d0×d1 の32通りで2つの関数を比べ、そのあとゲートを4通り確かめます。Windows / Icarus Verilog 13.0で2026年10月27日に実行した結果です。
sel=1 d0=01 d1=10 -> wide=10 narrow=00 (want 10)
PASS: syntax traps (68 checks)
同じ式を書いているのに、幅を書かなかった側は 10 を返せません。戻り値が1ビットなので上位が落ち、00 になります。この function については、Icarus Verilog 13.0に -Wall を付けてもコンパイルは通り、警告も出ませんでした。ただし「警告が出ない」のはこの罠に限った話で、下のゲートの書き順を間違えた版では output port y_gate is coerced to inout. という警告が出ます。ツールと版によっても変わるので、警告の有無を当てにしないでね。
ここが大事なところだよ。エラーにならないだけで、値を見れば分かる。見つからないのは、上位ビットが絡む試験値を入れていないときなんだ。
00と01だけ試すと、見つからない
同じ回路に対して、d0 と d1 を 00 と 01 の範囲だけで与えるテストベンチを実行すると、こうなります。
PASS: partial check (values 00 and 01 only)
合格表示です。1ビットに収まる値しか流していないので、落ちる上位ビットがそもそも無いんだ。10 と 11 を入れて初めて差が出ます。※この簡易テストベンチは、サイト側の検証スクリプトで実行したもので、配布ZIPには入っていません。
わざと壊して、テストの働きも確かめる
| 壊した場所 | 結果 |
|---|---|
function [1:0] から幅を外す | check=17 wide sel=0 d0=10 d1=00 actual=00 expected=10 |
| ゲートの出力を最後に書く | check=65 gate sel=1 d0=11 d1=11 actual=0z expected=00 |
2つ目に注目してね。and g_ok(a_int, b_int, y_gate); と書くと、第1引数が出力という規則どおり a_int が駆動され、出力のつもりだった y_gate は誰にも駆動されず z(ハイインピーダンス)になりました。回路図の左から右の並びをそのまま引数にすると、こうなるんだ。
確認問題
幅を書かない function に、8ビットの加算結果を返させたらどうなるでしょうか。
答え
戻り値は1ビットなので、合計の最下位ビットだけが残ります。同じ形で試したところ、こうなりました(2026年10月27日 Icarus Verilog 13.0)。
3+5 : narrow=0 (00000000) wide=8 (00001000)
3+4 : narrow=1 (00000001) wide=7 (00000111)3 + 5 = 8 のつもりでも 0 が返ります(8は 1000 で最下位が0)。3 + 4 = 7 なら最下位が1なので 1 が返り、一見「それらしい値」に見えるのも厄介なところだよ。呼び出し側の変数を8ビットにしても直りません。直すのは宣言側で、function [7:0] add8; のように返す幅を書きます。引数の幅、中間計算の幅、呼び出し側の幅も合わせて見直してね。ビット幅の扱いは文法実験4、桁あふれの扱いはC3の実習にもあります。
実習で確かめる、つまずきの補足
ゲートの端子順とfunctionの戻り幅
and g1(y, a, b); のような組込み論理ゲートは出力が先だよ。回路図の左から右の並びをそのまま引数にしないでね。順番を誤ったときの診断や値は接続先によって変わり、必ず全信号がXになるとは限らない。多重駆動、未接続、方向を確認しよう。
Verilogで型・幅を省略したfunctionの戻り値は1ビット。2ビットの値3を返すつもりでも下位の1ビットしか残らないんだ。
function [1:0] select2;
input sel;
input [1:0] d0, d1;
begin
select2 = sel ? d1 : d0;
end
endfunction
呼び出し側だけでなく、戻り値、引数、中間計算の幅を揃えよう。試験値を 00 と 01 だけにせず、10 と 11 も含めると上位ビットの欠落を発見できるよ。
📝 E5 演算の注意事項まとめ
| 注意点 | 内容 |
|---|---|
| ビット幅の不一致 | 足りない側は上位に0を補う。多い側は上位が欠落する |
| 定数のビット幅 | ビット幅を明示して記述すること(省略すると32ビット) |
| 符号付き演算 | 本教材の範囲(Verilog-1995相当)では符号を直接記述せず、符号処理回路を別途設計する(※Verilog-2001以降は signed キーワードで符号付き演算も可能) |
| 演算優先順位 | 優先順位に頼らず括弧で明示する方が安全・可読性が高い |
| 除算・余り | 論理合成ツール非対応のことが多い。シミュレーション専用 |
| ===演算子 | x/zも比較可能だが論理合成不可。テストベンチ専用 |
