[Veryl] Genericsの使いどころ
はじめに
VerylはSystemVerilogの代替として開発されている新興のHDLです。
昨年のアドベントカレンダーで紹介した、クロック・リセットの抽象化など、RTLを効率よくコーディングするための機能が数多く実装されています。
その1つとして、Genericsがあります。SystemVerilogのパラメータ化機能を強化した機能ですが、「パラメータ化との違いが分からない」、「使いどころがよく分からない」などの意見を聞くことがあります。本稿では、Genericsでしかできない、Verylならではの書き方を紹介したいと思います。
インスタンスするモジュールを可変にする
ASIC開発では、SRAMセルをインスタンスする必要があったり、シンクロナイザの実装にマルチビットFFセルを使用するなど、特定のセルをインスタンスすることが多くあります。そのような使い方に対応する必要がある共通モジュールをSystemVerilogで実装する場合、セルのインスタンス部分を `includeに切り出すなどの対応が必要です。例えば、SRAMを使用したFIFOの場合、SRAMのインスタンス部分を別ファイルに切り出します。また、深さや幅などに応じて、使用するSRAMセルも異なるので、その違いに対応する必要もあります。
module large_fifo;
`include "sram_inst.svh" // SRAMのインスタンス部分を"sram_inst.svh"に切り出す
endmodule
一方、VerylのGenericsでは、仮引数の型にモジュールのプロトタイプを型に取れ、また、インスタンス記述にプロトタイプで型付けされた仮引数を使うことが出来ます。
module large_fifo::<SRAM_CELL: sram_cell_pram> {
inst u_sram: SRAM_CELL (
// ...
);
}
使用するSRAMセルは、large_fifoインスタンス時に指定します。
inst u_fifo_256x32: large_fifo::<sram_256x32> (
// ...
);
inst u_fifo_512x16: large_fifo::<sram_512x16> (
// ...
);
VerylのGenericsでは、「中でインスタンスするモジュールを可変にしたい」と言うユースケースに対して、特殊な対応をすることなく、直接的な対応をすることが出来ます。
関数をパラメータ化する
入出力の幅違いの関数を定義したいなど、関数をパラメータ化したい場合は少なくないです。しかし、SystemVerilogは直接的にはサポートしておらず、「仮想クラスの静的関数として実装する」と言う間接的な方法でサポートしていますが、EDAツールによってサポートしていたり・していなかったりするので、一般的に使える方法ではありません。
virtual class selector #(
parameter int N = 2,
parameter int WIDTH = 8
);
static function select(
input logic [$clog2(N)-1:0] select,
input logic [N-1:0][WIDTH-1] data
);
return data[select];
endfunction
endclass
always_comb begin
a = selector #(4, 16)::select(b, c);
d = selector #(8, 32)::select(e, f);
end
Verylでは、functionやtaskもGenerics化することができます。上記のような回りくどい方法に依ることなく、functionやtask自身にパラメータを持たせることが出来ます。
function mux::<N: u32, WIDTH: u32> (
select: input logic<$clog2(N)>,
data : input logic<N, WIDTH> .
) -> logic<WIDTH> {
return data[select];
}
always_comb {
a = mux::<4, 16>(b, c);
d = mux::<8, 32>(e, f);
}
パラメータと型をひとまとめにする
AXIなどのバスプロトコルを扱うデザインでは、アドレス幅やデータ幅など多くのパラメータを持っていて、また、これらのパラメータから導出される型も多く持っています。取り回しを考えると、パラメータと型はひとまとめにして扱いたいところですが、SystemVerilogで直接的に行う方法はありません。
Verylでは、packageもGenerics化できるため、Generics化したpackageをパラメータと型をひとまとめにする器として使うことが出来ます。また、引数としてpacakgeを取るGenerics化したモジュールを定義できるので、下位モジュールへの伝搬も容易に行うことが出来ます。
package axi_pkg::<
id_width : u32,
address_width: u32,
data_width : u32,
// ...
> for axi_proto_pkg {
const ID_WIDTH : u32 = id_width ;
const ADDRESS_WIDTH: u32 = address_width;
const DATA_WIDTH : u32 = data_width ;
type axi_id : logic<ID_WIDTH> ;
type axi_address: logic<ADDRESS_WIDTH>;
type axi_data : logic<DATA_WIDTH> ;
// ...
}
interface axi_if::<AXI_PKG: axi_prot_pkg> {
var awready: logic ;
var awvalid: logic ;
var awid : AXI_PKG::axi_id ;
var awaddr : AXI_PKG::axi_address;
// ...
}
alias package pkg = axi_pkg::<8, 32, 64>; // aliasを定義することで、Generics引数の指定を省略できる
inst a_if: axi_if::<pkg>[2];
inst b_if: axi_if::<pkg>;
inst u_arbiter: axi_arbiter::<pkg> (
slave_if : a_if,
master_if: b_if,
// ...
);
最後に
本稿では、Genericsの代表的な使い方を3例紹介しました。しかし、Genericsは新しい機能のため、まだまだこなれていないのが実情です。ですので、「こう言うことがしたいが、どう書いたらいいか?」や「こういう機能が欲しい」、「バグを踏んだ」などがあれば、是非とも、GitHubやDiscordに報告していただけると、ありがた山です(あと2回で終わりとか信じ難い)。