LoginSignup
19
11

More than 3 years have passed since last update.

VHDLユーザがSystemVerilogでハマったところの記録

Last updated at Posted at 2019-11-29

はじめに

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文を書く場合はそのまま書けば問題ありません。

VHDL
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文がエラーになります。

SystemVerilog
case (state)
    S0 :
        if (cond0) begin
            a = b;
        end
        if (cond1) begin // illegal
            c = d;
        end
    S1 :
       // ...
endcase

2つのif文を書くためにはS0 : begin ... endとする必要があります。

SystemVerilog
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でクロックを生成できます。

SystemVerilog
always begin
    #CLK_PERIOD;
    clk = ~clk;
end

上記で問題はないですが、将来的にSystemVerilogのLinterで
always_comb, always_ff, always_latchのみ許可しalwaysを禁止にしたいと考えていたため
alwaysを使用しない書き方を探していました。

以下のようにinitialforeverでクロックを生成することができます。

SystemVerilog
initial begin
    clk = 1'b0;
    forever #(CLK_PERIOD / 2) clk <= ~clk;
end

VHDLのaggregate assignmentをどう書くか

例えば何かの処理で符号付固定小数点の信号を正の最大値にしたい場合、
VHDLではaggregate assignmentを使って以下のように書けます。

VHDL
signal a : signed(DATA_BITS - 1 downto 0);

a <= (DATA_BITS - 1 => '0', others => '1');

SystemVerilogでは$bitsを使って以下のように書けます。

SystemVerilog
logic signed[DATA_BITS - 1 : 0] a;

assign a = {1'b0, {($bits(a) - 1){1'b1}}};

正の最大値という意味では以下の記述のほうがわかりやすいですが
aが32bitより大きくなったときにエラーになりそうなので避けています。(動作未確認)

SystemVerilog
assign a = (2 ** ($bits(a) - 1)) - 1;

stringを含むstructの配列を作る

SystemVerilogではstructを使うとテストパターンを見やすく書けます。

SystemVerilog
    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_PATTERNpacked arrayになるので
Cannot create a packed array from unpacked elements.というエラーが出ます。

SystemVerilog
    ...
    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にするには変数の右側で配列の要素数を宣言します。

SystemVerilog
    ...
    localparam test_pattern_type TEST_PATTERN[0 : TEST_PATTERN_NUM - 1] = '{
    ...

ただしunpacked arrayは合成ツール上がエラーを出すことが多いので
合成用コードには使わない、テストコードでも最低限の使用にとどめておくのか無難です。

またunpacked arrayの場合は配列の大きさだけを指定することができます。(参考コメント)

SystemVerilog
    ...
    localparam test_pattern_type TEST_PATTERN[TEST_PATTERN_NUM] = '{
    ...

この場合は[0 : TEST_PATTERN_NUM - 1]で宣言した場合と同じ順になりました。

参考

電子回路日和 - Associated Array

Interface arrayに対するfor

SystemVerilogではInterfaceをarrayとして使用できます。
例えば、複数個のADCが独立で並んでいるようなmoduleを作成する場合には以下のようにgenerateで書けます。

SystemVerilog では generate/endgenerate は文法上は省略可能です。(参考コメント)
しかし使用しているQuartusではエラーになったので、ここでは省略せずに書いています。

SystemVerilog
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の時に出力を更新するようなモジュールを以下のように書くとエラーになります。

SystemVerilog
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する必要があります。

SystemVerilog
    // ...
    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のポート同士を接続すると合成時にエラーになります。

SystemVerilog
module module_A (
    inout logic inout_port // illegal
);
    module_B inst(
        .inout_port(inout_port)
    );
endmodule

inoutは複数のドライバを持つ可能性があるため、wireを指定する必要があります。

SystemVerilog
module module_A (
    inout wire inout_port
);
    module_B inst(
        .inout_port(inout_port)
    );
endmodule

case文で複数条件で共通処理をする

stateがS1またはS2のときに同じ処理をするようにcase文を書く場合、
VHDLでは以下のように|でorの条件を書くことができます。

VHDL
case state is
    when S0 =>
        a <= b;
    when S1 | S2 =>
        a <= c;
    -- ...
end case;

SystemVerilogの場合は,で複数の条件を書くことができます。

SystemVerilog
case (state)
    S0 :
        a = b;
    S1, S2 :
        a = c;
    // ...
endcase

参考

LRM 12.5 Case Statement Syntax 12-3に以下の記述があります。

SystemVerilog
    case_item_expression {, case_item_expression} : statement

配列への一括代入

例えば、配列の全てのビットに初期値を一括代入する場合、
VHDLではothersを使って以下のように書けます。

VHDL
    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と同じイメージで書くと以下のようになりますが
{}の位置が複雑で読みにくくなります。

SystemVerilog
    parameter logic INIT = 1'b1;

    logic[HEIGHT - 1 : 0][WIDTH - 1 : 0] data_array = {HEIGHT{{WIDTH{INIT}}}};

defaultを使うと以下のように読みやすくなります。

SystemVerilog
    logic[HEIGHT - 1 : 0][WIDTH - 1 : 0] data_array = '{default : INIT};

また、defaultの代わりに三項演算子を使って以下のようにも書けます。

SystemVerilog
    logic[HEIGHT - 1 : 0][WIDTH - 1 : 0] data_array = INIT ? '1 : '0;
19
11
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
11