ゲート回路とセレクタを書く
まず実験:選ばれなかった入力はどうなる?
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)
読み取れること
- 4つの書き方は同じ機能。
selが0〜3に確定している64パターンすべてで一致しました。このページの8章で並べている4つの記述スタイルが、機能として同じであることを実行で確かめた形です。 - 選ばれない入力が不定値でも、出力は確定する。
d=0xxxでsel=3のとき、他の3本がxでも出力は0です。選択信号が確定していれば、通る経路だけが出力を決めるからです。
ここで注意したいのは、2つ目が「選ばれない入力は回路に無い」という意味ではないことなんだ。4本ぶんの経路は回路として存在していて、sel がどれを通すかを決めているだけだよ。このページの補足にある「選択しない入力も回路の一部」というのは、そういう意味なんだ。
わざと壊して、テストの働きも確かめる
| 壊した場所 | 結果 |
|---|---|
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=xif連鎖と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. プリミティブゲートの種類
| ゲートタイプ | 論理 | 入力数 | 記述例 |
|---|---|---|---|
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); |
and です。入力信号の数だけポートリストに追加します。
論理合成対象のプリミティブは上記7種類が主です。その他のプリミティブ(bufif・notif等)は論理合成対象外でテストベンチ向けです。
3. assign文によるゲート記述
プリミティブゲートと同等の回路を、assign文+ビット演算子で記述できます。実際の設計ではこちらの方が一般的です。
// 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;
4. 4to1セレクタ: スタイル1 — function + if文
4本の信号(din[3:0])から選択信号(sel[1:0])で1本を選択するセレクタの記述方法を4種類紹介します。
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);
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];
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セレクタの記述
(SEL=0のときA、SEL=1のときBを出力する)
解答1: プリミティブゲートによる記述(ゲートレベル)
各ビットごとに独立したセレクタ回路を論理ゲート(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
