ネストした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
...