Verilog記述テクニック集
(1)学習の概要と目標
HDL にも知っていると便利な「コツ」があります。ここではトラブル例を通じて function と always 文の特徴を深く理解し、代入記号の使い分けといくつかのステートメントを学びます。
- トラブル例を参考に、
functionとalways文の特徴を理解する - 代入記号の使い分けと、いくつかのステートメントを学ぶ
- 組み合わせ回路記述での
functionとalways文のメリット/デメリットを理解し説明できる - 代入記号を状況に応じて使い分けできる
wait、repeat、foreverの各ステートメントを理解し説明できる
シミュレーションと論理合成により 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);
もふねこ:
=(ブロッキング)と <=(ノンブロッキング)の使い分けは、初めのうちは絶対迷うよね🐾
シンプルに「フリップフロップ(順序回路)を作るときは <= を使う!」って覚えておけば、大半のバグは防げるから安心してね!
(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 になる。論理合成の回路図でも最上位ビットが接続されていないことを確認できる。
📌 まとめ
functionは組み合わせ回路、reg + alwaysは順序回路 — 区別が明確functionの落とし穴:戻り値のビット幅ミス、引き数なしの直参照alwaysの落とし穴:ノンブロッキング代入で出力を参照すると 1 周期遅延 → 出力ごとに always 文を分ける- 代入記号:回路記述の
regには<=、テストベンチのregには=、wireには必ず= - テストベンチ用ステートメント:
repeat(固定ループ)、forever(無限ループ)、wait(条件待ち)