moduleの中のmodule
Verilogの設計はmoduleのtreeです。トップレベルmodule(あなたのtestbenchやチップのトップレベルラッパー)が下位のmoduleをインスタンス化し、それらがさらに下位のmoduleをインスタンス化し、最終的にベンダー供給のgateプリミティブまで降りていきます。 インスタンス化 はその入れ子の構文です。
すでにその形は見ています。最初のmoduleで使いました:
and_gate dut(.a(a), .b(b), .y(y));
この行はand_gateの単一instanceを刻印し、dutと名付け、ポートをローカルsignalに接続しています。各部分を分解してみましょう。
インスタンス化の形
module_name instance_name (port_connections);
module_nameはプロジェクトのどこかにあるmodule宣言の名前と一致しなければなりません。Verilogは大文字小文字を区別します。instance_nameはあなたが選ぶラベルで、通常このinstanceの役割を表す名前にします。階層パスや波形ビューで使います。port_connectionsはinstanceのポートをローカルsignalに配線します。書き方は2通りあります。
名前付きポート接続(こちらを使ってください)
名前付き形式はこんな見た目です:
my_module instance_name(
.clk (clk),
.reset (reset_n),
.data_in(in_bus),
.data_out(out_bus),
.valid (out_valid)
);
各.port(signal)のペアは「このinstanceのportという名のポートを、ローカルのsignalという名のsignalに接続する」という意味です。順序は問いません。moduleの宣言に新しいポートを追加しても、各サイトでデフォルト値を与えるか更新する限り、既存のインスタンス化は壊れません。
実用上の注意:
- ポート名(括弧の左)はmoduleの宣言と完全に一致しなければなりません。
- signal名(括弧の中)はインスタンス化があるところ(通常は親module)でローカルです。
ポートが未接続なら、内側を空にします:.optional_port()。signalはinstance内でフロート(z)します。一部の合成ツールは警告を出しますが、大半は受け入れます。
位置ポート接続(避けてください)
簡潔な形式はsignalをポートリストの順序で並べます:
my_module instance_name(clk, reset_n, in_bus, out_bus, out_valid);
短いですが、もろいです。moduleのポートリストを並べ替える(実際のリファクタで起こります)と、すべての位置インスタンス化が静かに誤配線されます。ポートリストにメンバが1つか2つだけで、変わる可能性が低くない限り、位置接続には手を出さないでください。
依然として受け入れられるのは、ポート順がAPIの一部となっている小さなユーティリティmoduleです。2入力gateなら位置接続でも構いません。30ポートのメモリコントローラはトラブルを呼び込みます。
完全な階層の例
これは実際の3階層の階層構造です:test → full_adder → 2つのhalf_adderインスタンス。各instanceはhalf_adder内のgateの独自のコピーを持ちます。合成ツールはインスタンス化ごとに1つの回路を出力します。
同じmoduleの複数インスタンス
同じmoduleを複数回インスタンス化すると、各instanceは 独立したハードウェア です。stateを共有しません。gateを共有しません。各instanceを設計から刻印された新しいコピーだと考えてください。
adder add0(.a(a0), .b(b0), .sum(s0));
adder add1(.a(a1), .b(b1), .sum(s1));
adder add2(.a(a2), .b(b2), .sum(s2));
adder add3(.a(a3), .b(b3), .sum(s3));
これは並列に動作する4つの別々のadderです。adderがregisterを含むなら、各instanceがそのregisterの独自のコピーを、独自のstateで持ちます。
generateループ:繰り返しハードウェアを刻印する
4つのinstanceを手で書くなら問題ありません。64個書くのは退屈です。generateブロックでelaboratorに代わりにタイピングしてもらえます:
新しい構文が3つ:
genvar iはgenerate内で使えるループ変数を宣言します。これはランタイムsignalではなく、elaboration時にだけ存在します。generate ... endgenerateはループを包みます。明示的なgenerateキーワードなしでも受け付けるツールもありますが、書くと意図が明確になります。begin : invert_loopはgenerateスコープにラベルを付けます。ラベルは各生成instanceの階層名の一部になります(dut.invert_loop[0].u_inv、dut.invert_loop[1].u_invなど)。
合成ツールはループを展開し、WIDTH個のbit_inverterのコピーを生成します。各コピーは独立したハードウェアです。
インスタンス化時のparameterオーバーライド
moduleにparameterがあれば、module名とinstance名の間に#(.PARAM(value))でオーバーライドできます:
counter #(.WIDTH(16)) c16 (.clk(clk), .count(out16));
counter #(.WIDTH(32)) c32 (.clk(clk), .count(out32));
両instanceは同じcounterソースを使いますが、異なる幅を持ちます。構文はParametersで扱いました。インスタンス化にきれいに収まります。
階層名
階層ができたら、すべてのsignalは 階層パス を持ちます:
test.dut.ha0.sum
これは:test moduleの中のdut instanceの中のha0 instanceの中のsumという名のsignal、と読みます。波形ビューア、エラーメッセージ、そしてtestbenchからサブmoduleの深部を覗く時々の$display呼び出しで、これらのパスを目にします:
$display("internal carry1 = %b", dut.carry1);
このような階層参照はtestbenchとデバッグ専用です。合成可能なRTLは他のmoduleの内部に手を伸ばしません。
よくある間違い
ポート名の不一致。 .clk_in(clk)はローカルのclkをclk_inという名のポートに接続します。moduleのポートが実際はclkなら、パーサが教えてくれます(ツールによってメッセージの明確さは異なります)。
ポートの幅の不一致。 4ビットsignalを8ビットポートに接続すると、暗黙にゼロ拡張されます。逆だと暗黙に切り詰められます。多くのツールは警告を出します。警告が見えないなら、もっとよく確認してください。
parameterの#を忘れる。 counter (.WIDTH(8)) c(.clk(clk))はオーバーライドのように見えますが、違います。パーサは(.WIDTH(8))をポート接続として扱おうとして失敗します。正:counter #(.WIDTH(8)) c(.clk(clk))。
instance名の重複使用。 同じスコープ内で2つのinstanceは同じ名前を持てません。エラーメッセージは通常明確ですが、コピペの誘惑で間違えます。
次に読むもの
これでmoduleを実際の階層に配線できるようになりました。次のドキュメントはVerilogの構造的な側面を仕上げます。連続代入では、最初の章から自由に使ってきたassign文をさらに深く掘り下げます。
よくある質問
Verilogでmoduleをインスタンス化するには?
module名、次にinstance名、次にポート接続のリストを括弧内に書きます:my_module instance_name(.port(signal), ...);。最も一般的なスタイルは名前付き接続(.port(signal))で、順序に関わらずポート名で一致させます。簡潔な位置スタイル(my_module instance(signal1, signal2))はポートリストの順序に依存し、保守上危険です。
名前付き接続と位置接続の違いは?
位置接続はsignalをmoduleのポートリストと同じ順序で並べます。最初のsignalが最初のポートに、2番目が2番目に、というふうにです。名前付き接続は.port_name(signal_name)を使い、名前で一致させます。名前付きは冗長ですが、ポートの並べ替えに対して安全で、呼び出し側でも自己説明的です。2〜3個を超えるポートには名前付きを使ってください。
同じVerilog moduleを複数回インスタンス化できますか?
はい。それが本来の目的です。各instanceは独自のstateを持つ独立したハードウェアです。adder moduleがあれば、SIMDユニットで64回インスタンス化でき、それぞれ異なる入力を持ちます。generateループはinstanceが似ていてインデックス付きのときの定番構文です。
Verilogのgenerateブロックとは?
generate ... endgenerateは繰り返しのハードウェアを刻印するコンパイル時の構造です。generate内のforループはbodyの内容をN個のinstanceとして生成します。generateはシミュレーション開始前の elaboration 時に実行されるので、ランタイムのループではなく、合成ツール向けのコードジェネレータです。