ブロック図から回路を組み立てる
概要: そろそろ本格的な回路記述を始めます。仕様が与えられるので、それを理解して記述してください。ちょっと大変かもしれませんが頑張ってください。
目標:
- 動作仕様・ブロック図から回路の動作を把握できる
- 仕様から入出力端子表を作成できる
- 内部ブロック図(回路構成)を検討できる
- 各ブロックを組み合わせ回路/順序回路に分類できる
- ブロック構成に沿ってVerilog記述ができる
まず実験:位置でつなぐと、黙って入れ替わる
ブロック図から回路を組み立てるとき、部品のつなぎ方には位置接続と名前接続があります。どちらでも動くのに、片方だけが静かに壊れる場面を、動かして確かめます。
回路の仕様
- 部品
sub_unitはa、b、do_subを受け、do_sub=0でa+b、do_sub=1でa-bを返します。 aとbは入れ替えてはいけない入力です。- 同じ部品を、名前接続・位置接続(正しい順)・位置接続(最初の2つを取り違え)の3通りでつなぎます。
回路本体 port_connection.v を見る
`timescale 1ns/1ps
`default_nettype none
// A small unit with two data inputs that are NOT interchangeable.
module sub_unit (
input wire [3:0] a,
input wire [3:0] b,
input wire do_sub,
output wire [4:0] result
);
assign result = do_sub ? ({1'b0, a} - {1'b0, b})
: ({1'b0, a} + {1'b0, b});
endmodule
// The same unit connected three ways.
module port_connection (
input wire [3:0] x,
input wire [3:0] y,
input wire do_sub,
output wire [4:0] r_named,
output wire [4:0] r_ordered,
output wire [4:0] r_swapped
);
// by name: the order in the instance does not matter
sub_unit u_named (.b(y), .do_sub(do_sub), .a(x), .result(r_named));
// by position: correct order
sub_unit u_ordered (x, y, do_sub, r_ordered);
// by position with the first two swapped: a common slip
sub_unit u_swapped (y, x, do_sub, r_swapped);
endmodule
`default_nettype wire
テストベンチ tb_port_connection.v を見る
`timescale 1ns/1ps
`default_nettype none
// 16 x 16 inputs x add/subtract = 512 patterns.
module tb_port_connection;
reg [3:0] x, y;
reg do_sub;
wire [4:0] r_named, r_ordered, r_swapped;
reg [4:0] want;
integer checks = 0;
integer differ_add = 0;
integer differ_sub = 0;
integer i, j;
port_connection dut(.x(x), .y(y), .do_sub(do_sub),
.r_named(r_named), .r_ordered(r_ordered), .r_swapped(r_swapped));
task check;
input [4:0] actual, expected;
input [8*10-1:0] label;
begin
checks = checks + 1;
if (actual !== expected)
$fatal(1, "FAIL: check=%0d %0s x=%b y=%b do_sub=%b actual=%b expected=%b",
checks, label, x, y, do_sub, actual, expected);
end
endtask
initial begin
$dumpfile("port_connection.vcd");
$dumpvars(0, tb_port_connection);
for (i = 0; i < 16; i = i + 1) begin
for (j = 0; j < 16; j = j + 1) begin
x = i[3:0]; y = j[3:0];
do_sub = 1'b0; #1;
want = i + j;
check(r_named, want, "named add");
check(r_ordered, want, "order add");
if (r_swapped !== r_ordered) differ_add = differ_add + 1;
#1;
do_sub = 1'b1; #1;
want = {1'b0, i[3:0]} - {1'b0, j[3:0]};
check(r_named, want, "named sub");
check(r_ordered, want, "order sub");
if (r_swapped !== r_ordered) differ_sub = differ_sub + 1;
#1;
end
end
x = 4'd9; y = 4'd4; do_sub = 1'b1; #1;
$display(" 9 - 4: named=%0d ordered=%0d swapped=%0d", r_named, r_ordered, r_swapped);
x = 4'd9; y = 4'd4; do_sub = 1'b0; #1;
$display(" 9 + 4: named=%0d ordered=%0d swapped=%0d", r_named, r_ordered, r_swapped);
$display(" swapped differs from ordered: add %0d of 256, subtract %0d of 256",
differ_add, differ_sub);
$display("PASS: port connection (%0d checks)", checks);
$finish;
end
initial begin
#20000;
$fatal(1, "FAIL: testbench timeout");
end
endmodule
`default_nettype wire
実行方法
iverilog -g2012 -Wall -s tb_port_connection -o sim.vvp port_connection.v tb_port_connection.v
vvp sim.vvp
16×16の入力と加算/減算、合わせて512パターンを与えます。Windows / Icarus Verilog 13.0で2026年10月28日に実行した結果です。
9 - 4: named=5 ordered=5 swapped=27
9 + 4: named=13 ordered=13 swapped=13
swapped differs from ordered: add 0 of 256, subtract 240 of 256
PASS: port connection (1024 checks)
加算では、取り違えがまったく見えない
足し算は順番を入れ替えても同じ答えになるので、256通りすべてで差が出ません。引き算にすると256通り中240通りで違う答えになります。9 - 4 のつもりが 4 - 9 になり、5ビットで折り返して27が出ました。
ここが怖いところだよ。取り違えても「動いているように見える」場面があるんだ。しかも見えるかどうかは、その部品が何をするかで決まる。加算器やAND回路のように順番を問わない部品だと、取り違えはずっと隠れたままになるよ。
ポートの順序が変わったとき
部品側のポート宣言の順序を入れ替える変更も試しました。部品を直した人は、呼び出し側を直したつもりがないかもしれません。
| 変更 | 結果 |
|---|---|
| 部品のポート宣言の順序を入れ替え | check=8 order sub x=0000 y=0001 actual=00001 expected=11111(位置接続だけが壊れ、名前接続は正しいまま) |
名前接続の .a と .b を取り違え | check=7 named sub x=0000 y=0001 actual=00001 expected=11111 |
名前でつないでおけば、部品側の順序が変わっても影響を受けません。名前接続を基本にしておくと、こうした変更に強くなるよ。
加算だけ試すと、見つからない
ポートの順序を入れ替えた状態で、加算だけを試すテストベンチを実行すると、こうなります。
PASS: partial check (addition only)
合格表示です。部品のつなぎ方を確かめたいなら、入力の順番で答えが変わる操作(引き算、比較、シフトなど)を試験に入れてね。※この簡易テストベンチは、サイト側の検証スクリプトで実行したもので、配布ZIPには入っていません。
確認問題
位置接続を使うとき、安全に使える場面はあるでしょうか。
答え
ポートが少なく、順序が変わる見込みがなく、同じファイル内で完結しているような小さな部品なら、位置接続でも実害は出にくいです。ただし「変わる見込みがない」は人の見積もりなので、部品を共有したり、あとから端子を足したりする場面では名前接続のほうが安全です。この教材では名前接続を基本にします。どちらを使うにしても、上で見たように順番で答えが変わる操作を試験に入れておくことが要ります。
この後の節では、仕様から回路記述へ進む手順と、繰り返し演算回路を題材にした組み立て方を見ていきます。
1. 仕様から回路記述への手順
いきなりコードを書き始めるのはNG!🐾
まずは仕様から「どんな入出力があるか」「どういうブロックに分けるか」を整理するのが、失敗しない回路設計のコツだよ!
新しく回路を記述する場合には回路の仕様から作成することもありますが、ここでは動作仕様とブロック図が与えられた場合の回路記述手順について説明します。
| ステップ | 作業内容 | 成果物の例 |
|---|---|---|
| ① 仕様理解 | 動作仕様・ブロック図から何を行う回路か・どのように動作するか把握する | 入出力端子表、タイミングチャート |
| ② 回路構成の検討 | 必要な回路を書き出し、信号の流れを書き込んで内部ブロック図を完成させる | 内部ブロック図 |
| ③ 回路種別の決定 | 各回路ブロックを「組み合わせ回路」または「順序回路」のどちらにするか決める | 回路種別一覧 |
| ④ 回路記述 | 決定した回路構成に沿って各ブロックをVerilogで記述する | Verilogソースコード |
2. 繰り返し演算回路の仕様
終了判定を通して「繰り返し演算回路」の仕様の理解から回路記述までを体験します。繰り返し演算回路の仕様を示します。何を行う回路か・またどのように動作するかを理解してください。
- クロックの立ち上がりでLD=1 のとき → 入力
DINをDOUTに出力(ロード動作) - クロックの立ち上がりでLD=0 のとき →
DOUTの値に応じて演算を繰り返す:DOUTが奇数のとき →DOUT - 1DOUTが偶数のとき →DOUT ÷ 2(右シフト1ビット)DOUTが0のとき → 0を保持(終了状態)
- DOUTが0になると次にLD=1でDINを読み込むまで
DOUTは0を保持する
動作例:DIN = 255 の場合
| クロック | LD | DOUT | 演算 | 次のDOUT |
|---|---|---|---|---|
| 1回目↑ | 1 | (ロード) | DIN読み込み | 255 |
| 2回目↑ | 0 | 255(奇数) | 255 − 1 | 254 |
| 3回目↑ | 0 | 254(偶数) | 254 ÷ 2 | 127 |
| 4回目↑ | 0 | 127(奇数) | 127 − 1 | 126 |
| 5回目↑ | 0 | 126(偶数) | 126 ÷ 2 | 63 |
| … | 0 | … | 繰り返し | … |
| 最終 | 0 | 0 | 終了(0保持) | 0 |
3. 入出力端子表
| 信号名 | 方向 | ビット幅 | 機能 |
|---|---|---|---|
CLK | 入力 | 1 | 動作クロック(立ち上がりで動作) |
RB | 入力 | 1 | 非同期リセット(0でリセット) |
LD | 入力 | 1 | ロード制御(1のときDINをロード) |
DIN | 入力 | 8 | 演算の初期値入力(0〜255) |
DOUT | 出力 | 8 | 演算結果出力 |
4. 動作タイミングの確認ポイント
タイミングチャートの読み方のポイントを整理します。
- CLKの立ち上がりエッジで出力が変化する
- LD=1のとき:次のCLK↑でDOUT = DIN(ロード)
- LD=0のとき:次のCLK↑でDOUT = DOUT[0]==1 ? DOUT-1 : DOUT>>1
- DOUT=0になったら:LD=1にしてDINを再ロードするまで0を保持
5. 内部ブロック構成の検討
回路をブロックに分けるときの鉄則!🐾
「計算(組み合わせ回路)」と「記憶(順序回路)」をしっかり分けること!これができると、一気にプロっぽい設計になるよ!
この回路の動作が理解できたら今度は回路構成を考えます。以下の構成で実現できます。
| ブロック名 | 回路種別 | 機能 |
|---|---|---|
| 演算ブロック(NEXT_DOUT) | 組み合わせ回路 | 現在のDOUTとLDから次のDOUT値を計算する |
| レジスタブロック(DOUT_REG) | 順序回路(D-FF) | CLKの立ち上がりで演算結果をラッチして出力する |
- 「次の状態を計算する」部分 → 組み合わせ回路(function または assign)
- 「値を保持・出力する」部分 → 順序回路(always + posedge CLK)
- LD=0かつDOUT[0]==1 → DOUT-1(奇数判定は最下位ビット
DOUT[0]で判定) - LD=0かつDOUT[0]==0かつDOUT≠0 → DOUT>>1(右1ビットシフト = ÷2)
6. 回路記述(解答例)
内部ブロック構成に沿って回路記述を行います。演算部はfunction、レジスタ部はalways文で記述します。
module REPEAT_CALC ( CLK, RB, LD, DIN, DOUT );
input CLK, RB, LD;
input [7:0] DIN;
output [7:0] DOUT;
reg [7:0] DOUT;
// ① 演算ブロック: 次のDOUT値を計算する(組み合わせ回路 → function)
function [7:0] NEXT_DOUT;
input ld;
input [7:0] dout;
begin
if ( ld == 1'b1 )
NEXT_DOUT = dout; // LD=1: そのまま保持(ロード動作はalways側で実施)
else if ( dout == 8'd0 )
NEXT_DOUT = 8'd0; // DOUT=0: 終了(0を保持)
else if ( dout[0] == 1'b1 )
NEXT_DOUT = dout - 8'd1; // 奇数: -1演算
else
NEXT_DOUT = dout >> 1; // 偶数: ÷2(右シフト)
end
endfunction
// ② レジスタブロック: CLK立ち上がりで演算結果をラッチ(順序回路)
always @( posedge CLK or negedge RB )
begin
if ( RB == 1'b0 )
DOUT <= 8'd0; // 非同期リセット
else if ( LD == 1'b1 )
DOUT <= DIN; // LD=1: DINをロード
else
DOUT <= NEXT_DOUT( LD, DOUT ); // LD=0: 演算結果をラッチ
end
endmodule
- 奇数・偶数の判定は最下位ビット
DOUT[0]で行う(1なら奇数、0なら偶数) - ÷2はVerilogの右シフト演算子
>> 1で実現(符号なし右シフト) - DOUT=0の終了判定を忘れずに記述すること(無限ループ防止)
- 非同期リセット → LD=1(ロード) → 演算の優先順位で記述する
実習で確かめる、つまずきの補足
ポート名でつなぎ、同じ幅の誤接続も試す
位置指定はモジュール宣言のポート順に依存するよ。同じ1ビットのenableとresetを交換しても、幅チェックだけでは発見できない。一方、ポート数の不一致などはツールがエラーにする場合もあるんだ。
counter4 u_counter (
.clk(clk), .rst_n(rst_n), .enable(enable),
.load(load), .load_data(load_data), .q(q)
);
名前付き接続でも、右側の信号を間違える可能性は残る。仕様変更では方向・幅・極性・接続先を一覧で確認し、リセット、イネーブル、ロードを別々に動かして出力を検査しよう。未接続出力は意図をコメントに、未接続入力には必要な既定値を設計として与えてね。
📝 C7 まとめ: ブロック図から回路記述へ
| 設計ステップ | 作業内容 |
|---|---|
| ①仕様理解 | 動作仕様とブロック図から何を行う回路かを把握する |
| ②入出力端子表 | 外部接続信号の名前・方向・ビット幅・機能を整理する |
| ③内部ブロック図 | 必要な回路を書き出し、信号の流れを整理する |
| ④回路種別決定 | 各ブロックを組み合わせ回路か順序回路かに分類する |
| ⑤回路記述 | ブロック構成に沿ってVerilogで記述する |
