Verilog generateブロックをマスター:動的モジュールインスタンス化と条件付き設計
generate ブロックを使用すると、モジュールインスタンスを複数生成したり、任意のモジュールを条件付きでインスタンス化したりできます。設計をVerilogパラメータに基づいて構築する機能を提供します。同じ操作やモジュールインスタンスを複数回繰り返す必要がある場合や、与えられたVerilogパラメータに基づいて特定のコードを条件付きで含める場合に特に便利です。
generate ブロックには、ポート、パラメータ、specparam 宣言や specify ブロックを含められません。しかし、他のモジュールアイテムや他のgenerateブロックは許可されます。すべての生成インスタンス化は、module 内で、キーワード generate と endgenerate の間に記述されます。
生成されるインスタンス化には、モジュール、連続代入、always または initial ブロック、ユーザー定義プリミティブが含まれます。generate構造には2種類あります:ループと条件分岐。
- Generate for ループ
- Generate if-else
- Generate case
Generate for ループ
半加算器を、generate forループ構造を使って、別のトップレベル設計モジュール my_design 内で N 回インスタンス化します。ループ変数はキーワード genvar で宣言し、この変数がgenerateブロックのエラボレーション専用であることをツールに伝えます。
// 半加算器の設計
module ha (input a, b, output sum, cout);
assign sum = a ^ b;
assign cout = a & b;
endmodule
// N個の半加算器インスタンスを含むトップレベル設計
module my_design #(parameter N=4) (
input [N-1:0] a, b,
output [N-1:0] sum, cout
);
genvar i; // 生成時に使用される一時的なループ変数(シミュレーション時には不可用)
generate
for (i = 0; i < N; i = i + 1) begin
ha u0 (a[i], b[i], sum[i], cout[i]);
end
endgenerate
endmodule
テストベンチ
テストベンチパラメータで設計内の半加算器インスタンス数を制御します。N=2なら、my_designは半加算器2インスタンスを持ちます。
module tb;
parameter N = 2;
reg [N-1:0] a, b;
wire [N-1:0] sum, cout;
my_design #(.N(N)) md (.a(a), .b(b), .sum(sum), .cout(cout));
initial begin
a <= 0; b <= 0;
$monitor ("a=0x%0h b=0x%0h sum=0x%0h cout=0x%0h", a, b, sum, cout);
#10 a <= 'h2; b <= 'h3;
#20 b <= 'h4;
#10 a <= 'h5;
end
endmodule
a[0] と b[0] が sum[0] と cout[0] を、a[1] と b[1] が sum[1] と cout[1] を出力します。
シミュレーションログ
ncsim> run a=0x0 b=0x0 sum=0x0 cout=0x0 a=0x2 b=0x3 sum=0x1 cout=0x2 a=0x2 b=0x0 sum=0x2 cout=0x0 a=0x1 b=0x0 sum=0x1 cout=0x0 ncsim: *W,RNQUIE: Simulation is complete.
エラボレートされたRTLには、generate ブロックによって生成された2つの半加算器インスタンスが確かに存在します。
Generate if
以下は、generate 構造内で if else を使って、2つの異なる多重化器実装を選択する例です。最初の設計は assign 文でMUXを実装し、2番目は case 文を使用。トップレベル設計モジュールで USE_CASE というパラメータを定義し、2つの選択肢から選びます。
// 設計#1: assign文でMUX実装
module mux_assign (input a, b, sel, output out);
assign out = sel ? a : b;
initial $display ("mux_assign is instantiated");
endmodule
// 設計#2: case文でout信号を駆動
module mux_case (input a, b, sel, output reg out);
always @ (a or b or sel) begin
case (sel)
0: out = a;
1: out = b;
endcase
end
initial $display ("mux_case is instantiated");
endmodule
// トップレベル設計: パラメータでどちらかを選択
module my_design (input a, b, sel, output out);
parameter USE_CASE = 0;
generate
if (USE_CASE)
mux_case mc (.a(a), .b(b), .sel(sel), .out(out));
else
mux_assign ma (.a(a), .b(b), .sel(sel), .out(out));
endgenerate
endmodule
テストベンチ
テストベンチはトップレベルモジュール my_design をインスタンス化し、パラメータ USE_CASE を1に設定して、case 文を使う設計をインスタンス化します。
module tb;
reg a, b, sel;
wire out;
integer i;
my_design #(.USE_CASE(1)) u0 (.a(a), .b(b), .sel(sel), .out(out));
initial begin
a <= 0; b <= 0; sel <= 0;
for (i = 0; i < 5; i = i + 1) begin
#10 a <= $random; b <= $random; sel <= $random;
$display ("i=%0d a=0x%0h b=0x%0h sel=0x%0h out=0x%0h", i, a, b, sel, out);
end
end
endmodule
USE_CASE が1ならシミュレーションログから case 文の設計が、0なら assign 文の設計がインスタンス化されたことがわかります。これはシミュレーションログに表示される $display 文で確認できます。
シミュレーションログ
// USE_CASE = 1の場合 ncsim> run mux_case is instantiated i=0 a=0x0 b=0x0 sel=0x0 out=0x0 ... // USE_CASE = 0の場合 ncsim> run mux_assign is instantiated i=0 a=0x0 b=0x0 sel=0x0 out=0x0 ...
Generate case
Generate caseは、case 式に基づいて複数の選択肢から1つを選び、モジュール、initial/alwaysブロックをインスタンス化できます。
// 設計#1: 半加算器
module ha (input a, b, output reg sum, cout);
always @ (a or b) {cout, sum} = a + b;
initial $display ("Half adder instantiation");
endmodule
// 設計#2: 全加算器
module fa (input a, b, cin, output reg sum, cout);
always @ (a or b or cin) {cout, sum} = a + b + cin;
initial $display ("Full adder instantiation");
endmodule
// トップレベル設計: 半加算器と全加算器を選択
module my_adder (input a, b, cin, output sum, cout);
parameter ADDER_TYPE = 1;
generate
case (ADDER_TYPE)
0: ha u0 (.a(a), .b(b), .sum(sum), .cout(cout));
1: fa u1 (.a(a), .b(b), .cin(cin), .sum(sum), .cout(cout));
endcase
endgenerate
endmodule
テストベンチ
module tb;
reg a, b, cin;
wire sum, cout;
my_adder #(.ADDER_TYPE(0)) u0 (.a(a), .b(b), .cin(cin), .sum(sum), .cout(cout));
initial begin
a <= 0; b <= 0; cin <= 0;
$monitor("a=0x%0h b=0x%0h cin=0x%0h cout=0%0h sum=0x%0h", a, b, cin, cout, sum);
for (int i = 0; i < 5; i = i + 1) begin
#10 a <= $random; b <= $random; cin <= $random;
end
end
endmodule
半加算器がインスタンス化されるため、cin は出力 sum と cout に影響を与えません。
シミュレーションログ
ncsim> run Half adder instantiation a=0x0 b=0x0 cin=0x0 cout=00 sum=0x0 a=0x0 b=0x1 cin=0x1 cout=00 sum=0x1 a=0x1 b=0x1 cin=0x1 cout=01 sum=0x0 a=0x1 b=0x0 cin=0x1 cout=00 sum=0x1 ncsim: *W,RNQUIE: Simulation is complete.
Verilog
- Verilogマスター:デジタル回路設計のための完全チュートリアル
- Verilog連結マスター:実践ガイド
- Verilogエッセンシャル:簡潔な概要
- Verilog代入文の理解:種類、構文、ベストプラクティス
- Verilogブロッキング代入とノンブロッキング代入:違いを徹底解説
- Verilog関数のマスター:効率的なRTL設計のために
- Verilogタスクの理解:関数との違い、構文、使用法
- Verilogクロックジェネレータ:設計と実装ガイド
- Verilog数学関数のマスター:効率的なハードウェア設計のために
- Verilog $timeformatのマスター:精度と表示制御
- 任意のスコープでのVerilog timescale理解と表示