デコーダとエンコーダを書く

📌 このページの概要と目標

概要: 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まで確かめるほうです。

わざと壊して、テストの働きも確かめる

2026年10月26日 Windows / Icarus Verilog 13.0での実行結果
壊した場所結果
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]
000001
010010
100100
111000
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
💡 always文による組み合わせ回路の書き方
  • 出力信号を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文によるデコーダの特長
  • 真理値表をそのままcase文で記述するだけで完成
  • default:上記4つ以外の値(不定値xやzなど)のときの処理
  • defaultでdoutに不定値を代入 → 論理合成ツールがゲート数最小の回路を生成
  • 入出力ビット数が多くても記述量が少なく読みやすい(if文・等号演算より優れる)

4. casex文・casez文によるデコーダ

もふねこ

「0でも1でもどっちでもいいよ(Don't Care)」っていうときは、casex文が便利!🐾
xを使うと「ここはどちらでもいい」と伝えられるので、論理合成ツールが最適化できる余地が生まれるんだ。どれだけ小さくなるかはツールと制約しだいだから、効果は合成レポートで確かめてね。

case文の仲間としてcasex文とcasez文があります。

文動作
case1, 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]
000001
010001
100100
111000
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 と casez の使い分け
  • 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]
000100
001001
010010
100011
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
💡 else(デフォルト処理)について
  • 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
💡 case文によるエンコーダの特長 defaultはif文のelseと同様。真理値表以外の入力に対する出力を記述します。不定値xを指定すると「その入力では出力は任意」と伝えられるため、論理合成ツールが最適化に使える条件が増えます。どれだけ小さくなるかはツールと制約によるので、実際の効果は合成レポートで確認してください。
もふねこ

🐾 もふねこの現場メモ:
「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]
00000000001
00100000010
01000000100
01100001000
10000010000
10100100000
11001000000
11110000000
▶ 解答を見る
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
💡 casex文ではダメなの? 3to8デコーダは8つの入力パターンすべてが固有の出力を持つため、don't care(x)でまとめられる行がひとつもありません。casexで書いても結局8パターン全部を列挙することになり、普通のcase文とまったく同じです。casex/casezが活きるのは、応用実験1のプライオリティエンコーダのように「上位ビットが1なら下位ビットは見ない」という回路です。

修了判定3:8to3エンコーダをif文で記述する

設問: 8to3エンコーダを記述する。if文で記述。

din[7:0]dout[2:0]
00000001000
00000010001
00000100010
00001000011
00010000100
00100000101
01000000110
10000000111
▶ 解答を見る
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ビットを「どちらでもよい」として比較を省略できるドントケア条件がある回路記述