この質問への回答に関するコメントスレッド:VHDLエンティティでの誤った出力
「整数では、FPGAの内部ロジック表現を制御またはアクセスできませんが、SLVを使用すると、キャリーチェーンを効率的に利用するようなトリックを実行できます。」
では、内部表現にアクセスするために、整数のs を使用するよりもビット表現のベクトルを使用してコーディングする方が、どのような状況で適切であると思いましたか?そして、どのような利点を測定しましたか(チップ領域、クロック周波数、遅延などに関して)?
この質問への回答に関するコメントスレッド:VHDLエンティティでの誤った出力
「整数では、FPGAの内部ロジック表現を制御またはアクセスできませんが、SLVを使用すると、キャリーチェーンを効率的に利用するようなトリックを実行できます。」
では、内部表現にアクセスするために、整数のs を使用するよりもビット表現のベクトルを使用してコーディングする方が、どのような状況で適切であると思いましたか?そして、どのような利点を測定しましたか(チップ領域、クロック周波数、遅延などに関して)?
回答:
他の2つのポスターで提案されたコードを両方vector
とinteger
形式で記述しましたが、両方のバージョンができるだけ同じように動作するように注意しています。
シミュレーションの結果を比較し、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ビューアを使用して、両方で同様のロジックが生成されることを確認しました。明らかに、コンパレータ付きのアップカウンタは大きくなりますが、整数とベクトルの両方で同じになります。
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より大きい数が必要な場合は、ベクトルを使用するしかありません。
variable c : unsigned(32 downto 0);
... c
33ビット変数ではないですか?
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を使用しても大きなメリットはないと思います。署名付き/署名なしがトライステート信号をサポートしているかどうかもわかりません。
if (count-1) < 0
シンセサイザはキャリーアウトビットを推測し、slvの例とほとんど同じ回路を生成すると思います。また、unsigned
最近はこのタイプを使用すべきではありません:)
私のアドバイスは、両方を試してから、合成、マップ、配置配線レポートを確認することです。これらのレポートは、各アプローチが消費しているLUTの数を正確に示し、ロジックが動作できる最大速度も示します。
私はあなたがあなたのツールチェーンのなすがままであり、「正しい」答えはないというデビッド・ケスナーに同意します。合成は黒魔術であり、何が起こったかを知るための最良の方法は、生成されたレポートを注意深く完全に読むことです。ザイリンクスツールを使用すると、FPGA内を確認することもできます。各LUTのプログラミング方法、キャリーチェーンの接続方法、スイッチファブリックによるすべてのLUTの接続方法などが含まれます。
ケスナー氏のアプローチのもう1つの劇的な例として、1 / 2、1 / 4、1 / 8、1 / 16などの複数のクロック周波数が必要だとします。サイクルごとに常にカウントされる整数を使用できます。そして、その整数値に対して複数のコンパレーターがあり、各コンパレーター出力は異なるクロック分周を形成しています。コンパレータの数によっては、ファンアウトが不当に大きくなり、バッファリングのためだけに余分なLUTを消費し始める場合があります。SLVアプローチでは、ベクトルの各ビットを出力として使用します。
明らかな理由の1つは、符号付きと符号なしでは32ビット整数よりも大きな値が許可されることです。これはVHDL言語設計の欠陥であり、必須ではありません。VHDLの新しいバージョンはそれを修正でき、任意のサイズをサポートするために整数値を必要とします(JavaのBigIntと同様)。
それ以外は、ベクトルと比較して整数のパフォーマンスが異なるベンチマークについて聞いてとても興味があります。
ところで、Jan Decaluweはこれについて素晴らしいエッセイを書きました:これらのIntはCountin 'のために作られています