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

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

概要: always文を使ったデコーダとエンコーダの記述を学び、if文・case文の使い方を理解する。

目標: 以下の回路を記述できる

  • always文を用いたデコーダ、エンコーダ
  • 等号演算、if文、case文を用いたデコーダ
  • casex、casez文を用いたデコーダ
  • if文、case文を用いたエンコーダ

修了判定:

  • 修了判定1:3to8デコーダをif文で記述する
  • 修了判定2:3to8デコーダをcase文で記述する
  • 修了判定3:8to3エンコーダをif文で記述する

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を指定することで、論理合成ツールは回路が最も小さくなるように合成します。
もふねこ

🐾 もふねこの現場メモ:
就職した学生から「7セグLEDがシミュレーションでは完璧に動くのに、実機だけ表示が化ける」という相談が来たことがあるんだ。原因は4ビット入力の10〜15を書かなかったこと。実機では電源投入時のノイズで「来ないはず」の入力が一瞬入り、意図しないラッチが表示を壊してしまう。defaultやelseは「保険」じゃなくて「必須」だよ🐾
※この罠の詳しい解説は noteのコラム で書いているよ🐾

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

📝 C2 まとめ: デコーダとエンコーダ 記述スタイル比較

記述スタイル特徴推奨場面
等号演算(assign)各出力ビットを独立したassign文で記述。ビット数増加で記述量増大入出力ビット数が少ない場合
if文(always)条件分岐で記述。ビット数増加で記述量増大条件が複雑なとき
case文(always)真理値表をそのまま記述可能。最もシンプル・読みやすいデコーダ・エンコーダ全般(推奨)
casex文(always)xビットを「どちらでもよい」として比較を省略できるドントケア条件がある回路記述