ゲート回路とセレクタを書く
📌 このページについて
ゲート回路は実際の回路記述では使いませんが、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. プリミティブゲートの種類
| ゲートタイプ | 論理 | 入力数 | 記述例 |
|---|---|---|---|
not | NOT(インバータ) | 1 | not n1 (out, in); |
and | AND | 2以上 | and a1 (out, in0, in1); |
or | OR | 2以上 | or o1 (out, in0, in1); |
nand | NAND | 2以上 | nand na1 (out, in0, in1); |
nor | NOR | 2以上 | nor nr1 (out, in0, in1); |
xor | XOR(排他的OR) | 2以上 | xor x1 (out, in0, in1); |
xnor | XNOR | 2以上 | 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;
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];
⚠️ 使用条件(以下の両方を満たすとき限定)
- 出力
doutのビット幅が1ビット - 選択対象
dinのビット幅が2の累乗(2, 4, 8, 16…) - selの値とdinのビット番号が値のまま対応(sel=0→din[0]、sel=1→din[1]…)
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を出力する)
(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;
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
