ひと工夫ある定数
parameterは定数です。コンパイラが値を選び、シミュレーション開始前に設計に焼き付けます。ひと工夫はインスタンス化する側が値を 変えられる ことです。同じmoduleのソースファイルから、呼び出し側がparameterをどう設定するかによって、異なるサイズの回路を生成できます。
これが再利用可能なIPを作る方法です。WIDTHとDEPTHのparameterを持つFIFO moduleは会社のすべてのチームに役立ちます。WIDTHのparameterを持つカウンタは、16まで数えるか40億まで数えるかに関わらず同じソースです。
基本のparameter宣言
新しい構文が3つ:
- parameterブロック:
#( parameter WIDTH = 8 )をmodule名とポートリストの間に置きます。デフォルト値8は誰もオーバーライドしないときにmoduleが使う値です。 - 使用箇所:
output reg [WIDTH-1:0] count。parameterは単なる定数で、ビット範囲に差し込みます。同じmoduleソースがWIDTHに応じて8ビットまたは16ビット出力を生成します。 - オーバーライド構文:インスタンス化時の
counter #(.WIDTH(16)) dut16(...)。#(...)ブロックはinstance名の前、module名の後に置きます。指定しないparameterはデフォルトのままです。
localparam:内部定数
呼び出し側に オーバーライドさせたくない 定数があります。state encodingが典型例です:
localparamはIDLE、RUNNING、DONEをmodule内で利用可能にしますが、インスタンス化時にオーバーライド可能にはしません。これが正しい選択です。IDLEの意味するstate値を外から変えるのは怖すぎます。
呼び出し側が設定すべきもの(幅、深さ、挙動オプション)にはparameterを、それ以外にはlocalparamを使ってください。一般的なパターンはlocalparamをparameterから導出することです:
parameter WIDTH = 32;
localparam WIDTH_M1 = WIDTH - 1; // 一度計算される、オーバーライド不可
実践でのparameter化された幅
parameterの最も一般的な使い方はバス幅を柔軟にすることです。任意の幅で動作するadderはこちら:
1つのソースファイル、2つのinstance、2つの異なる幅、コピペなし。これがparameterの全体の見返りです。
複数のparameter、名前付きオーバーライド
大きなmoduleでは複数のparameterを持つことが多くなります。任意の順序で名前でオーバーライドします:
module fifo #(
parameter WIDTH = 8,
parameter DEPTH = 16,
parameter AFULL = DEPTH - 2
)(
input wire clk,
input wire reset,
// ... ports ...
);
// ...
endmodule
// 呼び出し側で:
fifo #(.WIDTH(32), .DEPTH(1024)) cmd_queue (.clk(clk), .reset(reset), ...);
すべてのparameterをオーバーライドする必要はなく、言及されないものはデフォルトのままです。例のAFULLのデフォルトはDEPTHから計算されているので、DEPTHをオーバーライドすればAFULLは自動的に追従します。呼び出し側に独立してAFULLをオーバーライドさせたくない場合は、localparamも同じ依存関係を扱える、というのと正に一致します。
よくある間違い
オーバーライドで#を忘れる。 counter (.WIDTH(16)) dut(...)はオーバーライドのように見えますが、Verilogは(.WIDTH(16))をポート接続として読みます。counter #(.WIDTH(16)) dut(...)が必要です。
定数として十分でない場所でparameterを使う。 parameterはelaboration時、signalが存在する前に解決されます。ランタイムsignalにparameter化することはできません。値がシミュレーション時の入力の振る舞いに依存するなら、それはparameterではなくロジックです。
ポートリストでparameterとlocalparamを混同する。 トップの#(...)ブロックに入るのはparameterだけです。localparamはbody内です。入れ替えるとコンパイラが教えてくれます。
次に読むもの
これでコンパイル時にサイズが決まるmoduleを作れます。次の2つのドキュメントは、リテラル定数(8'h1F、4'b1010、32'd100)の書き方、そしてsignalが駆動されていなかったり競合したりするときに現れるxとzの値のルールを扱います。その後、operatorに移ります。これで見てきたvectorとparameterでできるすべてです。
よくある質問
Verilogのparameterとは?
parameterはmodule内で宣言されるコンパイル時の定数です。定数が許される場所どこでも使えます。バス幅、memory深さ、state encoding、デフォルト値などです。重要なのは、moduleの各instanceがparameterをオーバーライドできることで、同じソースファイルがある場所では8ビットカウンタ、別の場所では32ビットカウンタを生成できます。
parameterとlocalparamの違いは?
parameter値はインスタンス化時にmoduleの外からオーバーライドできます。localparam値はできません。内部定数専用です。state encodingや、呼び出し側にいじられたくない派生定数にはlocalparamを使ってください。
Verilogでparameterをオーバーライドするには?
moduleをインスタンス化するとき、instance名の前にparameterのオーバーライドブロックを追加します:counter #(.WIDTH(16)) my_inst (.clk(clk), .count(count));。.WIDTH(16)構文がそのparameterを設定します。リストにないparameterはデフォルトのままです。複数のオーバーライドは#(...)内でカンマで区切ります。
parameter化されたmoduleとは?
1つ以上のparameter宣言を呼び出し側がオーバーライドできるように公開するmoduleです。parameter化されたFIFOはWIDTHとDEPTHのparameterを持つかもしれず、同じソースがある場所では32ビット×16深さのFIFO、別では8ビット×1024深さのFIFOを生成します。これが再利用可能なIPブロックライブラリの書き方です。