VECTOR表現とINTEGERを使用するのが適切なのはいつですか?


11

この質問への回答に関するコメントスレッド:VHDLエンティティでの誤った出力

「整数では、FPGAの内部ロジック表現を制御またはアクセスできませんが、SLVを使用すると、キャリーチェーンを効率的に利用するようなトリックを実行できます。」

では、内部表現にアクセスするために、整数のs を使用するよりもビット表現のベクトルを使用してコーディングする方が、どのような状況で適切であると思いましたか?そして、どのような利点を測定しましたか(チップ領域、クロック周波数、遅延などに関して)?


どうやら低レベルの実装を制御するだけの問題なので、測定するのは難しいと思います。
clabacchio

回答:


5

他の2つのポスターで提案されたコードを両方vectorinteger形式で記述しましたが、両方のバージョンができるだけ同じように動作するように注意しています。

シミュレーションの結果を比較し、Xilinx Spartan 6をターゲットにしてSynplify Proを使用して合成しました。以下のコードサンプルは実際のコードから貼り付けられているので、お気に入りのシンセサイザーで使用して、同じように動作するかどうかを確認できます。


ダウンカウンター

まず、David Kessnerによって提案されたダウンカウンター:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity downcounter is
    generic (top : integer);
    port (clk, reset, enable : in  std_logic; 
         tick   : out std_logic);
end entity downcounter;

ベクトルアーキテクチャ:

architecture vec of downcounter is
begin
    count: process (clk) is
        variable c : unsigned(32 downto 0);  -- don't inadvertently not allocate enough bits here... eg if "integer" becomes 64 bits wide
    begin  -- process count
        if rising_edge(clk) then  
            tick <= '0';
            if reset = '1' then
                c := to_unsigned(top-1, c'length);
            elsif enable = '1' then
                if c(c'high) = '1' then
                    tick <= '1';
                    c := to_unsigned(top-1, c'length);
                else
                    c := c - 1;
                end if;
            end if;
        end if;
    end process count;
end architecture vec;

整数アーキテクチャ

architecture int of downcounter is
begin
    count: process (clk) is
        variable c : integer;
    begin  -- process count
        if rising_edge(clk) then  
            tick <= '0';
            if reset = '1' then
                c := top-1;
            elsif enable = '1' then
                if c < 0 then
                    tick <= '1';
                    c := top-1;
                else
                    c := c - 1;
                end if;
            end if;
        end if;
    end process count;
end architecture int;

結果

コード的には、整数の1はto_unsigned()呼び出しを回避するため、私には望ましいように見えます。そうでなければ、選択することはあまりありません。

プロのSynplifyを通してそれを実行すると、top := 16#7fff_fffe#生成66個のLUTのためのvectorバージョンおよび64個のLUTのための integerバージョンを。どちらのバージョンもキャリーチェーンを多用しています。どちらも280MHzを超えるクロック速度を報告します。シンセサイザは、キャリーチェーンを上手に利用できる能力を備えています。RTLビューアを使用して、両方で同様のロジックが生成されることを確認しました。明らかに、コンパレータ付きのアップカウンタは大きくなりますが、整数とベクトルの両方で同じになります。


2 ** nカウンターによる除算

ajs410の提案:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity clkdiv is
    port (clk, reset : in     std_logic;
        clk_2, clk_4, clk_8, clk_16  : buffer std_logic);
end entity clkdiv;

ベクトルアーキテクチャ

architecture vec of clkdiv is

begin  -- architecture a1

    process (clk) is
        variable count : unsigned(4 downto 0);
    begin  -- process
        if rising_edge(clk) then  
            if reset = '1' then
                count  := (others => '0');
            else
                count := count + 1;
            end if;
        end if;
        clk_2 <= count(0);
        clk_4 <= count(1);
        clk_8 <= count(2);
        clk_16 <= count(3);
    end process;

end architecture vec;

整数アーキテクチャ

to_unsigned上記と同じ効果を明確に生成するビットを使用してピックオフすることを避けるために、いくつかのフープをジャンプする必要があります。

architecture int of clkdiv is
begin
    process (clk) is
        variable count : integer := 0;
    begin  -- process
        if rising_edge(clk) then  
            if reset = '1' then
                count  := 0;
                clk_2  <= '0';
                clk_4  <= '0';
                clk_8  <= '0';
                clk_16 <= '0';
            else
                if count < 15 then
                    count := count + 1;
                else
                    count := 0;
                end if;
                clk_2 <= not clk_2;
                for c4 in 0 to 7 loop
                    if count = 2*c4+1 then
                        clk_4 <= not clk_4;
                    end if;
                end loop; 
                for c8 in 0 to 3 loop
                    if count = 4*c8+1 then
                        clk_8 <= not clk_8;
                    end if;
                end loop; 
                for c16 in 0 to 1 loop
                    if count = 8*c16+1 then
                        clk_16 <= not clk_16;
                    end if;
                end loop; 
            end if;
        end if;
    end process;
end architecture int;

結果

この場合、コード的には、vectorバージョンの方が明らかに優れています。

合成結果に関して、この小さな例では、整数バージョン(ajs410が予測)はコンパレーターの一部として3つの追加のLUTを生成しますが、シンセサイザについては楽観的すぎましたが、非常に難読化されたコードで機能しています!


その他の用途

算術演算をラップアラウンドしたい場合、ベクトルは明らかに有利です(カウンターは単一行としても実行できます)。

vec <= vec + 1 when rising_edge(clk);

if int < int'high then 
   int := int + 1;
else
   int := 0;
end if;

少なくとも、そのコードから、作成者がラップアラウンドを意図していたことは明らかです。


実際のコードでは使用していませんが、熟考したもの:

「自然に折り返す」機能は、「オーバーフローによる計算」にも利用できます。加算/減算および乗算のチェーンの出力が制限されていることがわかっている場合、中間計算の上位ビットを(2の補数で)「ウォッシュで」出力されるため、格納する必要はありません。出力に到達するまでに。この論文にはその証拠が含まれていると言われていますが、簡単に評価するために少し密に見えました!コンピュータの追加とオーバーフローの理論-HL Garner

integerこの状況でsを使用すると、最終的にアンラップすることがわかっていても、ラップするとシミュレーションエラーが発生します。


フィリップが指摘したように、2 ** 31より大きい数が必要な場合は、ベクトルを使用するしかありません。


2番目のコードブロックにはvariable c : unsigned(32 downto 0);... c33ビット変数ではないですか?
clabacchio

@clabacchio:はい、「キャリービット」にアクセスしてラップアラウンドを確認できます。
Martin Thompson

5

VHDLを作成するときは、SIGNALSに整数(int)ではなくstd_logic_vector(slv)を使用することを強くお勧めします。(一方、ジェネリック、定数、変数にはintを使用すると非常に便利です。)簡単に言えば、int型の信号を宣言するか、整数の範囲を指定する必要がある場合は、おそらく何か問題でも。

intの問題は、VHDLプログラマーがintの内部ロジック表現が何であるかを理解していないため、それを利用できないことです。たとえば、1から10の範囲のintを定義した場合、コンパイラがこれらの値をどのようにエンコードするかわかりません。うまくいけば、それは4ビットとしてエンコードされますが、それ以上はわかりません。FPGA内の信号をプローブできれば、「0001」から「1010」、または「0000」から「1001」にエンコードされる可能性があります。また、人間にとってまったく意味のない方法でエンコードされている可能性もあります。

代わりに、intの代わりにslvを使用する必要があります。これは、エンコードを制御し、個々のビットに直接アクセスできるためです。後で説明するように、直接アクセスできることが重要です。

個々のビットにアクセスする必要があるときはいつでも、intをslvにキャストできますが、これは非常に面倒で非常に高速になります。それは、両方の世界で最高のものではなく、両方の世界で最悪のものを取得するようなものです。あなたはコードがコンパイラにとって最適化するのが難しく、読むことはほとんど不可能です。これはお勧めしません。

だから、私が言ったように、slvではビットエンコーディングを制御し、ビットに直接アクセスできます。これで何ができるでしょうか?いくつか例を示します。4,294,000,000クロックごとに1回パルスを出力する必要があるとしましょう。これは、intでこれを行う方法です。

signal count :integer range 0 to 4293999999;  -- a 32 bit integer

process (clk)
begin
  if rising_edge(clk) then
    if count = 4293999999 then  -- The important line!
      count <= 0;
      pulse <= '1';
    else
      count <= count + 1;
      pulse <= '0';
    end if;
  end if;
end process;

そして、slvを使用する同じコード:

use ieee.numeric_std.all;
signal count :std_logic_vector (32 downto 0);  -- a 33 bit integer, one extra bit!

process (clk)
begin
  if rising_edge(clk) then
    if count(count'high)='1' then   -- The important line!
      count <= std_logic_vector(4293999999-1,count'length);
      pulse <= '1';
    else
      count <= count - 1;
      pulse <= '0';
    end if;
  end if;
end process;

このコードのほとんどは、少なくとも結果のロジックのサイズと速度の点で、intとslvの間で同じです。もちろん、一方がカウントアップし、もう一方がカウントダウンしますが、この例ではそれは重要ではありません。

違いは「重要なライン」にあります。

intの例では、これは32入力コンパレータになります。ザイリンクスSpartan-3が使用する4入力LUTでは、11のLUTと3レベルのロジックが必要になります。一部のコンパイラは、これを減算に変換し、キャリーチェーンを使用して32個のLUTに相当する範囲をカバーしますが、3レベルのロジックよりも高速に実行する場合があります。

slvの例では、32ビットの比較がないため、「LUTはゼロ、ロジックのレベルはゼロ」です。唯一のペナルティは、カウンターが1ビット余分であることです。この追加のカウンタービットの追加のタイミングはすべてキャリーチェーン内にあるため、「ほぼゼロ」の追加のタイミング遅延があります。

もちろん、これは極端な例です。ほとんどの人は、この方法で32ビットカウンターを使用しないでしょう。小さいカウンターには適用されますが、その差はそれほど大きくありませんが、それほど劇的ではありません。

これは、intよりもslvを使用してタイミングを高速化する方法の1つの例にすぎません。slvを利用する方法は他にもたくさんあります-想像力を働かせるだけです。

更新: "if(count-1)<0"でのintの使用に関するMartin Thompsonのコメントに対処するための項目を追加しました

(注:「count <0の場合」を意味していると思います。これにより、slvバージョンと同等になり、余分な減算の必要がなくなります。)

状況によっては、意図したロジック実装が生成される可能性がありますが、常に機能するとは限りません。それはあなたのコードとあなたのコンパイラがどのようにint値をエンコードするかに依存します。

コンパイラ、およびintの範囲の指定方法によっては、ゼロのint値がFPGAロジックに入るときに「0000 ... 0000」のビットベクトルにエンコードされない可能性があります。バリエーションを機能させるには、「0000 ... 0000」にエンコードする必要があります。

たとえば、intを-5〜+5の範囲で定義するとします。0の値は "0000"のような4ビットにエンコードされ、+ 5は "0101"として、-5は "1011"としてエンコードされることを期待しています。これは、典型的な2の補数のエンコード方式です。

ただし、コンパイラが2の補数を使用することを想定しないでください。異常ではありますが、1の補数は「より良い」ロジックになる可能性があります。または、コンパイラは一種の「偏った」エンコーディングを使用でき、-5は「0000」、0は「0101」、+ 5は「1010」としてエンコードされます。

intのエンコーディングが「正しい」場合、コンパイラはキャリービットの処理方法を推測します。しかし、それが正しくない場合、結果のロジックは恐ろしいものになります。

この方法でintを使用すると、妥当なロジックサイズと速度が得られる可能性がありますが、これは保証ではありません。別のコンパイラー(XSTからSynopsisなど)に切り替えるか、別のFPGAアーキテクチャーに切り替えると、正確に間違った事態が発生する可能性があります。

未署名/署名済みvs.slvは、さらに別の議論です。VHDLで非常に多くのオプションを提供してくれた米国政府委員会に感謝することができます。:) slvを使用します。これは、モジュールとコア間のインターフェイスの標準であるためです。それ以外、およびシミュレーションの他のいくつかのケースでは、符号付き/符号なしではなくslvを使用しても大きなメリットはないと思います。署名付き/署名なしがトライステート信号をサポートしているかどうかもわかりません。


4
David、これらのコードフラグメントは同等ではありません。1はゼロから任意の数までカウントアップします(高価な比較演算子を使用)。もう一方は、任意の数からゼロまでカウントダウンします。両方のアルゴリズムを整数またはベクトルのいずれかで記述できます。任意の数に数えると悪い結果が得られ、ゼロに数えると良い結果が得られます。ソフトウェアエンジニア、ホットループからもう少しパフォーマンスを絞り出す必要がある場合、ゼロまでカウントダウンすることに注意してください。
フィリップ

1
フィリップのように、これが有効な比較であるとは確信していません。整数の例をカウントダウンして使用した場合、if (count-1) < 0シンセサイザはキャリーアウトビットを推測し、slvの例とほとんど同じ回路を生成すると思います。また、unsigned最近はこのタイプを使用すべきではありません:)
Martin Thompson

2
@DavidKessner確かにあなたは徹底的で合理的な答えを提供してくれました、あなたは私の+1を獲得しました。私は尋ねなければなりません...なぜ設計全体の最適化について心配しているのですか?それを必要とするコードの領域に集中するか、互換性のためにインターフェイスポイント(エンティティポート)のSLVに集中するほうが良いのではないでしょうか。ほとんどのデザインでは、タイミングを満たし、デバイスに適合する限り、LUTの使用が最小限に抑えられることを特に気にしません。特に厳しい制約がある場合は、最適な設計を意識しますが、一般的なルールではありません。
akohlsmith

2
この回答に対する賛成票の数に少し驚いています。@ bit_vector @は確かにマイクロアーキテクチャのモデリングと最適化に適した抽象レベルですが、一般的な推奨事項では、信号とポートの@ integer @などの「高レベル」タイプは、私が奇妙に思うものです。これらの機能が提供する価値を知るための抽象化が不足しているため、複雑で読みにくいコードが十分にあるのを目にしてきました。
trondd 2013

2
@david素晴らしいコメント。多くの点でソフトウェア開発と比較して、まだ中世にいるのは事実ですが、Quartus統合シンセシスとSynplifyでの経験から、それほど悪いことではないと思います。それらは、レジスタのリタイミングや、読みやすさを維持しながらパフォーマンスを向上させるその他の最適化など、多くのことを処理できます。大多数がいくつかのツールチェーンとデバイスをターゲットにしているとは思えませんが、あなたのケースでは、最小公分母の要件を理解しています:-)。
trondd 2013

2

私のアドバイスは、両方を試してから、合成、マップ、配置配線レポートを確認することです。これらのレポートは、各アプローチが消費しているLUTの数を正確に示し、ロジックが動作できる最大速度も示します。

私はあなたがあなたのツールチェーンのなすがままであり、「正しい」答えはないというデビッド・ケスナーに同意します。合成は黒魔術であり、何が起こったかを知るための最良の方法は、生成されたレポートを注意深く完全に読むことです。ザイリンクスツールを使用すると、FPGA内を確認することもできます。各LUTのプログラミング方法、キャリーチェーンの接続方法、スイッチファブリックによるすべてのLUTの接続方法などが含まれます。

ケスナー氏のアプローチのもう1つの劇的な例として、1 / 2、1 / 4、1 / 8、1 / 16などの複数のクロック周波数が必要だとします。サイクルごとに常にカウントされる整数を使用できます。そして、その整数値に対して複数のコンパレーターがあり、各コンパレーター出力は異なるクロック分周を形成しています。コンパレータの数によっては、ファンアウトが不当に大きくなり、バッファリングのためだけに余分なLUTを消費し始める場合があります。SLVアプローチでは、ベクトルの各ビットを出力として使用します。


1

明らかな理由の1つは、符号付きと符号なしでは32ビット整数よりも大きな値が許可されることです。これはVHDL言語設計の欠陥であり、必須ではありません。VHDLの新しいバージョンはそれを修正でき、任意のサイズをサポートするために整数値を必要とします(JavaのBigIntと同様)。

それ以外は、ベクトルと比較して整数のパフォーマンスが異なるベンチマークについて聞いてとても興味があります。

ところで、Jan Decaluweはこれについて素晴らしいエッセイを書きました:これらのIntはCountin 'のために作られています


フィリップに感謝します(ただし、これは「内部表現へのアクセスによるアプリケーションの方が優れている」というわけではありません。これは私が本当に
Martin Thompson

そのエッセイはいいですが、根本的な実装とその結果のロジックの速度とサイズを完全に無視しています。私はデカルウェが言うことのほとんどに同意しますが、彼は合成の結果について何も述べていません。合成の結果は重要ではない場合もあれば、重要な場合もあります。ですから、それは判断の呼びかけです。

1
@David、私はJanが合成ツールが整数にどのように反応するかについて完全には詳しく述べないことに同意します。しかし、それは裁判の呼びかけではありません。合成結果を測定し、特定の合成ツールの結果を決定できます。OPは、パフォーマンスの違い(ある場合)を示すコードフラグメントと合成結果を生成するための挑戦として、彼の質問を意味したと思います。
フィリップ

@フィリップいいえ、合成結果をまったく気にしないかどうかを判断する必要があることを意味しました。合成結果自体が判断の呼びかけであるというわけではありません。

@DavidKessner OK。誤解しました。
フィリップ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.