Menu

Verilog moduleのインスタンス化:サブmoduleの接続

1つのmoduleを別のmoduleの内側でインスタンス化する方法、名前付き接続と位置接続の違い、そして実際の設計を構築するときに使う複数インスタンスのパターン。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

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階層の階層構造です:testfull_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 igenerate内で使えるループ変数を宣言します。これはランタイムsignalではなく、elaboration時にだけ存在します。
  • generate ... endgenerate はループを包みます。明示的なgenerateキーワードなしでも受け付けるツールもありますが、書くと意図が明確になります。
  • begin : invert_loop はgenerateスコープにラベルを付けます。ラベルは各生成instanceの階層名の一部になります(dut.invert_loop[0].u_invdut.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)はローカルのclkclk_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 時に実行されるので、ランタイムのループではなく、合成ツール向けのコードジェネレータです。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める