Verilogモジュールの基礎:構造と構文
module は特定の機能を実装するVerilogコードのブロックです。モジュールは他のモジュール内に埋め込むことができ、上位モジュールは下位モジュールの入出力ポートを通じて通信します。
構文
module は module と endmodule キーワードで囲む必要があります。モジュール名は module キーワードの直後に指定し、オプションでポートリストを宣言できます。ポートリストで宣言されたポートは、モジュール本体内で再宣言できません。
module ([port_list]);
// モジュールの内容
endmodule
// ポートリストが空のモジュールも可能
module name;
// モジュールの内容
endmodule
すべての変数宣言、データフローステートメント、関数やタスク、下位モジュールインスタンス(あれば)は、module と endmodule キーワード内で定義する必要があります。同一ファイル内に異なる名前の複数モジュールを定義でき、順序は任意です。
例
モジュール dff はDフリップフロップを表し、3つの入力ポート(d, clk, rstn)と1つの出力ポート(q)を持ちます。モジュールの内容は、様々な入力組み合わせに対するDフリップフロップの振る舞いを記述します。ここでは、入力 d がクロックの立ち上がりエッジで常に出力 q に代入されます。rstn がハイのとき(アクティブローリセット)です。
// "dff"モジュール:3入力、1出力
module dff (
input d, input clk, input rstn, output reg q
);
// モジュールの内容
always @ (posedge clk) begin
if (!rstn) q <= 0;
else q <= d;
end
endmodule
ハードウェア回路図
このモジュールは合成時に以下のデジタル回路に変換されます。
モジュールの目的とは?
モジュールは特定の振る舞い特性を実装する設計単位であり、合成時にデジタル回路に変換されます。入力のあらゆる組み合わせを与えると、対応する出力を提供します。これにより、同じ モジュール を再利用して、より複雑なハードウェアを実装する大きなモジュールを形成できます。
例えば、上記のDFFを連結してシフトレジスタを作れます。
module shift_reg (
input d, input clk, input rstn, output q
);
wire [2:0] q_net;
dff u0 (.d(d), .clk(clk), .rstn(rstn), .q(q_net[0]));
dff u1 (.d(q_net[0]), .clk(clk), .rstn(rstn), .q(q_net[1]));
dff u2 (.d(q_net[1]), .clk(clk), .rstn(rstn), .q(q_net[2]));
dff u3 (.d(q_net[2]), .clk(clk), .rstn(rstn), .q(q));
endmodule
ハードウェア回路図
DFFインスタンスが、Verilog RTLモジュールで記述された通りにワイヤで接続されています。
逆に、大きな設計ブロックを小さなコンポーネントに分解することもできます。シンプルなGPUエンジンを、各々が特定機能を実装するモジュールとして表現できる小さなコンポーネントに分解する例を考えます。以下のGPUエンジンは5つのサブブロックに分割でき、各々が特定機能を実行します。バスインターフェースユニットが外部からデータを取り込み、別ユニットが命令を抽出し、以降のユニットが前段からのデータを処理します。
各サブブロックは、他モジュールと通信するための特定の入出力信号を持つ module として表現でき、必要に応じてさらに細かいブロックに分割可能です。
トップレベルモジュールとは?
トップレベル モジュールは、他のすべてのモジュールを 含む ものです。トップレベルモジュールは、他のどのモジュール内にもインスタンス化されません。
例えば、設計モジュールは通常、トップレベルのテストベンチモジュール内でインスタンス化され、入力刺激を与えてシミュレーションを実行します。しかし、テストベンチは他のどのモジュール内にもインスタンス化されず、すべてをカプセル化するブロックであるため、トップレベルモジュール となります。
設計トップレベル
以下の設計コードでは、design というモジュールがトップレベルです。設計を完成させるために必要な他のすべてのサブモジュールを含んでいるからです。サブモジュールはさらにネストしたサブモジュール(mod1 内の mod3、mod2 内の mod4)を持つ可能性があります。いずれにせよ、mod1 と mod2 がインスタンス化されるとき、これらすべてがトップレベルモジュールに含まれます。したがって design が完全な設計となり、設計の観点からはトップレベルモジュールです。
//---------------------------------
// 設計コード
//---------------------------------
module mod3 ([port_list]); reg c; // 設計コード endmodule
module mod4 ([port_list]); wire a; // 設計コード endmodule
module mod1 ([port_list]);
wire y;
mod3 mod_inst1 (...); // "mod3"モジュールのインスタンス、名前"mod_inst1"
mod3 mod_inst2 (...); // 同じく"mod3"、名前"mod_inst2"
endmodule
module mod2 ([port_list]);
mod4 mod_inst1 (...);
mod4 mod_inst2 (...);
endmodule
// トップレベルモジュール
module design ([port_list]);
wire _net;
mod1 mod_inst1 (...); // すべてのモジュールとサブモジュールを含むため
mod2 mod_inst2 (...); // 設計の観点からトップレベル
endmodule
テストベンチトップレベル
テストベンチモジュールは設計の機能をチェックするための刺激を含み、主にシミュレーションツールを用いた機能検証に使用されます。したがって設計がインスタンス化され、testbench モジュール内で d0 と呼ばれます。シミュレータの観点からは、テストベンチがトップレベルモジュールです。
//-----------------------------------------------------------
// テストベンチコード
// シミュレーション観点ではこれがトップレベルモジュール
// 'design'がこのモジュール内でインスタンス化されるため
//-----------------------------------------------------------
module testbench;
design d0 ([port_list_connections]);
// テストベンチコードの残り
endmodule
階層名
モジュールが互いにインスタンス化されると階層構造が形成され、トップレベルモジュールは ルート と呼ばれます。与えられたモジュール内の各下位モジュールインスタンスは異なる識別子名を持つ必要があるため、信号へのアクセスに曖昧さはありません。階層名は、各レベルの識別子をドット . で区切ったリストで構成されます。任意のモジュール内から、その信号への階層パスを使って任意の信号にアクセスできます。
// 上記トップレベルモジュールの例を利用
design.mod_inst1 // mod_inst1モジュールインスタンスへのアクセス
design.mod_inst1.y // mod_inst1内の信号"y"へのアクセス
design.mod_inst2.mod_inst2.a // mod4モジュール内の信号"a"へのアクセス
testbench.d0._net; // testbenchからdesignモジュール内のトップレベル信号_netにアクセス
Verilog
- Verilogマスター:デジタル回路設計のための完全チュートリアル
- Verilog連結マスター:実践ガイド
- Verilogエッセンシャル:簡潔な概要
- Verilog代入文の理解:種類、構文、ベストプラクティス
- Verilogブロッキング代入とノンブロッキング代入:違いを徹底解説
- Verilog関数のマスター:効率的なRTL設計のために
- Verilogタスクの理解:関数との違い、構文、使用法
- Verilog階層参照スコープのマスター:モジュールとブロックでの名前衝突回避
- Verilogクロックジェネレータ:設計と実装ガイド
- Verilog数学関数のマスター:効率的なハードウェア設計のために
- Verilog $timeformatのマスター:精度と表示制御