型のパラメータ化と interface based typedef
石谷 @PEZY Computing です。SystemVerilog Advent Calendar 2020 の第3篇目です。
第1篇目、第2篇目はこちら。
SystemVerilog では、
- 型のパラメータ化
- interface 内で定義された型の参照
をすることができます。
今回は、これらを紹介し、応用例として、ハンドシェイクプロトコルを実装する、より一般化した interface および機能モジュールを例示していきます。
型のパラメータ化
SystemVerilog では、数値だけではなく、型をパラメータとして宣言することができます。宣言の際は、type
キーワードを使います。
module foo_bar #(
parameter type DATA_TYPE = logic
)(
input DATA_TYPE i_d,
output DATA_TYPE o_d
);
endmodule
インスタンスする際に、DATA_TYPE
に所望の型を与えます。
typedef enum logic [1:0] {
FOO_0,
FOO_1
} foo_enum;
foo_enum foo_0;
foo_enum foo_1;
foo_bar #(
.DATA_TYPE (foo_enum )
) u_foo (
.i_d (foo_0 ),
.o_d (foo_1 )
);
logic [1:0] bar_0;
logic [1:0] bar_1;
foo_bar #(
.DATA_TYPE (logic[1:0] )
) u_bar (
.i_d (bar_0 ),
.o_d (bar_1 )
);
enum
では、厳密な型チェックがされます。なので、enum
を使う場合は、
- 出力ポートの型と、接続先の型
- 代入の両辺の型
を合わせる必要があります。上記の例の場合では、以下のコードは文法エラーになります。
foo_enum foo_0;
foo_enum foo_1;
logic [1:0] bar_0;
logic [1:0] bar_1;
assign foo_0 = bar_0; // Syntax Error
foo_bar #(
.DATA_TYPE (logic[1:0] )
) u_bar (
.i_d (bar_1 ),
.o_d (foo_1 ) // Syntax Error
);
なので、FIFO 等の共通モジュールは、enum
の使用を考慮して、型のパラメータ化は必須と言えます。
interface based typedef
interface 内で定義された (typedef された) 型は、そのインスタンスの接続先のモジュール内で使うことができます。これは interface based typedef
と呼ばれ、LRM (IEEE 1800-2017) では 6.18 User-defined types
にその説明があります。
interface foo_if #(int W = 1);
typedef logic [W-1:0] foo_type;
foo_type foo_data;
modport mp (input foo);
endinterface
module bar (
foo_if.mp foo_port
);
typedef foo_port.foo_type foo_type; // これ
foo_type foo_data;
assign foo_data = foo.foo_data;
endmodule
module baz;
foo_if #(1) foo_if_1();
bar u_bar_1 (foo_if_1);
foo_if #(2) foo_if_2();
bar u_bar_2 (foo_if_2);
endmodule
上記の例では、foo_if
内で定義された foo_type
を foo_port
を介して、module bar
内に取り込んでいます。
module bar
自体はパラメータ化されていませんが、
- u_bar_1 の foo_type は
logic [0:0]
- u_bar_2 の foo_type は
logic [1:0]
となります。
応用例として、型定義を一か所にまとめるために使うことができます。
コンフィグレーションパラメータから導かれる型を、それぞれのモジュールで定義するのは、非常に面倒です。
そこで、interface 内に型を定義し、その interface を介して型を取り込むことで、型の定義を一か所に纏められるようになります。以下が使用例になります。
- https://github.com/taichi-ishitani/tnoc/blob/master/rtl/common/tnoc_types.sv
- https://github.com/taichi-ishitani/tnoc/blob/master/rtl/common/tnoc_flit_if.sv#L21
応用例
応用例として、valid/ready 方式のハンドシェイクを実装する interface のより一層の一般化を行います。
まずは interface の定義です。当該 interface は valid/ready/data を持ち、data の型が DATA_TYPE
としてパラメータ化されています。
interface handshake_if #(
type DATA_TYPE = logic
);
typedef DATA_TYPE __data_type;
logic valid;
logic ready;
DATA_TYPE data;
function automatic logic ack();
return valid && ready;
endfunction
modport master (
output valid,
input ready,
output data,
import ack
);
modport slave (
input valid,
output ready,
input data,
import ack
);
型をパラメータ化しているので、enum
を直接使えますし、data が構造体の場合、そのメンバーフィールドを直接参照することも可能です。
typedef enum logic {
FOO_0,
FOO_1
} foo_enum;
handshake_if #(foo_enum) foo_if();
assign foo_if.data = FOO_0;
typedef struct packed {
logic bar_0;
logic bar_1;
} bar_struct;
handshake_if #(bar_struct) bar_if();
assign bar_if.data.bar_0 = '0;
assign bar_if.data.bar_1 = '1;
型パラメータ DATA_TYPE
を __data_type
として typedef
しているところも、注目点です。
こうすることで、interface based typedef
を用いて、接続先に DATA_TYPE
を取り込むことができるようになります。
次の機能モジュールです。ここでは FIFO を実装し、以下のようになります。
module handshake_fifo #(
parameter int DEPTH = 2
)(
input var i_clk,
input var i_rst_n,
handshake_if.slave slave_if,
handshake_if.master master_if
);
typedef slave_if.__data_type __data_type;
logic push;
logic pop;
logic empty;
logic full;
always_comb begin
slave_if.ready = !full;
master_if.valid = !empty;
end
always_comb begin
push = slave_if.ack();
pop = master_if.ack();
end
common_fifo #(
.DEPTH (DEPTH ),
.TYPE (__data_type )
) u_fifo (
.i_clk (i_clk ),
.i_rst_n (i_rst_n ),
.o_empty (empty ),
.o_full (full ),
.i_push (push ),
.i_data (slave_if.data ),
.i_pop (pop ),
.o_data (master_if.data )
);
endmodule
interface based typedef
を用いて、DATA_TYPE
を __data_type
として取り込んでいます。なので、handshake_fifo
自体には型に関するパラメータを持たせる必要はありません。
そして、以下が使用例になります。
typedef enum logic {
FOO_0,
FOO_1
} foo_enum;
handshake_if #(foo_enum) foo_if[2]();
handshake_fifo u_foo_fifo (
.i_clk (i_clk ),
.i_rst_n (i_rst_n ),
.slave_if (foo_if[0] ),
.master_if (foo_if[1] )
);
typedef struct packed {
logic bar_0;
logic bar_1;
} bar_struct;
handshake_if #(bar_struct) bar_if[2]();
handshake_fifo u_bar_fifo (
.i_clk (i_clk ),
.i_rst_n (i_rst_n ),
.slave_if (bar_if[0] ),
.master_if (bar_if[1] )
);
まとめ
型のパラメータ化
と interface based typedef
を用いれば、汎用性と利便性が高いモジュール定義を行うことが可能となります。
ただ、interface based typedef
は、ツールの対応がこなれていない為か、バグを踏むことがあるので、要注意です。
例えば、これ。