ゲート回路とセレクタを書く

📌 このページについて ゲート回路は実際の回路記述では使いませんが、HDLの基礎として学びます。セレクタは4種類の記述スタイルを比較しながら、それぞれの特徴を理解しましょう。

まず実験:選ばれなかった入力はどうなる?

C言語の if は、通らなかった側の処理が「起きなかった」ことになります。Verilogの条件分岐は違って、選ばれなかった側も回路として用意され、選択信号がどれを通すかを切り替える形になります。ここで扱う4to1セレクタはその典型です。ただし、どんな if もそのまま同じ形になるわけではなく、条件の書き方によっては優先順位のついた構造になります(応用実験1)。この違いを、動かせるサンプルで確かめます。

4to1セレクタ4種と自動判定テストベンチをダウンロード(ZIP)

回路の仕様

  • データ入力は d0〜d3(各1ビット)、選択信号は sel[1:0] です。
  • このページの4つの記述スタイル(if連鎖/case/ビット選択/条件演算子ネスト)を1つのモジュールに並べています。
  • 4つの出力は、どの入力の組合せでも d[sel] と一致するはずです。
4つの書き方を並べた回路 sel4to1_styles.v を見る
`timescale 1ns/1ps
`default_nettype none
// The same 4-to-1 selector written four ways, matching the styles on the
// C1 page. All four must behave identically for every input combination.
module sel4to1_styles (
    input  wire       d0,
    input  wire       d1,
    input  wire       d2,
    input  wire       d3,
    input  wire [1:0] sel,
    output reg        y_if,
    output reg        y_case,
    output wire       y_assign,
    output wire       y_ternary
);
    // style 1: if chain
    always @* begin
        if (sel == 2'd0)      y_if = d0;
        else if (sel == 2'd1) y_if = d1;
        else if (sel == 2'd2) y_if = d2;
        else                  y_if = d3;
    end

    // style 2: case
    always @* begin
        case (sel)
            2'd0: y_case = d0;
            2'd1: y_case = d1;
            2'd2: y_case = d2;
            default: y_case = d3;
        endcase
    end

    // style 3: bit select on a packed vector
    wire [3:0] bus = {d3, d2, d1, d0};
    assign y_assign = bus[sel];

    // style 4: nested conditional operator
    assign y_ternary = (sel == 2'd0) ? d0 :
                       (sel == 2'd1) ? d1 :
                       (sel == 2'd2) ? d2 : d3;
endmodule
`default_nettype wire
テストベンチ tb_sel4to1_styles.v を見る
`timescale 1ns/1ps
`default_nettype none
// 16 data combinations x 4 select values = 64 patterns, four styles each.
// Then the inputs that are not selected are driven to x on purpose.
module tb_sel4to1_styles;
    reg [3:0] d;
    reg [1:0] sel;
    wire y_if, y_case, y_assign, y_ternary;
    reg expected;
    integer checks = 0;
    integer i, s;

    sel4to1_styles dut(.d0(d[0]), .d1(d[1]), .d2(d[2]), .d3(d[3]), .sel(sel),
                       .y_if(y_if), .y_case(y_case),
                       .y_assign(y_assign), .y_ternary(y_ternary));

    task check;
        input actual;
        input want;
        input [8*10-1:0] style;
        begin
            checks = checks + 1;
            if (actual !== want)
                $fatal(1, "FAIL: check=%0d %0s d=%b sel=%0d actual=%b expected=%b",
                       checks, style, d, sel, actual, want);
        end
    endtask

    task check_all;
        begin
            check(y_if, expected, "if");
            check(y_case, expected, "case");
            check(y_assign, expected, "assign");
            check(y_ternary, expected, "ternary");
        end
    endtask

    initial begin
        $dumpfile("sel4to1_styles.vcd");
        $dumpvars(0, tb_sel4to1_styles);

        for (i = 0; i < 16; i = i + 1) begin
            for (s = 0; s < 4; s = s + 1) begin
                d = i[3:0];
                sel = s[1:0];
                #1;
                expected = d[sel];
                check_all;
                #1;
            end
        end

        // Only the selected input is known. The others are left unknown.
        d = 4'bxxx1; sel = 2'd0; #1; expected = 1'b1; check_all;
        d = 4'bxx0x; sel = 2'd1; #1; expected = 1'b0; check_all;
        d = 4'bx1xx; sel = 2'd2; #1; expected = 1'b1; check_all;
        d = 4'b0xxx; sel = 2'd3; #1; expected = 1'b0; check_all;
        $display("  sel=3, d=0xxx -> y_if=%b y_case=%b y_assign=%b y_ternary=%b",
                 y_if, y_case, y_assign, y_ternary);

        $display("PASS: selector styles (%0d checks)", checks);
        $finish;
    end

    initial begin
        #5000;
        $fatal(1, "FAIL: testbench timeout");
    end
endmodule
`default_nettype wire

実行方法

iverilog -g2012 -Wall -s tb_sel4to1_styles -o sim.vvp sel4to1_styles.v tb_sel4to1_styles.v
vvp sim.vvp

データ4本の16通り × sel の4通り=64パターンで、4つの出力をそれぞれ d[sel] と比べます。そのあと、選ばれない入力だけを不定値 x にして4通りを追加で確かめます。Windows / Icarus Verilog 13.0で2026年10月26日に実行した結果です。

  sel=3, d=0xxx -> y_if=0 y_case=0 y_assign=0 y_ternary=0
PASS: selector styles (272 checks)

読み取れること

  1. 4つの書き方は同じ機能。 sel が0〜3に確定している64パターンすべてで一致しました。このページの8章で並べている4つの記述スタイルが、機能として同じであることを実行で確かめた形です。
  2. 選ばれない入力が不定値でも、出力は確定する。 d=0xxx で sel=3 のとき、他の3本が x でも出力は0です。選択信号が確定していれば、通る経路だけが出力を決めるからです。

ここで注意したいのは、2つ目が「選ばれない入力は回路に無い」という意味ではないことなんだ。4本ぶんの経路は回路として存在していて、sel がどれを通すかを決めているだけだよ。このページの補足にある「選択しない入力も回路の一部」というのは、そういう意味なんだ。

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

2026年10月26日 Windows / Icarus Verilog 13.0での実行結果
壊した場所結果
if連鎖の最後の else を落とすcheck=77 if d=0100 sel=3 actual=1 expected=0
case の既定値を d2 に取り違えるcheck=78 case d=0100 sel=3 actual=1 expected=0
ビット選択の並び順を逆にするcheck=19 assign d=0001 sel=0 actual=0 expected=1

1つ目が、条件の書き漏らしだよ。sel=3 のときにどこにも代入されないので、出力は前の値を保ちます。実際、直前の sel=2 のときの値(d2=1)がそのまま残って、期待値の d3=0と食い違いました。これが意図しないラッチで、応用実験5で詳しく扱っているよ。

sel=0 だけ試すと、見つからない

同じ壊れた回路に対して、sel=0 のまま16通りのデータだけを与えるテストベンチを実行すると、こうなります。

PASS: partial check (16 of 64 patterns, sel=0 only)

合格表示です。選択信号を動かさないテストでは、他の経路が壊れていても分かりません。セレクタを試すときは、データだけでなく選択信号の全パターンを回してね。※この簡易テストベンチは、サイト側の検証スクリプトで実行したもので、配布ZIPには入っていません。ZIPに入っているのは全64パターンを回すほうです。

確認問題

sel 自体が不定値 x になったら、case 文で書いたセレクタの出力はどうなるでしょうか。

答え

2'd0〜2'd2 のどれにも一致せず、default があればその代入が実行されます。同じサンプルで sel を 2'bxx にして実行すると、こうなりました(2026年10月27日 Icarus Verilog 13.0)。

sel=xx   d=0101 -> if=0 case=0 assign=x ternary=x
sel=xx   d=1010 -> if=1 case=1 assign=x ternary=x

if連鎖とcaseは、最後の else と default が d3 を代入しているので、d3 の値がそのまま出ます。ビット選択と条件演算子は x になります。つまり sel が確定していないときは、4つの書き方は同じになりません。上の64パターンで一致したのは、sel が0〜3に確定している範囲の話なんだ。なお、このページの5章のcase記述は default に 1'bx を置いていて、この実習の d3 とは別の選び方だよ。どちらにするかも仕様で決めることになる。選択信号をリセットで確定させておくことと、既定の分岐に何を置くかを決めておくこと、両方が効いてくるよ。不定値の扱いは検証実験4の実習で確かめられます。

この後の節では、プリミティブゲートとassign文の書き方、そして4つのセレクタ記述スタイルを順に見ていきます。

1. プリミティブゲートの記述

もふねこ

ここからは「Cシリーズ(回路ノート)」!実際の論理回路をどうやってVerilogで書くか学んでいくよ🐾
まずは基本中の基本、「ゲート回路」と「セレクタ(選択回路)」の4つの書き方を比べてみよう!

VerilogHDLにはあらかじめインバータ・AND・ORなどの基本ゲート回路が用意されています。これをプリミティブゲートと呼びます。定義や宣言なしにそのまま使えます。

書式

ゲートタイプ  インスタンス名 (出力, 入力1, 入力2, 入力3, 追加の入力);

// 例: NOTゲート
not n1 (out_not, in0);

// インスタンス名は省略可能
not (out_not, in0);
項目内容
ゲートタイプ予約語(not / and / or / nand / nor / xor など)
インスタンス名任意の名前(省略可。ゲートタイプと同一名は不可)
ポートリスト第1引数が出力、以降が入力(順番接続のみ、名前接続は不可)。and/or系のように出力が1つのゲートの場合で、not/buf は出力を複数書けて最後の端子が入力になる
⚠️ プリミティブゲートの制約
  • ゲートタイプは予約語のため、インスタンス名として使用不可
  • ポート接続は順番接続のみ(名前による接続は使えない)
  • インスタンス名の省略は可(モジュール呼び出しとの違い)

2. プリミティブゲートの種類

ゲートタイプ論理入力数記述例
notNOT(インバータ)1not n1 (out, in);
andAND2以上and a1 (out, in0, in1);
orOR2以上or o1 (out, in0, in1);
nandNAND2以上nand na1 (out, in0, in1);
norNOR2以上nor nr1 (out, in0, in1);
xorXOR(排他的OR)2以上xor x1 (out, in0, in1);
xnorXNOR2以上xnor xn1 (out, in0, in1);
💡 入力本数が変わってもゲートタイプ名は同じ 2入力ANDも3入力ANDも and です。入力信号の数だけポートリストに追加します。 論理合成対象のプリミティブは上記7種類が主です。その他のプリミティブ(bufif・notif等)は論理合成対象外でテストベンチ向けです。

3. assign文によるゲート記述

プリミティブゲートと同等の回路を、assign文+ビット演算子で記述できます。実際の設計ではこちらの方が一般的です。

C1 図1:2入力ANDゲートの回路図。in0とin1がANDに入り、その出力がout_and2として出る。
C1 図1:2入力ANDゲートの回路図。横に長い図はスクロールして確認できます。
// 1入力: NOT
assign out_not  = ~in0;

// 2入力: AND
assign out_and2 = in0 & in1;

// 2入力: OR
assign out_or2  = in0 | in1;

// 2入力: NAND
assign out_nand = ~(in0 & in1);

// 2入力: NOR
assign out_nor  = ~(in0 | in1);

// 2入力: XOR
assign out_xor  = in0 ^ in1;

// 3入力: AND
assign out_and3 = in0 & in1 & in2;
💡 プリミティブ vs assign文 どちらも同じ論理を表し、ふつうは合成しても違いは出ませんが、最終的な回路はツールと制約しだいです。assign文の方が可読性が高く、名前接続も使えるため、実際の設計ではassign文が推奨されます。

4. 4to1セレクタ: スタイル1 — function + if文

4本の信号(din[3:0])から選択信号(sel[1:0])で1本を選択するセレクタの記述方法を4種類紹介します。

C1 図2:4to1セレクタのブロック図。4ビットのdin 4bitと2ビットのsel 2bitがMUX4to1に入り、選ばれた1ビットがdout 1bitとして出る。
C1 図2:4to1セレクタのブロック図。横に長い図はスクロールして確認できます。
module MUX4to1 (din, sel, dout);
    input  [3:0] din;   // 4ビット入力(各ビットを個別に選択)
    input  [1:0] sel;   // 2ビット選択信号
    output       dout;

    // functionの定義
    function mux;
        input [3:0] din;
        input [1:0] sel;
        begin
            if      (sel == 2'd0) mux = din[0];
            else if (sel == 2'd1) mux = din[1];
            else if (sel == 2'd2) mux = din[2];
            else                  mux = din[3];
        end
    endfunction

    assign dout = mux(din, sel);  // functionを呼び出してdoutに代入
endmodule
sel値選択される信号
2'b00 (0)din[0]
2'b01 (1)din[1]
2'b10 (2)din[2]
2'b11 (3)din[3]

5. 4to1セレクタ: スタイル2 — function + case文

if文の代わりにcase文を使って同じセレクタを記述します。

function mux;
    input [3:0] din;
    input [1:0] sel;
    begin
        case (sel)
            2'd0: mux = din[0];
            2'd1: mux = din[1];
            2'd2: mux = din[2];
            2'd3: mux = din[3];
            default: mux = 1'bx;  // sel が不定値・Zのとき不定出力
        endcase
    end
endfunction

assign dout = mux(din, sel);
💡 if文 vs case文 case文は真理値表と1対1で対応するため、デコーダやセレクタの記述に適しています。defaultは不定値/ハイインピーダンス入力時の動作を明示するために必ず記述しましょう。

6. 4to1セレクタ: スタイル3 — assign文(ビット選択)

functionを使わず、assign文1行で記述できるシンプルな方法です。

assign dout = din[sel];
⚠️ 使用条件(以下の3つをすべて満たすとき限定)
  1. 出力 dout のビット幅が1ビット
  2. 選択対象 din のビット幅が2の累乗(2, 4, 8, 16…)
  3. selの値とdinのビット番号が値のまま対応(sel=0→din[0]、sel=1→din[1]…)
3つの条件がすべて揃ったときのみ使用できます。動作は他の記述方法と同一です。

7. 4to1セレクタ: スタイル4 — 条件演算子ネスト

条件演算子(? :)を3段ネストして4to1セレクタを記述します。

assign dout = (sel == 2'd0) ? din[0] :
              (sel == 2'd1) ? din[1] :
              (sel == 2'd2) ? din[2] :
                              din[3];
⚠️ 条件演算子ネストの注意 条件演算子をネストしたセレクタは、論理合成時に他の記述方法より回路規模が大きくなる場合があります。シンプルな2to1セレクタには向いていますが、多入力セレクタにはcase文を使うことを推奨します。

8. 4種類の記述方法まとめ

もふねこ

セレクタの書き方だけで4種類もあったね!🐾
実務では、見やすくて真理値表と対応しやすい「case文」を使うのが一番おすすめだよ!

実習で確かめる、つまずきの補足

選択しない入力も回路の一部

2入力MUXは Q = (~SEL & D0) | (SEL & D1) で表せるよ。合成では機能と対象資源に合わせて最適化されるから、ifの各分岐が必ず独立した同じ形の回路として残るわけではないんだ。

組合せ回路でSEL=0の代入を省くと、Qの前値を保持する動作になる。if (sel) q = d1; else q = d0; または完全な三項演算にして、選ばない場合の値も定義しよう。入力が変わる途中のグリッチと、条件漏れによる保持は別の現象として切り分けてね。

📝 C1 まとめ: 4to1セレクタ 記述スタイル比較

スタイル記述量可読性制約推奨場面
function + if文多め○なし条件が複雑な場合
function + case文多め◎(真理値表と対応)defaultを忘れずにデコーダ・セレクタ全般
assign(ビット選択)1行◎(最も簡潔)出力1ビット・din幅2の累乗・sel値直対応のとき限定条件が揃う場合のみ
条件演算子ネスト少なめ△(ネストが深い)回路規模が大きくなる可能性2to1程度まで

9. 修了判定:2ビット 2to1セレクタの記述

⚠️ 修了判定 設問:A(2ビット)かB(2ビット)を、SEL(1ビット)によって選択し、Y(2ビット)に出力する2to1セレクタを、4種類の記述スタイル(プリミティブゲート、if文、case文、条件演算子)で記述せよ。
(SEL=0のときA、SEL=1のときBを出力する)
C1 図3:2to1セレクタのブロック図。A 2bitとB 2bit、SEL 1bitが2to1セレクタに入り、2ビットのY 2bitが出る。
C1 図3:2to1セレクタのブロック図。横に長い図はスクロールして確認できます。

解答1: プリミティブゲートによる記述(ゲートレベル)

各ビットごとに独立したセレクタ回路を論理ゲート(NOT, AND, OR)で構成します。

C1 図4:NOT、AND、ORで組んだ回路図。SELはNOTとANDの両方に入る。NOTの出力とA0が一方のANDへ、SELとB0がもう一方のANDへ入り、2つのANDの出力がORを通ってY0として出る。
C1 図4:NOT、AND、ORで組んだ回路図。横に長い図はスクロールして確認できます。
// プリミティブゲートとassign文の併用
module MUX2to1_GATE ( A, B, SEL, Y );
input  [1:0] A, B;
input        SEL;
output [1:0] Y;

wire sel_n;
not ( sel_n, SEL );

// 0ビット目のセレクタ
wire y0_a, y0_b;
and ( y0_a, A[0], sel_n );
and ( y0_b, B[0], SEL );
or  ( Y[0], y0_a, y0_b );

// 1ビット目のセレクタ(論理式)
assign Y[1] = (A[1] & sel_n) | (B[1] & SEL);

endmodule

解答2: function + if文

module MUX2to1_IF ( A, B, SEL, Y );
input  [1:0] A, B;
input        SEL;
output [1:0] Y;

function [1:0] mux;
input [1:0] A, B;
input       SEL;
begin
    if ( SEL == 1'b0 ) mux = A;
    else               mux = B;
end
endfunction

assign Y = mux( A, B, SEL );
endmodule

解答3: function + case文

module MUX2to1_CASE ( A, B, SEL, Y );
input  [1:0] A, B;
input        SEL;
output [1:0] Y;

function [1:0] mux;
input [1:0] A, B;
input       SEL;
begin
    case ( SEL )
        1'b0: mux = A;
        1'b1: mux = B;
        default: mux = 2'bxx;
    endcase
end
endfunction

assign Y = mux( A, B, SEL );
endmodule

解答4: 条件演算子

module MUX2to1_COND ( A, B, SEL, Y );
input  [1:0] A, B;
input        SEL;
output [1:0] Y;

assign Y = (SEL == 1'b0) ? A : B;

endmodule