デコーダとエンコーダを書く
概要: always文を使ったデコーダとエンコーダの記述を学び、if文・case文の使い方を理解する。
目標: 以下の回路を記述できる
- always文を用いたデコーダ、エンコーダ
- 等号演算、if文、case文を用いたデコーダ
- casex、casez文を用いたデコーダ
- if文、case文を用いたエンコーダ
修了判定:
- 修了判定1:3to8デコーダをif文で記述する
- 修了判定2:3to8デコーダをcase文で記述する
- 修了判定3:8to3エンコーダをif文で記述する
まず実験:仕様にない入力を与えてみる
デコーダは「使う入力」より「使わない入力」でつまずきます。BCD(0〜9)を7セグメントへ変える回路で、10〜15を書かなかったらどうなるかを、動かせるサンプルで確かめます。条件の書き漏らしとラッチの関係は応用実験5でも扱っています。
7セグデコーダ2種と自動判定テストベンチをダウンロード(ZIP)
回路の仕様
- 入力は4ビットの
d、出力はセグメント7本({a,b,c,d,e,f,g}、1で点灯)です。 seg_holdはif (d <= 9)の中だけで代入し、elseを書いていません。seg_safeはcaseにdefaultを置き、10〜15では消灯します。- 0〜9の点灯パターンは2つとも同じです。違うのは使わない入力の扱いだけです。
回路本体 seg7_decoder.v を見る
`timescale 1ns/1ps
`default_nettype none
// Two BCD to seven-segment decoders that differ only in how they treat
// the inputs 10 to 15, which the specification does not use.
// seg_hold : no else / no default. The output keeps its previous value.
// seg_safe : every input is assigned. 10 to 15 blank the display.
// Segment order is {a,b,c,d,e,f,g}, active high.
module seg7_decoder (
input wire [3:0] d,
output reg [6:0] seg_hold,
output reg [6:0] seg_safe
);
always @* begin
if (d <= 4'd9) begin
case (d)
4'd0: seg_hold = 7'b1111110;
4'd1: seg_hold = 7'b0110000;
4'd2: seg_hold = 7'b1101101;
4'd3: seg_hold = 7'b1111001;
4'd4: seg_hold = 7'b0110011;
4'd5: seg_hold = 7'b1011011;
4'd6: seg_hold = 7'b1011111;
4'd7: seg_hold = 7'b1110000;
4'd8: seg_hold = 7'b1111111;
4'd9: seg_hold = 7'b1111011;
endcase
end
// no else: nothing is assigned for 10 to 15
end
always @* begin
case (d)
4'd0: seg_safe = 7'b1111110;
4'd1: seg_safe = 7'b0110000;
4'd2: seg_safe = 7'b1101101;
4'd3: seg_safe = 7'b1111001;
4'd4: seg_safe = 7'b0110011;
4'd5: seg_safe = 7'b1011011;
4'd6: seg_safe = 7'b1011111;
4'd7: seg_safe = 7'b1110000;
4'd8: seg_safe = 7'b1111111;
4'd9: seg_safe = 7'b1111011;
default: seg_safe = 7'b0000000;
endcase
end
endmodule
`default_nettype wire
テストベンチ tb_seg7_decoder.v を見る
`timescale 1ns/1ps
`default_nettype none
// The expected patterns are written as a plain table here, separately from
// the decoder's case statement, so a copied mistake cannot hide.
module tb_seg7_decoder;
reg [3:0] d;
wire [6:0] seg_hold, seg_safe;
reg [6:0] want [0:9];
integer checks = 0;
integer i;
seg7_decoder dut(.d(d), .seg_hold(seg_hold), .seg_safe(seg_safe));
task check;
input [6:0] actual, expected;
input [8*12-1:0] label;
begin
checks = checks + 1;
if (actual !== expected)
$fatal(1, "FAIL: check=%0d %0s d=%0d actual=%b expected=%b",
checks, label, d, actual, expected);
end
endtask
task drive;
input [3:0] value;
begin
d = value;
#1;
end
endtask
initial begin
$dumpfile("seg7_decoder.vcd");
$dumpvars(0, tb_seg7_decoder);
want[0] = 7'b1111110; want[1] = 7'b0110000; want[2] = 7'b1101101;
want[3] = 7'b1111001; want[4] = 7'b0110011; want[5] = 7'b1011011;
want[6] = 7'b1011111; want[7] = 7'b1110000; want[8] = 7'b1111111;
want[9] = 7'b1111011;
// 0 to 9: both decoders follow the specification.
for (i = 0; i <= 9; i = i + 1) begin
drive(i[3:0]);
check(seg_hold, want[i], "hold 0-9");
check(seg_safe, want[i], "safe 0-9");
end
// The same unused input after two different digits.
drive(4'd9); drive(4'd10);
check(seg_hold, want[9], "after 9"); // keeps the pattern for 9
check(seg_safe, 7'b0000000, "safe after 9");
$display(" d=10 after 9: seg_hold=%b seg_safe=%b", seg_hold, seg_safe);
drive(4'd5); drive(4'd10);
check(seg_hold, want[5], "after 5"); // keeps the pattern for 5
check(seg_safe, 7'b0000000, "safe after 5");
$display(" d=10 after 5: seg_hold=%b seg_safe=%b", seg_hold, seg_safe);
// 11 to 15 behave the same way.
for (i = 11; i <= 15; i = i + 1) begin
drive(4'd3);
drive(i[3:0]);
check(seg_hold, want[3], "held pattern");
check(seg_safe, 7'b0000000, "safe blank");
end
$display("PASS: seg7 decoder coverage (%0d checks)", checks);
$finish;
end
initial begin
#1000;
$fatal(1, "FAIL: testbench timeout");
end
endmodule
`default_nettype wire
実行方法
iverilog -g2012 -Wall -s tb_seg7_decoder -o sim.vvp seg7_decoder.v tb_seg7_decoder.v
vvp sim.vvp
テストベンチは、同じ入力10を「9のあと」と「5のあと」の2回与えます。Windows / Icarus Verilog 13.0で2026年10月26日に実行した結果です。
d=10 after 9: seg_hold=1111011 seg_safe=0000000
d=10 after 5: seg_hold=1011011 seg_safe=0000000
PASS: seg7 decoder coverage (34 checks)
ここでのPASSは「seg_hold が正しい」という意味ではないよ。0〜9では仕様どおりで、10〜15では前の値を保つ——その振る舞いを確かに観測できた、という意味なんだ。
同じ入力なのに、出力が違う
seg_hold の出力は、直前に表示していた数字のパターンそのものです。9のあとなら9の形、5のあとなら5の形が残ります。同じ入力で出力が変わるということは、この回路が過去の入力を覚えているということ。組合せ回路のつもりが、値を保つ回路(ラッチ)になっているんだ。
ここが大事なところだよ。この現象はRTLシミュレーションで観測できる。実機でしか分からない故障ではないんだ。見逃すとしたら、まず「仕様にない入力を試していない」。試していても、その入力で何を出すべきかを期待値として書いていなければ、やはり素通りする。入力の選び方と期待値の決め方、両方そろって初めて見つかるんだ。
seg_safe のほうは、10〜15でいつでも消灯になります。どちらが正しいかは仕様しだいだよ。消灯、エラー表示、直前の値を保つ、上位で到達しないことを保証する——どれも別の約束だから、決めて書いておこう。
0〜9だけ試すと、見つからない
同じ回路に対して、0〜9の10通りだけを与えるテストベンチを実行するとこうなります。
PASS: partial check (10 of 16 input values)
合格表示です。回路は何も直していません。4ビット入力なら全16通りを試せるのに、仕様に出てくる値だけで止めてしまうと、残り6通りの振る舞いは誰も確かめていないことになるよ。※この簡易テストベンチは、サイト側の検証スクリプトで実行したもので、配布ZIPには入っていません。ZIPに入っているのは10〜15まで確かめるほうです。
わざと壊して、テストの働きも確かめる
| 壊した場所 | 結果 |
|---|---|
seg_safe の default を消す | check=22 safe after 9 d=10 actual=1111011 expected=0000000 |
| 6の点灯パターンを1ビット間違える | check=14 safe 0-9 d=6 actual=1111111 expected=1011111 |
期待値はテストベンチ側に表として書き、回路の case とは別に用意しています。同じ書き間違いを両方へ写してしまうと、比べても気づけないからなんだ。
確認問題
エンコーダで、入力が複数同時に1になったときの出力は、どう決めておけばよいでしょうか。
答え
仕様で決めます。よくあるのは「上位の入力を優先する」で、これを形にしたものが優先順位エンコーダです(応用実験1)。決めずに書くと、記述の順序や if の並びでたまたま決まった動作になり、読む人には意図か偶然か区別できません。テストでも、同時に1になる入力の組合せを試して、決めた優先順位どおりかを確かめてね。このページの5章・6章のエンコーダも、その目で読み直してみよう。
この後の節では、等号演算・if文・case文・casex文によるデコーダと、エンコーダの書き方を順に見ていきます。
1. 等号演算によるデコーダ
ここでは「デコーダ」と「エンコーダ」という、信号を翻訳する回路を作っていくよ🐾
前回のセレクタと同じで色々な書き方があるから、どれが一番読みやすいか考えながら見てみてね!
==(等号演算子)は、2つの値を比較し、一致すれば1(真)、不一致なら0(偽)を返す1ビットの値です。この性質を利用してデコーダを記述できます。
2to4デコーダ 真理値表
| din[1:0] | dout[3:0] |
|---|---|
| 00 | 0001 |
| 01 | 0010 |
| 10 | 0100 |
| 11 | 1000 |
module decoder_cond( din, dout );
input [1:0] din;
output [3:0] dout;
assign dout[0] = (din==2'b00);
assign dout[1] = (din==2'b01);
assign dout[2] = (din==2'b10);
assign dout[3] = (din==2'b11);
endmodule
- 入出力のビット数が少ない場合は簡潔に記述できる
- 入出力のビット数が多くなると記述量が増え、読みにくくなる
2. if文によるデコーダ
等号演算によるデコーダと同じ動作を、always文+if文で記述します。always文を使った組み合わせ回路では、出力信号をreg型として宣言し、センシティビティリストに入力信号を指定します。
module decoder_if( din, dout );
input [1:0] din;
output [3:0] dout;
reg [3:0] dout;
always @( din )
begin
if ( din==2'b00 )
dout = 4'b0001;
else if ( din==2'b01 )
dout = 4'b0010;
else if ( din==2'b10 )
dout = 4'b0100;
else
dout = 4'b1000;
end
endmodule
- 出力信号をreg型として宣言する
always @(din):括弧内の信号(din)が変化したときにbegin〜endを実行- 代入にはブロッキング代入(=)を使う(「組み合わせ回路は
=、順序回路は<=」が業界主流のセオリー。なお本サイトの応用・合成ノートには<=で書いた例も残っているが、どちらでも合成結果は同じ。詳しくは応用実験6のまとめを参照) - 入出力ビット数が多くなると記述量が増え読みにくくなる(等号演算と同様)
3. case文によるデコーダ
if文によるデコーダと比較して、always文のbegin〜endの間だけが異なります。case文は真理値表をそのまま記述できるため、最もシンプルで読みやすい記述方法です。
module decoder_case( din, dout );
input [1:0] din;
output [3:0] dout;
reg [3:0] dout;
always @( din )
begin
case ( din )
2'b00: dout = 4'b0001;
2'b01: dout = 4'b0010;
2'b10: dout = 4'b0100;
2'b11: dout = 4'b1000;
default: dout = 4'bxxxx;
endcase
end
endmodule
- 真理値表をそのままcase文で記述するだけで完成
default:上記4つ以外の値(不定値xやzなど)のときの処理- defaultでdoutに不定値を代入 → 論理合成ツールがゲート数最小の回路を生成
- 入出力ビット数が多くても記述量が少なく読みやすい(if文・等号演算より優れる)
4. casex文・casez文によるデコーダ
「0でも1でもどっちでもいいよ(Don't Care)」っていうときは、casex文が便利!🐾
xを使うと「ここはどちらでもいい」と伝えられるので、論理合成ツールが最適化できる余地が生まれるんだ。どれだけ小さくなるかはツールと制約しだいだから、効果は合成レポートで確かめてね。
case文の仲間としてcasex文とcasez文があります。
| 文 | 動作 |
|---|---|
case | 1, 0, 不定値x, ハイインピーダンスz のすべてを一致比較 |
casex | 比較する値にxまたはzを使用すると、そのビットの比較を行わず一致とみなす |
casez | 比較する値にzを使用すると、そのビットの比較を行わず一致とみなす |
casexの使用例
din[1]が0のとき、din[0]の値にかかわらずdoutは4'b0001とする場合:
真理値表(din[1]=0 のとき din[0] は無関係)
| din[1:0] | dout[3:0] |
|---|---|
| 00 | 0001 |
| 01 | 0001 |
| 10 | 0100 |
| 11 | 1000 |
module decoder_casex( din, dout );
input [1:0] din;
output [3:0] dout;
reg [3:0] dout;
always @( din )
begin
casex ( din )
2'b0x: dout = 4'b0001; // din[1]=0 ならビット0は無視
2'b10: dout = 4'b0100;
2'b11: dout = 4'b1000;
default: dout = 4'bxxxx;
endcase
end
endmodule
- casex:don't care(x)をそのまま書けるため教科書の例によく登場する。ただし入力側の不定値(x)まで一致扱いになり、バグを隠す危険がある
- casez:z(および ? 記法)のみを don't care として扱う。実務で don't care が必要な場合はこちらが推奨(詳しくは応用実験1の「もふねこの現場メモ」を参照してね🐾)
5. if文によるエンコーダ
エンコーダはデコーダとは逆で、入力ビット数が出力ビット数より多くなります。ここでは入力4ビット・出力2ビットの4to2エンコーダをif文で記述します。
このエンコーダはdinの「1になる場所(ビット位置)」を出力doutで表します。
4to2エンコーダ 真理値表
| din[3:0] | dout[1:0] |
|---|---|
| 0001 | 00 |
| 0010 | 01 |
| 0100 | 10 |
| 1000 | 11 |
module encoder_if( din, dout );
input [3:0] din;
output [1:0] dout;
reg [1:0] dout;
always @( din )
begin
if ( din == 4'b0001 )
dout = 2'b00;
else if ( din == 4'b0010 )
dout = 2'b01;
else if ( din == 4'b0100 )
dout = 2'b10;
else if ( din == 4'b1000 )
dout = 2'b11;
else
dout = 2'bxx;
end
endmodule
- 4to2エンコーダで有効な入力は4通り(0001, 0010, 0100, 1000)のみ
- それ以外の12通りの入力値やx/z入力はelse以降が実行される
- elseでdoutに不定値xを代入 → 論理合成ツールが最小回路規模で合成
- 00や11などの固定値を代入することも可能だが、回路サイズ最小化は期待できない
6. case文によるエンコーダ
if文によるエンコーダと比較して、always文のbegin〜endの間のみが異なります。case文によるエンコーダも、真理値表をそのままcase文で記述すれば完成です。
module encoder_case( din, dout );
input [3:0] din;
output [1:0] dout;
reg [1:0] dout;
always @(din)
begin
case(din)
4'b0001: dout = 2'b00;
4'b0010: dout = 2'b01;
4'b0100: dout = 2'b10;
4'b1000: dout = 2'b11;
default: dout = 2'bxx;
endcase
end
endmodule
🐾 もふねこの現場メモ:
「10〜15は来ないはず」と考えて書かずに済ませると、来たときの動きが決まらないんだ。しかも「来ないはず」が成り立つかは、入力をどこから受けるかで変わるよ。複数のビットが同時に変わるとき、ビットごとの到達時刻のずれで途中のコードを一瞬通ることがある。たとえば7(0111)から8(1000)は4ビット全部が変わるので、変化の順しだいで10〜15を一瞬経由しうる。逆に8(1000)から1(0001)は変わるビットが2本だけで、途中に出るのは9か0だから10〜15は通らない。来ないことを上流で保証するのか、来ても安全な出力にするのかを、仕様として決めてから書こう🐾
※書かなかったときに前の値が残る様子は このページの実習 で実際に見られるよ。仕組みは 応用実験5、無効入力の決め方は このページの補足 にあるよ🐾
7. 修了判定(練習問題)
修了判定1:3to8デコーダをif文で記述する
設問: 3to8出力デコーダを記述する。if文で記述。
| din[2:0] | dout[7:0] |
|---|---|
| 000 | 00000001 |
| 001 | 00000010 |
| 010 | 00000100 |
| 011 | 00001000 |
| 100 | 00010000 |
| 101 | 00100000 |
| 110 | 01000000 |
| 111 | 10000000 |
▶ 解答を見る
module decoder_if( din, dout );
input [2:0] din;
output [7:0] dout;
reg [7:0] dout;
always @( din )
begin
if ( din == 3'b000 )
dout = 8'b00000001;
else if ( din == 3'b001 )
dout = 8'b00000010;
else if ( din == 3'b010 )
dout = 8'b00000100;
else if ( din == 3'b011 )
dout = 8'b00001000;
else if ( din == 3'b100 )
dout = 8'b00010000;
else if ( din == 3'b101 )
dout = 8'b00100000;
else if ( din == 3'b110 )
dout = 8'b01000000;
else
dout = 8'b10000000;
end
endmodule
修了判定2:3to8デコーダをcase文で記述する
設問: 3to8出力デコーダを記述する。case文で記述。(真理値表は修了判定1と同じ)
▶ 解答を見る
module decoder_case3to8( din, dout );
input [2:0] din;
output [7:0] dout;
reg [7:0] dout;
always @( din )
begin
case ( din )
3'b000: dout = 8'b00000001;
3'b001: dout = 8'b00000010;
3'b010: dout = 8'b00000100;
3'b011: dout = 8'b00001000;
3'b100: dout = 8'b00010000;
3'b101: dout = 8'b00100000;
3'b110: dout = 8'b01000000;
3'b111: dout = 8'b10000000;
default: dout = 8'bxxxxxxxx;
endcase
end
endmodule
修了判定3:8to3エンコーダをif文で記述する
設問: 8to3エンコーダを記述する。if文で記述。
| din[7:0] | dout[2:0] |
|---|---|
| 00000001 | 000 |
| 00000010 | 001 |
| 00000100 | 010 |
| 00001000 | 011 |
| 00010000 | 100 |
| 00100000 | 101 |
| 01000000 | 110 |
| 10000000 | 111 |
▶ 解答を見る
module encoder_if( din, dout );
input [7:0] din;
output [2:0] dout;
reg [2:0] dout;
always @(din)
begin
if ( din == 8'h1 )
dout = 3'h0;
else if ( din == 8'h2 )
dout = 3'h1;
else if ( din == 8'h4 )
dout = 3'h2;
else if ( din == 8'h8 )
dout = 3'h3;
else if ( din == 8'h10 )
dout = 3'h4;
else if ( din == 8'h20 )
dout = 3'h5;
else if ( din == 8'h40 )
dout = 3'h6;
else if ( din == 8'h80 )
dout = 3'h7;
else
dout = 3'hxx;
end
endmodule
実習で確かめる、つまずきの補足
無効入力と優先順位を仕様にする
4→2エンコーダでone-hot入力だけを許すなら、有効なのは 0001,0010,0100,1000 の4通りだよ。全0と複数ビット1を「無効」としてvalidを0にする設計と、最上位の1を優先する設計は違う回路なんだ。
always @* begin
valid = 1'b1;
case (request)
4'b0001: code = 2'd0;
4'b0010: code = 2'd1;
4'b0100: code = 2'd2;
4'b1000: code = 2'd3;
default: begin code = 2'd0; valid = 1'b0; end
endcase
end
if/else-ifは複数の条件が同時に真なら先の条件を選ぶ。一方、x==0 と x==1 のように互いに排他的な条件では、合成が優先構造を取り除けることもあるよ。「ifだから必ず遅い」とは言えない。機能と優先順位を揃えたうえで合成レポートを比べよう。
7セグ表示も、入力が4ビットなら10〜15の動作が必要だよ。消灯を既定値にする場合は、基板の極性に合った消灯パターンを全ビットへ代入する。0〜9だけの試験で済ませず、無効値から有効値へ戻る順序も試してね。
📝 C2 まとめ: デコーダとエンコーダ 記述スタイル比較
| 記述スタイル | 特徴 | 推奨場面 |
|---|---|---|
| 等号演算(assign) | 各出力ビットを独立したassign文で記述。ビット数増加で記述量増大 | 入出力ビット数が少ない場合 |
| if文(always) | 条件分岐で記述。ビット数増加で記述量増大 | 条件が複雑なとき |
| case文(always) | 真理値表をそのまま記述可能。最もシンプル・読みやすい | デコーダ・エンコーダ全般(推奨) |
| casex文(always) | xビットを「どちらでもよい」として比較を省略できる | ドントケア条件がある回路記述 |