アトリビュート
アトリビュートは変数宣言などいくつかの宣言に注釈を付けることができます。
sv アトリビュート
sv アトリビュートは SystemVerilog のアトリビュートを表し、(* *) という形式の SystemVerilog アトリビュートに変換されます。
module ModuleA {
#[sv("ram_style=\"block\"")]
let _a: logic<10> = 1;
#[sv("mark_debug=\"true\"")]
let _b: logic<10> = 1;
}
allow アトリビュート
allow アトリビュートは指定されたリントチェックを無効化するために使用できます。
module ModuleA {
#[allow(unused_variable)]
let a: logic<10> = 1;
}
指定可能なリント名は以下の通りです。
unused_variableunassign_variablemissing_reset_statementmissing_portinitial_assignmultiple_assign
ポータブルでない構文
initial_assign と multiple_assign は他のリント名とは性質が異なり、ポータブルでない構文、すなわち動作するかどうかがターゲットデバイスやツールチェーンに依存する構文を明示的に許可するためのものです。これらは対象となる変数の宣言に記述します。
initial_assign は initial ブロックでその変数へ代入することを許可します。これはターゲットデバイスがコンフィギュレーション時に変数を初期化することに依存しており、ASIC の合成ツールはこれを無視します。$readmemh や $readmemb のような関数呼び出しの出力引数を介した代入にも同様にこのアトリビュートが必要です。initial ブロックの内部で宣言された変数は設計の状態ではなく手続き的なローカル変数であるため、アトリビュートは不要です。
module ModuleA (
i_clk: input clock,
) {
#[allow(initial_assign)]
var count: logic<16>;
#[allow(initial_assign)]
var rom: logic<32> [1024];
initial {
count = '0;
$readmemh("rom.hex", rom);
}
always_ff {
count = count + 1;
}
#[allow(unused_variable)]
let _a: logic<32> = rom[0];
}
initial ブロックは駆動プロセスではないため、initial_assign の付いた変数は上の count のようにプロセスから駆動することもできます。そのために multiple_assign は必要ありません。
multiple_assign は複数のプロセスからその変数へ代入することを許可します。これは FPGA における真のデュアルポート SRAM の推論に必要です。
module ModuleB (
i_clk0 : input 'a clock ,
i_clk1 : input 'b clock ,
i_en0 : input 'a logic ,
i_en1 : input 'b logic ,
i_addr0: input 'a logic<5> ,
i_addr1: input 'b logic<5> ,
i_data0: input 'a logic<32>,
i_data1: input 'b logic<32>,
o_data : output 'a logic<32>,
) {
#[allow(multiple_assign)]
var ram: 'a logic<32> [32];
always_ff (i_clk0) {
if i_en0 {
ram[i_addr0] = i_data0;
}
}
unsafe (cdc) {
always_ff (i_clk1) {
if i_en1 {
ram[i_addr1] = i_data1;
}
}
}
assign o_data = ram[i_addr0];
}
SystemVerilog では always_ff で書き込まれる変数を他のプロセスから書き込むことは禁止されています。そのため、いずれかのアトリビュートが付いた変数に書き込む always_ff は通常の always として出力されます。always_comb にも同じ制限がありますが、こちらは変換されないため、複数の always_comb/assign 宣言から一つの変数を駆動するコードは Veryl では受理されても SystemVerilog のツールでは拒否されます。
これらの構文をサポートしないターゲットのプロジェクトに紛れ込まないよう、依存プロジェクトのコードでこれらを受け入れるかどうかは利用側が [lint.portability] で決定します。自身のコードではアトリビュートは常に尊重されます。
ifdef/ifndef/elsif/else アトリビュート
ifdef と ifndef アトリビュートは定義された値によってコードブロックを有効にするかどうかを制御するために使用することができます。さらに、ifdef と ifndef のついたコードブロックに続けてオプションとして elsif と else アトリビュートの付いたブロックを書くこともできます。
以下の例はこれらのアトリビュートの使用方法と、各コードブロックが定義された値によって有効になる様子を示しています。
ifdef/elsif/elseの順に宣言されたアトリビュート- もし
DEFINE_Aが定義されていれば、#[ifdef(DEFINE_A)]のついたコードブロック(コードブロックa)が有効になり、#[ifndef(DEFINE_B)]と#[else]のついたコードブロック(コードブロックbとc)は無効になります。 DEFINE_Aが定義されておらず、DEFINE_Bが定義されていれば、#[elfif(DEFINE_B)]のついたコードブロック(コードブロックb)が有効になり、#[ifndef(DEFINE_A)]と#[else]のついたコードブロック(コードブロックaとc)は無効になります。DEFINE_AとDEFINE_Bが定義されていなければ、#[else]のついたコードブロックが有効になり、#[ifndef(DEFINE_A)]と#[elsif(DEFINE_B)]のついたコードブロック(コードブロックaとb)は無効になります。
- もし
ifndef/elseの順に宣言されたアトリビュート- もし
DEFINE_Dが定義されていなければ、#[ifndef(DEFINE_D)]のついたコードブロック(コードブロックd)が有効になり、#[else]のついたコードブロック(コードブロックe)は無効になります。 DEFINE_Dが定義されていれば、#[else]のついたコードブロック(コードブロックe)が有効になり、#[ifndef(DEFINE_D)]のついたコードブロック(コードブロックd)は無効になります。
- もし
module ModuleA {
#[ifdef(DEFINE_A)]
{
// コードブロック a
let _a: logic<10> = 1;
}
#[elsif(DEFINE_B)]
{
// コードブロック b
let _a: logic<10> = 2;
}
#[else]
{
// コードブロック c
let _a: logic<10> = 3;
}
#[ifndef(DEFINE_D)]
{
// コードブロック d
let _b: logic<10> = 4;
}
#[else]
{
// コードブロック e
let _b: logic<10> = 5;
}
}
生成されたコードにおける末尾カンマ周りの複雑な調整を回避するため、カンマ区切りリストの最後のアイテムにifdefをつけることは禁止されています。
expand アトリビュート
expand アトリビュートが設定されているとき、modport のような構造化されたポートはVerilog のポートに展開されます。合成ツールによってはトップモジュールがそのようなポートを含んではならない場合があり、そのような場合にこのアトリビュートを使うことができます。使用可能な引数は以下の通りです。
modport: ポート方向がmodportのポートを展開する
interface InterfaceA::<W: u32> {
var ready: logic ;
var valid: logic ;
var data : logic<W>;
modport master {
ready: input ,
valid: output,
data : output,
}
modport slave {
ready: output,
valid: input ,
data : input ,
}
}
#[expand(modport)]
module ModuleA (
slave_if : modport InterfaceA::<8>::slave [4],
master_if: modport InterfaceA::<8>::master [4],
) {
for i in 0..4 :g {
connect slave_if[i] <> master_if[i];
}
}
module ModuleB {
inst a_if: InterfaceA::<8> [4];
inst b_if: InterfaceA::<8> [4];
inst u: ModuleA (
slave_if : a_if,
master_if: b_if,
);
}
align アトリビュート
align アトリビュートはフォーマッタの垂直方向の整列を制御することができます。number が align の引数として指定されたとき、全ての数値は整列されます。identifier も使用可能です。
module ModuleA {
let a : logic<32> = 1;
let aa : logic<32> = 1;
let aaa: logic<32> = 1;
let _b: logic = {
a[0] repeat 1, a[0] repeat 1, aa[1] repeat 8, aa[1] repeat 8, aaa[2] repeat 16, aaa[2] repeat 16, a[0] repeat 1,
aa[1] repeat 8, aaa[2] repeat 16, a[0] repeat 1,
};
#[align(number, identifier)]
let _c : logic = {
a [0 ] repeat 1 , a [0 ] repeat 1 , aa [1 ] repeat 8 , aa [1 ] repeat 8 , aaa[2 ] repeat 16,
aaa[2 ] repeat 16, a [0 ] repeat 1 , aa [1 ] repeat 8 , aaa[2 ] repeat 16, a [0 ] repeat 1 ,
};
}
fmt アトリビュート
fmt アトリビュートはフォーマットの方法を制御することができます。以下の引数がサポートされています。
compact: 改行なしのコンパクトなフォーマット
module ModuleA {
#[fmt(compact)]
{
inst u1: $sv::Module #( A: 1, B: 2 ) ( x: 1, y: _ );
inst u2: $sv::Module #( A: 1, B: 2 ) ( x: 1, y: _ );
inst u3: $sv::Module #( A: 1, B: 2 ) ( x: 1, y: _ );
inst u4: $sv::Module #( A: 1, B: 2 ) ( x: 1, y: _ );
}
}
skip:veryl fmtはモジュール、インターフェース、パッケージをフォーマットしない