moduleはVerilogの単位
Verilogではすべてがmoduleの中に存在します。moduleは回路の一部をまとめます。どのsignalが入ってきて、どのsignalが出ていき、その間にどんなwire/register/ロジックがあるかを宣言します。あなたが見たことのあるすべてのチップは、他のmoduleをインスタンス化するmoduleのtreeであり、最終的にはベンダーが供給するgateプリミティブまで降りていきます。
形は常に同じです:
module name(port_list);
// 宣言: wire, reg, parameter
// 本体: assign, インスタンス化, alwaysブロック
endmodule
完全なmoduleまで段階的に作っていきます。
最小の有用なmodule:2入力ANDゲート
シンプルで自己完結したものが必要です。2入力ANDゲートは最適です。入力2つ、出力1つ、1行のロジック。
Runを押してください。ANDの真理値表の4行が表示されるはずです。各部分を見ていきましょう。
module宣言を読む
module and_gate(
input wire a,
input wire b,
output wire y
);
module and_gateはand_gateという名前のmoduleを宣言します。この名前で他のmoduleがインスタンス化します。- 括弧内のリストが ポートリスト で、外から見えるsignalです。
input wire a-aは入力ポートで、wire(外から駆動される)です。output wire y-yはこのmodule内で何かに駆動される出力ポートです。
簡潔に書きたいならinput aと書けます(方向だけで型はデフォルトでwireになります)。しかし明示する習慣をつける価値があります。Moduleポートでポート形式の全集合を扱います。
本体
assign y = a & b;
これは 連続代入(continuous assignment) です。「aまたはbが変わるたびに、yを両者のビット単位ANDとして再計算する」という意味です。クロックもタイミングもなく、関係は常に真です。これが純粋な組み合わせ論理です。
endmoduleがブロックを閉じます。moduleはこれで完了です。
testbench
and_gate単独では実行できません。入力を駆動して出力を観察する2つ目のmoduleが必要です。それがtestbenchで、慣例としてtest、tb、<design>_tbと呼びます。
module test;
reg a, b;
wire y;
and_gate dut(.a(a), .b(b), .y(y));
...
endmodule
3つの注目点:
- ポートリストがない。 testbenchはシミュレーションの頂点で、その外側には何もありません。
reg a, bとwire y。 DUT(design under test)への入力は、手続きブロックから 駆動する のでtestbench内ではregです。出力はDUTが駆動するのでwireです。and_gate dut(.a(a), .b(b), .y(y))。 これが インスタンス化 です。and_gateのコピーを刻印してdut(一般的な名前 - 「design under test」)と呼びます。.a(a)構文は「instance上のaという名のポートを、ローカルのaというsignalに接続する」という意味です。Moduleインスタンス化で深掘りします。
刺激
initial begin
a = 0; b = 0; #1 $display("a=%b b=%b y=%b", a, b, y);
a = 0; b = 1; #1 $display("a=%b b=%b y=%b", a, b, y);
a = 1; b = 0; #1 $display("a=%b b=%b y=%b", a, b, y);
a = 1; b = 1; #1 $display("a=%b b=%b y=%b", a, b, y);
$finish;
end
initialブロックはシミュレーション開始時に1度だけ実行されます。その中で:
a = 0; b = 0;は入力を駆動します。これらは blocking代入 で、順序が重要で、各代入は次が始まる前に完了します。#1はシミュレーション時間を1単位進めます。入力変化後yが落ち着く時間を確保するために必要です。#1がなければ、$displayは古いyの値を表示してしまいます。$display(...)はシミュレーションコンソールに出力します。フォーマット文字列はCのprintfのように動作します。%bは2進数、%dは10進数、%hは16進数、%tはシミュレーション時間です。$finishはシミュレーションを終了します。これがないと、シミュレータは決して来ないイベントを永遠に待ち続けます。
壊してみる
moduleをORゲートに変更(&を|に)して再実行してみてください。真理値表が反転します。次にXORを試してください:
同じ骨格。違う演算子。組み合わせ論理ではこれが全体のゲームです。ポートを宣言し、assignを書き、入力を掃引し、出力を観察する。
2出力module
moduleは複数の出力を持てます。これは半加算器(half-adder)です。入力2つ、和とキャリーアウトの出力:
2つのassign文は隣同士に置かれていますが、並列に発生します。「まずsumを計算してからcarryを計算」ではありません。両方が常に真です。ハードウェアとソフトウェアで話した並行性が具体化されています。
これで分かったこと
Verilogファイルの骨格全体を見てきました。ポートとbodyを宣言する設計module、それをインスタンス化するtestbench module、initialブロックで入力を駆動し、結果をレポートする。読むことになるほぼすべてのVerilogソースファイルがこのテンプレートに当てはまります。言語の残りは、より豊かなロジック、マルチビットsignal、クロック同期の挙動、より大きなtestbenchの肉付けに過ぎません。
次はコメントとコードスタイル。moduleが成長しても読みやすく保つために。
よくある質問
最もシンプルなVerilog moduleは?
合法的な最小のVerilog moduleはmodule name; endmoduleだけです。ポートもbodyもありません。最小の有用なものは1出力のmoduleです:module and_gate(input wire a, input wire b, output wire y); assign y = a & b; endmodule。これはどんな大きな設計にも組み込める実物の組み合わせ論理です。
Verilog moduleを実行するには?
moduleだけでは実行できません。回路の記述であってプログラムではないからです。設計をインスタンス化して入力を駆動するtestbench moduleを書き、両方をiverilog -o sim design.v test.vでコンパイルしてvvp simを実行します。このページのブラウザエディタはRunを押すと両方のステップを自動的に行います。
Verilogのtestbenchとは?
testbenchは通常ポートを持たない2つ目のmoduleで、その役割は設計を動かすことです。設計をインスタンス化し、initialブロックで入力を揺らし、$displayや$monitorで出力を観察し、終わったら$finishを呼びます。testbenchは合成不可で、挙動の検証のためだけに存在します。
なぜVerilogコードに$finishが必要なのですか?
ハードウェアは止まらないからです。シミュレータは時間が経過しているふりをしており、明示的な$finishがなければ、新しいイベントを永遠に待ち続けます。$finishはシミュレータに「終了、きれいに終わって」と伝えます。testbenchではinitialブロックの最終行になることが多く、テストを実行して終わる流れです。