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

📌 このページについて ゲート回路は実際の回路記述では使いませんが、HDLの基礎として学びます。セレクタは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引数が出力、以降が入力(順番接続のみ、名前接続は不可)
⚠️ プリミティブゲートの制約
  • ゲートタイプは予約語のため、インスタンス名として使用不可
  • ポート接続は順番接続のみ(名前による接続は使えない)
  • インスタンス名の省略は可(モジュール呼び出しとの違い)

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文+ビット演算子で記述できます。実際の設計ではこちらの方が一般的です。

graph LR in0[in0] --> AND1{{AND}} in1[in1] --> AND1 AND1 --> out_and2[out_and2] style AND1 fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
// 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種類紹介します。

graph LR subgraph 4to1セレクタ MUX[MUX4to1] end DIN[din
4bit] -->|4| MUX SEL[sel
2bit] -->|2| MUX MUX -->|1| DOUT[dout
1bit] style MUX fill:#e3f2fd,stroke:#1976d2,stroke-width:2px;
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];
⚠️ 使用条件(以下の両方を満たすとき限定)
  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文」を使うのが一番おすすめだよ!

📝 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を出力する)
graph LR A[A
2bit] -->|2| MUX[2to1セレクタ] B[B
2bit] -->|2| MUX SEL[SEL
1bit] --> MUX MUX -->|2| Y[Y
2bit] style MUX fill:#e3f2fd,stroke:#1976d2,stroke-width:2px;

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

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

graph LR SEL[SEL] --> NOT1{{NOT}} SEL --> AND2{{AND}} A0[A0] --> AND1{{AND}} NOT1 --> AND1 B0[B0] --> AND2 AND1 --> OR1{{OR}} AND2 --> OR1 OR1 --> Y0[Y0] style NOT1 fill:#e3f2fd,stroke:#1976d2; style AND1 fill:#e8f5e9,stroke:#388e3c; style AND2 fill:#e8f5e9,stroke:#388e3c; style OR1 fill:#fff3e0,stroke:#f57c00;
// プリミティブゲートと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