はじめに
Verilogを使ったことがないVHDLユーザが
SystemVerilogを使いはじめてハマったところを随時まとめていきます。
LRMを読んだら参考ページを追記します。
開発環境
- Windows 10 64bit
- Python 3.7.0
- vunit-hdl 4.0.8
- ModelSim-Intel FPGA Edition
ハマったところ
case文内に複数のif文を書く
VHDLでcase文内に2つのif文を書く場合はそのまま書けば問題ありません。
case state is
when S0 =>
if cond0 = '1' then
a := b;
end if;
if cond1 = '1' then
c := d;
end if;
when S1 =>
-- ...
end case;
これをそのままSystemVerilogの文法に置き換えると2つ目のif文がエラーになります。
case (state)
S0 :
if (cond0) begin
a = b;
end
if (cond1) begin // illegal
c = d;
end
S1 :
// ...
endcase
2つのif文を書くためにはS0 : begin ... end
とする必要があります。
case (state)
S0 : begin
if (cond0) begin
a = b;
end
if (cond1) begin
c = d;
end
end
S1 :
// ...
endcase
根本的にstatementが2つ以上ある場合はbegin ... end
で囲う必要があるようです。
テストベンチのクロックでalwaysを使わずに書く
Verilogのテストベンチでは以下のようにalways
でクロックを生成できます。
always begin
#CLK_PERIOD;
clk = ~clk;
end
上記で問題はないですが、将来的にSystemVerilogのLinterで
always_comb
, always_ff
, always_latch
のみ許可しalways
を禁止にしたいと考えていたため
always
を使用しない書き方を探していました。
以下のようにinitial
とforever
でクロックを生成することができます。
initial begin
clk = 1'b0;
forever #(CLK_PERIOD / 2) clk <= ~clk;
end
VHDLのaggregate assignmentをどう書くか
例えば何かの処理で符号付固定小数点の信号を正の最大値にしたい場合、
VHDLではaggregate assignmentを使って以下のように書けます。
signal a : signed(DATA_BITS - 1 downto 0);
a <= (DATA_BITS - 1 => '0', others => '1');
SystemVerilogでは$bits
を使って以下のように書けます。
logic signed[DATA_BITS - 1 : 0] a;
assign a = {1'b0, {($bits(a) - 1){1'b1}}};
正の最大値という意味では以下の記述のほうがわかりやすいですが
aが32bitより大きくなったときにエラーになりそうなので避けています。(動作未確認)
assign a = (2 ** ($bits(a) - 1)) - 1;
stringを含むstructの配列を作る
SystemVerilogではstructを使うとテストパターンを見やすく書けます。
localparam DATA_BITS = 16;
typedef struct{
string name;
logic signed[0 : 1][DATA_BITS - 1 : 0] test_data;
logic signed[DATA_BITS - 1 : 0] ans_data;
} test_pattern_type;
localparam test_pattern_type TEST_PATTERN = '{
name : "0 -> + 1",
test_data : '{16'h0000, 16'h0001},
ans_data : 16'h0001
};
ただし、以下のように変数の左側で配列の要素数を宣言すると
TEST_PATTERN
がpacked array
になるので
Cannot create a packed array from unpacked elements.
というエラーが出ます。
...
localparam TEST_PATTERN_NUM = 2;
localparam test_pattern_type[0 : TEST_PATTERN_NUM - 1] TEST_PATTERN = {
{
name : "0 -> + 1",
test_data : {16'h0000, 16'h0001},
ans_data : 16'h0001
},
{
name : "0 -> - 1",
test_data : {16'h0000, 16'hFFFF},
ans_data : 16'hFFFF
}
};
unpacked array
にするには変数の右側で配列の要素数を宣言します。
...
localparam test_pattern_type TEST_PATTERN[0 : TEST_PATTERN_NUM - 1] = '{
...
ただしunpacked array
は合成ツール上がエラーを出すことが多いので
合成用コードには使わない、テストコードでも最低限の使用にとどめておくのか無難です。
またunpacked array
の場合は配列の大きさだけを指定することができます。(参考コメント)
...
localparam test_pattern_type TEST_PATTERN[TEST_PATTERN_NUM] = '{
...
この場合は[0 : TEST_PATTERN_NUM - 1]
で宣言した場合と同じ順になりました。
参考
Interface arrayに対するfor
SystemVerilogではInterfaceをarrayとして使用できます。
例えば、複数個のADCが独立で並んでいるようなmoduleを作成する場合には以下のようにgenerateで書けます。
SystemVerilog では generate/endgenerate は文法上は省略可能です。(参考コメント)
しかし使用しているQuartusではエラーになったので、ここでは省略せずに書いています。
module adc_receiver_array #(
parameter IC_NUM = 2
) (
input logic clk,
input logic rst,
adc_ic_interface.receiver ic_if[IC_NUM - 1 : 0],
adc_data_interface.receiver data_if[IC_NUM - 1 : 0]
);
generate
for(genvar i = 0; i <= IC_NUM - 1; i++) begin : generate_ic_array
adc_receiver adc_receiver_inst (
.clk,
.rst,
.adc_ic_interface(ic_if[i]),
.adc_data_interface(data_if[i])
);
end
endgenerate
end module
一方で、互いの要素が独立でないときにalways内のforで使おうとするとエラーになります。
interface arrayは変数ではなくモジュールインスタンス扱いです。
このためインデックスはエラボレーション時に決定される必要があります。
しかし、always内のforのインデックスは実行時に評価されるのでエラーになります。
あまり良い例ではないですが、
すべての入力データがvalidの時に出力を更新するようなモジュールを以下のように書くとエラーになります。
interface data_interface;
logic valid;
logic signed[7 : 0] data;
modport master(output valid, data);
modport slave(input valid, data);
endinterface
module sample #(
localparam NUM = 2
) (
input logic clk,
data_interface.slave din_if[NUM - 1 : 0],
data_interface.master dout_if[NUM - 1 : 0]
);
logic valid;
always_comb begin
valid = 1'b1;
for(int i = 0; i <= INPUT_NUM - 1; i++) begin // illegal
valid = valid & din_if[i].valid;
end
end
generate
for(genvar i = 0; i <= NUM - 1; i++) begin : output_ff_array
always_ff @(posedge clk) begin
dout_if[i].valid <= valid;
if (valid) begin
dout_if[i].data <= din[i].data;
end
end
end
endgenerate
end module
このような場合には一度別の変数にassignする必要があります。
// ...
logic [NUM - 1 : 0] valid_array;
logic valid;
generate
for(genvar i = 0; i <= NUM - 1; i++) begin : generate_valid_array
assign valid_array[i] = din_if[i].valid;
end
endgenerate
always_comb begin
valid = 1'b1;
for(int i = 0; i <= INPUT_NUM - 1; i++) begin // OK
valid = valid & valid_array[i];
end
end
// ...
本質的でない話(折りたたみ)
上記の`always_comb`はただのリダクション演算なので `for`を使わずに`assign valid = &valid_array;`と書けます。参考
PRHYZMICA – BLOG - System Verilog Array of Interfaces
Vivadoのバグではないかとの記載がありますが言語仕様として正しいようです。
inoutの接続
SystemVerilogでもinout
のポートを作ることができますが、
以下のようにlogic
型でinout
のポート同士を接続すると合成時にエラーになります。
module module_A (
inout logic inout_port // illegal
);
module_B inst(
.inout_port(inout_port)
);
endmodule
inout
は複数のドライバを持つ可能性があるため、wire
を指定する必要があります。
module module_A (
inout wire inout_port
);
module_B inst(
.inout_port(inout_port)
);
endmodule
case文で複数条件で共通処理をする
stateがS1またはS2のときに同じ処理をするようにcase文を書く場合、
VHDLでは以下のように|
でorの条件を書くことができます。
case state is
when S0 =>
a <= b;
when S1 | S2 =>
a <= c;
-- ...
end case;
SystemVerilogの場合は,
で複数の条件を書くことができます。
case (state)
S0 :
a = b;
S1, S2 :
a = c;
// ...
endcase
参考
LRM 12.5 Case Statement Syntax 12-3に以下の記述があります。
case_item_expression {, case_item_expression} : statement
配列への一括代入
例えば、配列の全てのビットに初期値を一括代入する場合、
VHDLではothers
を使って以下のように書けます。
constant INIT : std_logic := '1';
type data_array_type is array (HEIGHT - 1 downto 0) of std_logic_vector(WIDTH - 1 downto 0);
signal data_array : data_array_type := (others => (others => INIT));
SystemVerilogではVHDLと同じイメージで書くと以下のようになりますが
{}
の位置が複雑で読みにくくなります。
parameter logic INIT = 1'b1;
logic[HEIGHT - 1 : 0][WIDTH - 1 : 0] data_array = {HEIGHT{{WIDTH{INIT}}}};
default
を使うと以下のように読みやすくなります。
logic[HEIGHT - 1 : 0][WIDTH - 1 : 0] data_array = '{default : INIT};
また、default
の代わりに三項演算子を使って以下のようにも書けます。
logic[HEIGHT - 1 : 0][WIDTH - 1 : 0] data_array = INIT ? '1 : '0;