ネストしたmodule (nested modules)
概要
SVでは、module 宣言のなかに、module宣言が置けます。
以下の回路では、baseモジュールの下には、sub_moduleのインスタンス,sub_moduleの下には、sub_sub_moduleがインスタンス化されます。
module base(input d); wire w=d; wire q1,q2; initial begin #1; $display("%m %d"); end module sub_module; wire s=d; assign q1=s; initial begin #1; $display("%m"); end module sub_sub_module; wire ss=s; assign q2=ss; initial begin #1; $display("%m d=%b w=%b q1=%b q2=%b s=%b ss=%b",d,w,q1,q2,s,ss); #100; $finish; end endmodule endmodule endmodule module top; reg ck=1; always #10 ck=~ck; base dut(ck); endmodule
実行結果は次のようになります。
***** Veritak SV32 Engine Version 433 Build Jan 5 2013***** top.dut top.dut.sub_module top.dut.sub_module.sub_sub_module d=1 w=1 q1=1 q2=1 s=1 ss=1 Info: $finishコマンドを実行します。time=101ns
これをScopeTreeViewで見ると、以下のようになります。
これは、通常の記述でも、書こうと思えば書けますね。では、一体ないがよいかというと、
nest moduleを使うと、共通部/非共通部をパーティショニングできる。
上の回路で2,3行目の信号は、その下のsub_module/ sub_sub_moduleからダイレクトに見えます。また、12行目の信号は、その下のsub_sub_moduleからダイレクトに見えます。
30行目のように、一番下の sub_sub_moduleは、あたかも、自分のmoduleで宣言したかのように自由にアクセスできます。
一方、上のモジュールから、下のmoduleの信号は、見えません。共通な信号は、上の方でまとめて、共通でない信号は、ローカルモジュールとしてまとめることが出来ます。
ポートを介すると、なにかと分かりづらくなってしまいますが、設計意図を反映しやすい記述ができるようになります。
ローカライズしたmoduleは、他のモジュールからは見えませんし、それだけを取り出してインスタンス化することは、できません。
-必要でない部分を、できるだけ見えなくすることは、プログラムのカプセル化に通じる部分があると思います。
暗黙インスタンス化を使わない記述
上では、nested moduleだけに許されている”暗黙のインスタンス化”で、インスタンス化しました。暗黙インスタンスが適用されるのは、明示的インスタンス化がない場合かつ、ポートがない場合です。"暗黙のインスタンス化”では、インスタンス名=モジュール名でインスタンス化されます。なお、ポートのある記述で明示的インスタンスがない場合は、無視されてしまいます。(インスタンス化されません。)
以下は、適当に書いたサンプルです。回路としての意味はありません。(ネストモジュールにおけるスコープ(可視部分)の説明用です。)
module base #(parameter int W=3,WORDS=4 ) (input d); wire [W-1:0] q1[WORDS]; int address_reg;//各階層で同じ名前のレジスタがある task base_task; $display("base_task $bits(address_reg)=%3d",$bits(address_reg)); $display("q1=%p",q1); endtask function automatic int clog2(input longint x); longint val; if (!x) return 0; else begin val = 1; while (val<=x) begin clog2++; val <<=1; end return clog2; end endfunction initial begin #3; base_task;//可視task郡 end module sub_module #(parameter int ADDRESS=0)(); wire [W-1:0] s; assign q1[ADDRESS]=s; reg [ clog2(WORDS)-1:0] address_reg;//各階層で同じ名前のレジスタがある task sub_task; $display("sub_task $bits(address_reg)=%3d",$bits(address_reg)); endtask initial begin #2; base_task;//可視task郡 sub_task;//可視task郡 end module sub_sub_module #(parameter int SEL=0)(); wire [W-1:0] ss=$signed(d); assign s[SEL]=ss[SEL]; byte address_reg;//各階層で同じ名前のレジスタがある task sub_sub_task; $display("sub_sub_task $bits(address_reg)=%3d",$bits(address_reg)); endtask initial begin #1; base_task;//可視task郡 sub_task;//可視task郡 sub_sub_task;//可視task郡 #100; $finish; end endmodule for (genvar i=0; i< W; i++ ) begin parameter int SEL=i; sub_sub_module #(.SEL(SEL)) B(); end endmodule for (genvar i=0;i < WORDS;i++) begin parameter int ADDRESS=i; sub_module #(.ADDRESS(ADDRESS)) B(); end endmodule module top; reg ck=1; always #10 ck=~ck; base #(.WORDS(8),.W(4)) dut(ck); endmodule
上の記述の階層構造は、以下のようになります。
これ例では、暗黙のインスタンス化は用いずに、明示的にインスタンス化しています。まず、task
郡ですが、子どもの階層では、自分以上親の階層のモジュールのタスクを自由に呼び出すことができます。53-55行目、38-39行目。つまり、ネストモジュールの範囲内においては、親moduleは、子どもmoduleにとって可視範囲にある訳です。
30行目で親のコンスタントファンクション(コンパイル時に使用され、実行中は、得られた値が定数として使用される)を使っていますが、これが使用可能なのも同じ理屈です。
4,30,45行目で、address_reg という同じ名前のレジスタがありますがリーガルな記述です。
-このような同じ名前のつけかたは、推奨されないスタイルだと思います。
これらは、すべてそのスコープでのレジスタになることに注意してください。名前の探索は、まず自分のスコープ、そこで見つからなければ、その上のスコープ、そこで見つからなければ、また次の上のスコープ..という風に行われます。ベースモジュールまで探索して見つからなければ、コンパイルエラーになります。下の図のように、ネストモジュール中に他のモジュールが存在した場合は、それらは、module baseは勿論、module
sub_module/sub_sub_module からも見えません。
-ただし、パッケージや、$unit スコープが定義されている場合は、さらにそれらも探索が行われます。SVの場合、かなり広大な名前空間を探索することになるので、うっかり名前をミスっても、コンパイルエラーにならず、名前が意図しないスコープでヒットしてしまうことが多いと思います。
Veritak SV32 Engine Version 433 Build Jan 6 2013***** base_task $bits(address_reg)="32" q1='{15,15,15,15,15,15,15,15} sub_task $bits(address_reg)= 4 sub_sub_task $bits(address_reg)= 8 ...