[SystemVerilog] Parameterized Classについていろいろ



# Parameterized class
Verilogではmoduleに対してパラメータを渡すことができますが、SystemVerilogではclassに対してもパラメータを渡すことができます。クラスハンドルの宣言の時に設定し、変数のビット幅など、メソッドの引数では対応できない変数を決めることができます。

# 値 (Value)を渡す
簡単な例としては、クラスのデータメンバのビット幅を指定するのに使用できます。以下が簡単なサンプルプログラムです。本例では渡す値の型はintですが、logicでもrealでも何でも指定することができます。
ちなみにクラスのパラメータ宣言部のparameterキーワードは省略しても良いようです。以下の例ですと```#(parameter int WIDTH = 1)```はparameterを省略して```#(int WIDTH = 1)```と記述しても大丈夫なようです。ようですというのは、LRMのBNFではparameterキーワードは必須のように読み取れるのですが、LRMのプログラム例では省略されたものが多用されているからです。



```verilog
class ParamClass #(parameter int WIDTH = 1);
  logic [WIDTH - 1 : 0] sig;

  function void displaySigWidth();
    $display("sig's width is %d", $size(sig));
  endfunction
endclass

program top();
  initial begin
    ParamClass#(.WIDTH(8)) paramClassByte;
    ParamClass#(.WIDTH(32)) paramClassWord;

    paramClassByte = new();
    paramClassWord = new();

    paramClassByte.displaySigWidth(); // Display 8
    paramClassWord.displaySigWidth(); // Display 32
  end
endprogram
```

# 型 (Type)を渡す
parameterには値だけではなく、型そのものを指定することもできます。intやlogic以外にもvirtual interfaceや、クラスなど何でもTypeとすることが可能です。
Typeをパラメータに使う場合の典型的な利用方法はいわゆるコンテナクラスを作成する時になります。任意の型を格納できるクラスで、全ての格納オブジェクトに対して同じインターフェースを提供します。下は型をパラメータとするスタックのサンプルプログラムです。

```verilog
class ParamStack #(parameter type T = int);
  T stk[$];

  function void push(T t);
    stk.push_front(t);
  endfunction

  function T pop();
    stk.pop_front();
  endfunction
endclass

class MyClass;
endclass

program top();
  initial begin
    ParamStack#(.T(int)) paramStackInt;
    ParamStack#(.T(MyClass)) paramStackMyClass;
    MyClass myClass;

    paramStackInt = new();
    paramStackMyClass = new();
    myClass = new();

    paramStackInt.push(1);
    paramStackMyClass.push(myClass);
  end
endprogram
```


他の用途としてはダックタイピングが挙げられます。これは、継承関係が無いクラス間であっても同じメソッドさえ持っていれば、同じように扱う手法です。
以下の例ではMasterのインスタンスを2つ生成しています。1つはパラメータにDuckタイプを渡し、もう一つはDogタイプを渡します。DuckとDogには継承関係はありませんが、Masterクラスからはどちらのオブジェクトに対しても同じようにcall()を呼んで処理できます。
Parameterized typeを使わないで同様のことを行おうとするとそれぞれに専用のMasterクラスを定義しなければなりません。

```verilog
class Duck;
  function string call();
    return "quack";
  endfunction
endclass

class Dog;
  function string call();
    return "bow wow";
  endfunction
endclass

class Master #(parameter type T = Duck); 
  function void callMyPet(T myPet);
    $display("%s", myPet.call());
  endfunction
endclass

program top();
  initial begin
    Duck duck;
    Dog dog;
    Master #(.T(Duck)) duckMaster;
    Master #(.T(Dog)) dogMaster;
    
    duck = new();
    dog = new();    
    duckMaster = new();
    dogMaster = new();

    duckMaster.callMyPet(duck); // quack
    dogMaster.callMyPet(dog);   // bow wow
  end
endprogram
```

# 使用上の注意点

* 渡すパラメータはコンパイル時に決定されなければならない。つまり静的に解決できるものでなければならない
* Parameterには必ずdefaultの指定が必要 (指定しなくてもエラーを吐かないシミュレータもありますが)
* Parameterized classはパラメータ毎に別クラスとして扱われる。オブジェクトサイズの増加に注意
* Parameterized Classを継承時、派生クラスで同じパラメータを引数としてとりたい場合、派生クラスで再度parameter宣言すること

下の2つについてはもう少し詳しく説明します。

## Parameterized classはパラメータ毎に別クラスとして扱われる
同じクラス名でパラメータが異なるものはコンパイル時に全て展開され、それぞれ異なるクラスとして扱われます。
例えば以下のコードでは、baseClass1とbaseClass2は型が非互換なのでコンパイルエラーとなります。

```verilog
class BaseClass #(parameter int i = 1);
endclass

program top();
  initial begin
    BaseClass #(.i(1)) baseClass1;
    BaseClass #(.i(2)) baseClass2;

    baseClass1 = new();
    baseClass2 = baseClass1; // Error. Incompatible type
  end
endprogram
```

このため、定義したクラスの数が少なくても異なるパラメータを使用していくとどんどんコンパイル後のオブジェクトサイズが増大して行きますので注意してください。

## Parameterized classの継承
Parameterized Classでは、ベースクラスのパラメータを派生クラスでも同様に扱いたい場合は、もう一度パラメータ宣言し、その値を親クラスに渡します。そうでないとデフォルトの値が適用されてしまいます

### 派生クラスでParameterを再宣言しない場合

```verilog
class BaseClass #(parameter int I = 1);
  virtual function void printParameter();
    $display("I = %d", I);
  endfunction
endclass

class DerivedClass extends BaseClass;
endclass


program top();
  initial begin
    DerivedClass derivedClass; // There is no way to set parameter to derived class

    derivedClass = new();
    derivedClass.printParameter(); // Print default value of base class of 1
  end
endprogram
```

### 派生クラスでParameterを再宣言した場合

```verilog
class BaseClass #(parameter int I = 1);
  virtual function void printParameter();
    $display("I = %d", I);
  endfunction
endclass

class DerivedClass #(parameter int J = 1) extends BaseClass #(.I(J));
endclass


program top();
  initial begin
    DerivedClass #(.J(8)) derivedClass;

    derivedClass = new();
    derivedClass.printParameter(); // Print 8
  end
endprogram
```

# 参考文献
 "9.25 Parameterized classes". [1800-2017 - IEEE Standard for SystemVerilog--Unified Hardware Design, Specification, and Verification Language](https://doi.org/10.1109/IEEESTD.2018.8299595). pp. 191-192.