割り当てとユーザ定義型
*ishitaniさんから指摘された割り当てについての説明を修正しました(12/21/16:20)。
assign
配線(宣言子:wire)や組合せ回路のロジック(宣言子:logic)を結線したい場合assignを用います。後で説明する代入とこの組み合わせ回路への値の割り当ては異なり、私も含め初心者が混同したり混乱しやすい点でも有ります。
例えば配線"w_a"に値を直接割り当てたい場合は
assign 割り当てたいオブジェクトの名前 = 即値或いはオブジェクト名;
の形式で割り当て、
assign w_a = '0;
と記述します。SystemVerilogでは一つの指示の終端にセミコロン";"を付与することで明示します。また、ゼロ値を割り当てる場合”'0"とすれば幅を気にせずゼロ値を割り当てることができます。これは後で説明する構造体内のすべてオブジェクトに"0"で初期化できるので有用な割り当て方法です。
コーディングしている際に一部の部分だけに割り当てたい場合もあるでしょう。
その時は例えば
assign w_a[3:1] = '0;
として2ビット目から4ビット目まで部分的に割り当てることができます。注意したいのは論理合成する際に割り当てていない部分は浮遊しておりハイインピーダンスとして扱われます。ですので、その部分を参照すると組合せ回路では不定として扱われてそれが後続のロジックへ伝播していきます。assignはあくまで右辺の組み合わ回路の持つ値の一部あるいは全ての値を左辺の一部あるいは全てに割り当てるもので、後で説明する代入とは異なります。
また、オブジェクトの全体或いは一部に多重に割り当てている場合(マルチドライブと呼びます)、複数の信号が流入してくるので動作時に不定になるので注意してください。一般に論理合成時にワーニングとしてレポートされるでしょう。
logic宣言と同時に値やオブジェクトを割り当てるとシミュレータはそれを定数値として扱います。
従ってシミュレーション時に値が変化すると不定として扱われます。
assign logic_hoge = ...
...
wire [3:0] w_wire = '0;
logic [3:0] w_logic = w_logic_hoge;
上の例では配線"w_wire"にゼロ値が割り当てられますが、ロジックである”w_logic"では不定となります。
logic [3:0] w_logic;
assign w_logic = w_logic_hoge;
と記述する必要があります。
ユーザ定義型
これまでポート、配線、ロジックといったオブジェクトを宣言する際それぞれ、"input"や"output"、"wire"、"logic”といった宣言子を用いました。これらの宣言子はSystemVerilogが用意している宣言子であり予約語です。開発しているとオブジェクトに構造を持たせたい時がよくあります。
例えば、配線を考える時
wire [7:0] w_wire;
assign w_wire[7:5] = ...;
assign w_wire[4] = ...;
assign ... = wire_wire[3:0];
と、ビットフィールドに割り当てて記述するのは煩雑になりやすくエラーの温床になります。
例えばこれを
assign w_wire.f2 = ...;
assign w_wire.f1 = ...;
assign ... = wire_wire.f0;
のように役割に合わせてf2、f1、f0の様に名前を与えて利用できたらコーディングの負担が軽減でき、またエラーも防ぎやすくなることが想像できると思います。SystemVerilogではこの様なオブジェクトの構造体をユーザーが定義できる特徴を持っています。
この例では
typedef wire [2:0] f2_t;
typedef wire f1_t;
typedef wire [3:0] f0_t;
のように構造体の内容をtypedefを用いてwire型として宣言し、使用する際には宣言子として
f2_t f2;
と一般的な用法で宣言します。
先の例の場合構造体を作りたいので
typedef struct packed {
f2_t f2;
f1_t f1;
f0_t f0;
} w_wire_t;
と構造体(struct)で複数のオブジェクトを内包する(packed)記述で構造体インスタンスを持つユーザー定義型であるw_wire_tを作ることができ、”packed{}”の中に構造体に含めるオブジェクトを宣言しておきます。
これを宣言子として
w_wire_t w_wire;
と宣言すればオブジェクト"w_wire"を作成でき、先の様に想定した使用が可能になります。さらにユーザー定義の宣言子を組み合わせて階層構造のユーザー定義宣言も作成できます。
typedef wire [1:0] level0_t;
typedef level0_t [3:0] level1_t;
とすれlevel1_tの下にlevel0_tの構造を持たせることができます。そして構造体を階層にもできます。
例えば、
typedef struct packed {
w_wire_t f3;
logic [7:0] f1;
f0_t f0;
} w_hoge_t;
と自由に組み合わせて構造体を作れます。ただ、構造体を複雑にすると実際使用する際に却って誤りやすくなり、場合によっては検証時に面倒になるかもしれません。さらに第3者にとってコードが読みにくくもなるので、再利用性も下がる可能性があります。
以上の様な構造体は複数のモジュールで共用することが想定され、モジュールの中で宣言するのではなく例えば共用のファイルとして別途用意します。このファイルをパッケージと呼び次の様な宣言の中に記述します。
package foo_pkg;
endpackage
この例ではパッケージ"foo_pkg"を記述しており、この中に構造体を記述します。
このパッケージを利用したい時は
module bar
import foo_pkg::w_hoge_t;
(...);
...
endmodule
の様にしてパッケージ"foo_pkg"内で用意されている構造体"w_hoge_t"をモジュール”bar”で参照できる様にします。また特定の構造体ではなくパッケージ内の全てをとりあえず参照したい場合はワイルドカード"*"で構造体を指定します。さらに、同じ用法でパッケージ内で別のパッケージを参照することもできます。パッケージは共用する目的で使用するのでユーザー定義型以外にパラメータなども記述しておいてそれらをモジュール間で共用することができます。
always_combとfor文の組み合わせで特定パターンの組み合わせ回路を記述する際、モジュールにパラメータを持たせて各モジュール毎に異なる値を使用するのに用いますが、パッケージ内に記述しているものはあくまで共用目的なので、個別に値を使用したい時には使えません。