Verilog記述テクニック集
(1)学習の概要と目標
HDL にも知っていると便利な「コツ」があります。ここではトラブル例を通じて function と always 文の特徴を深く理解し、代入記号の使い分けといくつかのステートメントを学びます。
- トラブル例を参考に、
functionとalways文の特徴を理解する - 代入記号の使い分けと、いくつかのステートメントを学ぶ
- 組み合わせ回路記述での
functionとalways文のメリット/デメリットを理解し説明できる - 代入記号を状況に応じて使い分けできる
wait、repeat、foreverの各ステートメントを理解し説明できる
シミュレーションと論理合成により function と always 文の特徴を確認する
まず実験:case / casez / casex を同じパターンで比べる
3つはよく「ほぼ同じ」と説明されますが、どの値を don't care として扱うかが違います。同じパターンで書き分けた回路に、既知の入力と不明値を含む入力の両方を与えて確かめます。考え方はこのページの補足にもまとめてあるよ。
3通りの書き分けと自動判定テストベンチをダウンロード(ZIP)
回路の仕様
- 4ビットの
selを受け、上位から優先して 3 / 2 / 1 を返します。該当しなければ 0 です。 - パターンは3つとも同じ
4'b1???/4'b01??/4'b001?/defaultです。 - 違いは
case・casez・casexのどれで書いたかだけです。
回路本体 case_variants.v を見る
`timescale 1ns/1ps
`default_nettype none
// The same patterns written with case, casez and casex.
// The patterns use ? so the differences show up directly.
module case_variants (
input wire [3:0] sel,
output reg [1:0] y_case,
output reg [1:0] y_casez,
output reg [1:0] y_casex
);
always @* begin
case (sel) // exact match only; ? is z here
4'b1???: y_case = 2'd3;
4'b01??: y_case = 2'd2;
4'b001?: y_case = 2'd1;
default: y_case = 2'd0;
endcase
end
always @* begin
casez (sel) // z is don't care on BOTH sides; ? denotes z
4'b1???: y_casez = 2'd3;
4'b01??: y_casez = 2'd2;
4'b001?: y_casez = 2'd1;
default: y_casez = 2'd0;
endcase
end
always @* begin
casex (sel) // x and z are don't care on BOTH sides
4'b1???: y_casex = 2'd3;
4'b01??: y_casex = 2'd2;
4'b001?: y_casex = 2'd1;
default: y_casex = 2'd0;
endcase
end
endmodule
`default_nettype wire
テストベンチ tb_case_variants.v を見る
`timescale 1ns/1ps
`default_nettype none
// 16 known inputs, then inputs that contain x.
module tb_case_variants;
reg [3:0] sel;
wire [1:0] y_case, y_casez, y_casex;
reg [1:0] want_priority;
integer checks = 0;
integer i;
case_variants dut(.sel(sel), .y_case(y_case), .y_casez(y_casez), .y_casex(y_casex));
task check;
input [1:0] actual, expected;
input [8*10-1:0] label;
begin
checks = checks + 1;
if (actual !== expected)
$fatal(1, "FAIL: check=%0d %0s sel=%b actual=%b expected=%b",
checks, label, sel, actual, expected);
end
endtask
initial begin
$dumpfile("case_variants.vcd");
$dumpvars(0, tb_case_variants);
// Known inputs: casez and casex agree, plain case never matches.
for (i = 0; i < 16; i = i + 1) begin
sel = i[3:0];
#1;
want_priority = sel[3] ? 2'd3 : sel[2] ? 2'd2 : sel[1] ? 2'd1 : 2'd0;
check(y_case, 2'd0, "case");
check(y_casez, want_priority, "casez");
check(y_casex, want_priority, "casex");
#1;
end
// An input that contains x.
sel = 4'bxx01; #1;
$display(" sel=%b -> case=%0d casez=%0d casex=%0d", sel, y_case, y_casez, y_casex);
check(y_case, 2'd0, "x case");
check(y_casez, 2'd0, "x casez");
check(y_casex, 2'd3, "x casex"); // matched 1??? although bit3 is unknown
sel = 4'b0x10; #1;
$display(" sel=%b -> case=%0d casez=%0d casex=%0d", sel, y_case, y_casez, y_casex);
check(y_case, 2'd0, "x case");
check(y_casez, 2'd0, "x casez");
check(y_casex, 2'd2, "x casex"); // matched 01?? although bit2 is unknown
// z in the expression: casez ignores it too, and plain case can match
// because ? in a pattern is literally z.
sel = 4'bz001; #1;
$display(" sel=%b -> case=%0d casez=%0d casex=%0d", sel, y_case, y_casez, y_casex);
check(y_case, 2'd0, "z case");
check(y_casez, 2'd3, "z casez"); // the z in the expression is ignored
check(y_casex, 2'd3, "z casex");
sel = 4'b1zzz; #1;
$display(" sel=%b -> case=%0d casez=%0d casex=%0d", sel, y_case, y_casez, y_casex);
check(y_case, 2'd3, "z case"); // ? is z, so this is an exact match
check(y_casez, 2'd3, "z casez");
check(y_casex, 2'd3, "z casex");
$display("PASS: case variants (%0d checks)", checks);
$finish;
end
initial begin
#2000;
$fatal(1, "FAIL: testbench timeout");
end
endmodule
`default_nettype wire
実行方法
iverilog -g2012 -Wall -s tb_case_variants -o sim.vvp case_variants.v tb_case_variants.v
vvp sim.vvp
既知の入力0〜15では、casezとcasexは同じ
| 文 | 結果 |
|---|---|
case | 常に default。パターンの ? は z であって、0や1とは一致しない |
casez | ? が don't care として働き、優先順位どおりに選ばれる |
casex | casez と同じ結果 |
case にパターンの ? を書いても効かない、というのが1つ目の落とし穴だよ。そして既知の入力だけを試しているうちは、casezとcasexの違いは出てきません。
xを含む入力を与えると、casexだけ分岐してしまう
sel=xx01 -> case=0 casez=0 casex=3
sel=0x10 -> case=0 casez=0 casex=2
PASS: case variants (60 checks)
casex は式の側の x も don't care として扱います。xx01 の最上位ビットは不明なのに、4'b1??? に一致して3を返しました。「1だったことにして」分岐を選んだわけです。casez は x を無視しないので、ここでは一致せず default になります。
z を含む入力だと、casezも一致する
sel=z001 -> case=0 casez=3 casex=3
sel=1zzz -> case=3 casez=3 casex=3
casez が無視するのは z(と ?)で、パターン側だけでなく式の側の z も無視します。だから z001 が 4'b1??? に一致しました。無視しないのは x のほうなんだ。1zzz では plain case まで一致していますが、これは ? が z そのものなので「ビットが厳密に一致した」結果で、don't care として働いたわけではありません。
| 文 | 無視する値 |
|---|---|
case | なし(4値の厳密比較。? は z という値) |
casez | z と ? |
casex | x と z と ? |
これが casex の怖さだよ。不明値が来たときに、黙ってどれかの分岐に入ってしまう。しかもその不明値は出力に現れないから、波形を見ても「正常に動いている」ように見えるんだ。不明値の追い方は検証実験4の実習にあるよ。
既知の入力だけ試すと、見つからない
casez を casex に書き換えた回路に、既知の入力16通りだけを与えるテストベンチを実行すると、こうなります。
PASS: partial check (16 known inputs, no x)
合格表示です。xを入れないかぎり、この2つは見分けられないんだ。※この簡易テストベンチは、サイト側の検証スクリプトで実行したもので、配布ZIPには入っていません。
わざと入れ替えて、テストの働きも確かめる
| 入れ替えた場所 | 結果 |
|---|---|
casez を casex に | check=50 x casez sel=xx01 actual=11 expected=00 |
casex を casez に | check=51 x casex sel=xx01 actual=00 expected=11 |
確認問題
don't care を使いたいとき、casez と casex のどちらを選ぶとよいでしょうか。
答え
x を無視させたくないなら casez です。casex は式の側の不明値まで don't care にしてしまうので、上で見たように「不明なまま分岐が選ばれる」状態を作ります。どうしても入力側の不明値を扱いたい場合は、その条件が起こりうるのかを先に仕様で確かめてね。また、? を書いても case では don't care にならない点にも注意です。合成時の扱いはツールにも依存するので、使うなら対象ツールの資料も確認しよう。
この後の節では、function と always の使い分け、代入記号、いろいろなステートメントを見ていきます。
(2)function と always 文
if 文などによる組み合わせ回路は function か always 文の中で記述します。どちらを使うのがよいでしょうか?双方のメリット・デメリットを比較します。
| function による組み合わせ回路 | always 文による組み合わせ回路 | |
|---|---|---|
| メリット | 組み合わせ回路と順序回路の区別が明確 | 記述量が若干少ない(余分な識別子が不要) 出力信号に直接代入できる |
| デメリット |
・引き数や戻り値のビット幅が呼び出し側と一致しないとき ・module 内の信号を引き数を通さず参照したとき → 動作の不具合を起こすが文法エラーにならない |
・ラッチを生成する危険がある ・一つの always 文で複数の出力を記述すると動作結果が正しくない |
- 設計資産の有効活用のために、どちらかに統一する(職場・プロジェクトで採用している方に合わせる)
functionを使う場合:引き数と戻り値のビット幅を入念にチェックするalways文を使う場合:イベント式と代入方法を入念にチェックする
reg と always 文は順序回路 / function は組み合わせ回路
順序回路(reg + always)
// カウンタ
reg [7:0] CNT;
always @( posedge CK ) begin
if ( DIN == 2'b00 )
DOUT <= 4'b0001;
else if ( DIN == 2'b01 )
DOUT <= 4'b0010;
// その他の条件
end
reg 宣言した信号はフリップフロップかラッチを含む順序回路になる
組み合わせ回路(function)
// デコーダ
function [3:0] DEC;
input [1:0] DIN;
case ( DIN )
2'h0: DEC = 4'b0001;
2'h1: DEC = 4'b0010;
2'h2: DEC = 4'b0100;
2'h3: DEC = 4'b1000;
default: DEC = 4'bxxxx;
endcase
endfunction
assign DOUT = DEC( DIN );
function で記述した回路は組み合わせ回路になる
always 文で記述した場合、出力信号に直接代入できるため、function に比べ記述量が少なくてすみます。
always 文(出力に直接代入)
// デコーダ
reg [3:0] DOUT;
always @( DIN ) begin
case ( DIN )
2'h0: DOUT <= 4'b0001;
2'h1: DOUT <= 4'b0010;
2'h2: DOUT <= 4'b0100;
2'h3: DOUT <= 4'b1000;
default: DOUT <= 4'bxxxx;
endcase
end
function(識別子 DEC が必要)
// デコーダ
function [3:0] DEC;
input [1:0] DIN;
case ( DIN )
2'h0: DEC = 4'b0001;
2'h1: DEC = 4'b0010;
2'h2: DEC = 4'b0100;
2'h3: DEC = 4'b1000;
default: DEC = 4'bxxxx;
endcase
endfunction
assign DOUT = DEC( DIN );
(3)function のデメリット1 — 戻り値のビット幅を誤った記述
2to4 デコーダを function で記述した場合のトラブル例です。
出力 DOUT は 4 ビットだが、function の戻り値 DEC を [2:0](3 ビット)で宣言してしまった例:
// function による 2to4 デコーダ
wire [1:0] DIN;
wire [3:0] DOUT;
function [2:0] DEC; // ← 本来は [3:0] が正しい
input [1:0] DIN;
begin
case ( DIN )
2'h0: DEC = 4'b0001;
2'h1: DEC = 4'b0010;
2'h2: DEC = 4'b0100;
2'h3: DEC = 4'b1000;
default: DEC = 4'bxxxx;
endcase
end
endfunction
assign DOUT = DEC( DIN );
- 最上位ビットが欠落し、DEC へは下位 3 ビットのみが出力される
- 結果として DOUT の最上位ビットは 0 に固定される
- 文法エラーにはならないため、問題の発見に手間取る
| 時刻 | DIN | DOUT(期待値) | DOUT(実際) |
|---|---|---|---|
| 0 | 0 | 0001 | 0001 ✅ |
| 1000 | 1 | 0010 | 0010 ✅ |
| 2000 | 2 | 0100 | 0100 ✅ |
| 3000 | 3 | 1000 | 0000 ❌(最上位ビット欠落) |
(4)function のデメリット2 — 引き数を通さず module 内の信号を参照
1 ビットの 2to1 セレクタを function で記述した場合のトラブル例です。
wire DIN0, DIN1, SEL, DOUT;
function SELECTOR;
input S;
begin
if ( S==1'b0 )
SELECTOR = DIN0; // ← 引き数ではなく module 内信号を直接参照
else
SELECTOR = DIN1;
end
endfunction
assign DOUT = SELECTOR( SEL );
- 文法エラーにはならない(function は module 内の信号を参照できてしまう)
- SEL が変化した場合は正しく動作するが、DIN0 や DIN1 が変化しても DOUT は変わらない
- 理由:引き数が変化しないと function が再評価されないため
❌ NG:引き数なし(DIN0/DIN1 を直接参照)
function SELECTOR;
input S;
begin
if ( S==1'b0 )
SELECTOR = DIN0;
else
SELECTOR = DIN1;
end
endfunction
assign DOUT = SELECTOR( SEL );
// SEL変化→OK、DIN0/DIN1変化→NG
✅ OK:すべての入力を引き数経由で渡す
function SELECTOR;
input D0, D1, S;
begin
if ( S==1'b0 )
SELECTOR = D0;
else
SELECTOR = D1;
end
endfunction
assign DOUT = SELECTOR( DIN0, DIN1, SEL );
(5)always 文のデメリット — 複数出力を同一 always 文で記述
8 ビットのフラグ付き加算回路(入力 A・B を加算した SUM と、SUM が 0 のとき 1 になる ZFLAG を出力)を例に解説します。
wire [7:0] A, B;
reg [7:0] SUM;
reg ZFLAG;
always @( A or B ) begin
SUM <= A + B;
if ( SUM==8'h00 )
ZFLAG <= 1'b1;
else
ZFLAG <= 1'b0;
end
- ノンブロッキング代入を使用した場合、always 文内で出力(SUM)を参照すると ZFLAG が 1 周期遅れて出力される
| 時刻 | A | B | SUM | ZFLAG(期待) | ZFLAG(実際) |
|---|---|---|---|---|---|
| 0 | 00 | 00 | 00 | 1 | 0 ❌ |
| 1000 | 0f | ff | 0e | 0 | 1 ❌ |
| 2000 | 01 | ff | 00 | 1 | 0 ❌ |
| 3000 | 01 | 7f | 80 | 0 | 1 ❌ |
wire [7:0] A, B;
reg [7:0] SUM;
reg ZFLAG;
always @( A or B )
SUM <= A + B;
always @( SUM )
if ( SUM==8'h00 )
ZFLAG <= 1'b1;
else
ZFLAG <= 1'b0;
出力 SUM は入力 A・B の回路、出力 ZFLAG は入力 SUM の回路のように always 文を分けることで正しく動作する。
(6)代入記号(= と <=)の使い分け
| 回路記述 | テストベンチ | |||
|---|---|---|---|---|
| 組み合わせ回路(always 文) | 順序回路(always 文) | initial 文・always 文・task で使用 | ||
| reg への代入 | 用途 | always 文 | always 文 | initial 文・always 文・task |
| 記号 | <= |
<= |
= |
|
| wire への代入 | 用途 | assign 文・function の戻り値代入などに使用 | ||
| 記号 | =(ここで <= を使うと文法エラー) |
|||
記述例
組み合わせ回路(reg + always + <=)
wire [7:0] A, B;
reg [7:0] SUM;
// 加算回路
always @( A or B )
SUM <= A + B;
順序回路(reg + always + <=)
wire CK, RES;
reg [2:0] Q;
// 3 ビットカウンタ
always @( posedge CK or posedge RES )
begin
if ( RES==1'b1 )
Q <= 3'h0;
else
Q <= Q + 3'h1;
end
テストベンチ(reg + = )
reg CK, RES;
// クロックの記述
always begin
CK = 0; #(STEP/2);
CK = 1; #(STEP/2);
end
// テスト入力
initial begin
RES = 0;
#STEP RES = 1;
#STEP RES = 0;
#(STEP*20)
$finish;
end
wire への代入(assign + =)
// 回路記述
assign DOUT = DEC( DIN ); // <= は文法エラー
// テストベンチ
wire CEB, OEB, WEB;
wire WRITE, READ;
// 読み出し/書き込みの内部信号
assign READ = (OEB==1'b0) & (CEB==1'b0);
assign WRITE = (WEB==1'b0) & (CEB==1'b0);
なお、業界全体では「組み合わせ回路は =(ブロッキング)、順序回路は <=(ノンブロッキング)」というスタイルも広く使われており、近年の実務ではむしろこちらが主流です(もふねこのnote教材はこちらの流儀で統一しています)。どちらの流儀でも論理合成の結果は同じになるので、職場やプロジェクトの規約に合わせてください🐾
もふねこ:
=(ブロッキング)と <=(ノンブロッキング)の使い分けは、初めのうちは絶対迷うよね🐾
シンプルに「フリップフロップ(順序回路)を作るときは <= を使う!」って覚えておけば、代入記号で迷う場面はぐっと減るよ!
(7)いろいろなステートメント
テストベンチの記述に役立ついくつかのステートメントを紹介します。これらは initial 文や task の中で実行できます。
| ステートメント | 構文 | 説明 |
|---|---|---|
repeat |
repeat ( <式> ) <ステートメント> |
固定回数のループ。<式> は繰り返し回数 |
forever |
forever <ステートメント> |
無限ループ |
wait |
wait ( <式> ) <ステートメント> |
式が真なら次の文を実行。偽ならステートメントを実行し、真になるのを待つ |
repeat — CK の立ち上がりを 20 回待つ
initial begin
// 初期化処理など
// CK の立ち上がりを 20 回待つ
repeat( 20 ) @( posedge CK );
// その後の処理
end
forever — クロック生成(always 文と同等)
initial // クロックの記述
forever begin
CK=1; #(STEP/2);
CK=0; #(STEP/2);
end
wait — BUSY 信号が 0 になるのを待つ
initial begin
#STEP BUSY = 1'b0;
// その他の初期化処理など
// BUSY が 0 になるまで待つ
wait( BUSY==1'b0 );
end
(8)修了判定
- 本文の中で紹介した
function記述(2to4 デコーダ)をシミュレーションして動作確認する - また、論理合成して、最上位ビットがどのように合成されているか、回路図で確認する
ファイル名:デコード回路 → dec2to4.v、テストベンチ → dec2to4_test.v
| 入力 | 出力 |
|---|---|
| DIN [1:0] | DOUT [3:0](1 ビットのみ "1"、残りは "0") |
Compiling source file "dec2to4.v"
Compiling source file "dec2to4_test.v"
0 DIN=0 DOUT=0001
1000 DIN=1 DOUT=0010
2000 DIN=2 DOUT=0100
3000 DIN=3 DOUT=0000 ← 戻り値ビット幅誤りのため最上位が欠落
4000 DIN=x DOUT=0xxx
End of simulation
DIN=3 のとき DOUT の期待値は 1000 だが、function の戻り値ビット幅を [2:0] と誤って記述した場合 0000 になる。論理合成の回路図でも最上位ビットが接続されていないことを確認できる。
実習で確かめる、つまずきの補足
casexが未知値まで一致させる理由
casex はcase式と各項目の両方にあるX/Zを比較から除外するよ。未初期化の入力が正常な条件に一致して、問題が見えなくなることがあるんだ。casez もZと ? を無視するので、未接続のZが紛れ込む場合に気をつけよう。
通常の case はX/Zも含めて一致を調べる。0/1の項目だけなら未知入力はdefaultへ進むけれど、defaultの出力を0にしただけでは「未知入力だった」という問題まで通知できない。TBで入力の未知値を検査するのが役立つよ。
Don't Careは「この条件ではどの出力でも仕様を満たす」という設計上の約束。代入省略、未初期化のX、安全な無効入力処理と混同しないでね。
📌 まとめ
- functionは計算をまとめる構文。reg + alwaysが組合せ回路か順序回路かは、イベント式と代入の網羅性で決まる
functionの落とし穴:戻り値のビット幅ミス、引き数なしの直参照alwaysの落とし穴:ノンブロッキング代入で出力を参照すると 1 周期遅延 → 出力ごとに always 文を分ける- 代入記号:回路記述の
regには<=、テストベンチのregには=、wireには必ず= - テストベンチ用ステートメント:
repeat(固定ループ)、forever(無限ループ)、wait(条件待ち)