2017年2月
FPGAにおける浮動小数点の計算速度
2017.02.20
FPGAの固定小数点の乗算であれば1クロックで動きますが、浮動小数点はどのくらいかかるのでしょうか?
以前、HLSで演算回路を作ろうとしたら、加算に11クロックかかるとかいうレポートが出てきて唖然としました。HDLで書いて、その実力を探ってみたいと思います。
![]()
実験に用いた回路は下の図のようなものです。
使ったLogiCoreは、Floating-point 5.0というもの。デバイスは、Kintex-7のXC7K160-1。浮動小数点のフォーマットは単精度です。

作る回路は、固定小数点で入ってきたIとQを浮動小数に直して、二乗して和を取って、固定小数点に戻すというものです。これらの回路をISE14.7とCoreGenで作ります。まずはfixed→floatからです。



レイテンシの部分を変えて、どのくらいリソース使用量が変わるかを試してみました。
latency LUT SLICE DSP WorstTime ---------------------------------- 1 173 58 0 6 204 78 0
fixed2floatはレイテンシを増やしてもLUTやSLICEの使用量が増えるだけで、あまり意味はありませんでした。レイテンシ1でも250MHzで動くようなので、これは1クロックでやるべきだと思います。
![]()
次は浮動小数点の乗算です。この場合は同じ値の二乗を取る回路になります。結果は、
latency LUT SLICE DSP WorstTime ---------------------------------- 1 194 139 6 8.2ns 100MHz OK 2 152 81 6 4.4ns 200MHz OK 3 154 76 6 3.6ns 250MHz OK 6 162 64 6 3.6ns
となって、レイテンシ1以上であれば使用量はそれほど変化しないことがわかります。クロックを250MHzにしてImplementすると、タイミングエラーが出て、
Source: inst_fix2floatQ/blk00000065 (FF) Destination: inst_Qsq/blk00000030 (FF) Requirement: 4.000ns Data Path Delay: 8.165ns (Levels of Logic = 5)(Component delays alone exceeds constraint) Clock Path Skew: -0.022ns (0.702 - 0.724) Source Clock: adcclk_i_BUFGP rising at 0.000ns Destination Clock: adcclk_i_BUFGP rising at 4.000ns Clock Uncertainty: 0.035ns
となるので、レイテンシ1の回路の遅延は頑張っても8.2ns程度かかるということがわかります。
100MHzならレイテンシ1で計算できるのですが、125MHz以上ならばレイテンシ2は欲しいところです。
![]()
浮動小数点で一番難しいのは、加減算だと思います。これもCoreGeneratorで作ります。


条件をいろいろ変えて実験してみると、下のような結果となりました。
latency LUT SLICE DSP WorstTime ---------------------------------- 1 183 74 2 15ns 2 213 105 2 7.8ns 100MHzOK 3 234 100 2 5.1ns 4 - - 2 4.7ns 200MHzOK 5 - - 2 3.9ns 6 215 90 2 4.8ns 250MHzOK 11 210 94 2 2.7ns
レイテンシ1では100MHzも通りません。100MHzで動作させるならレイテンシ2、200MHzで動作させるならレイテンシ4。250MHzで動作させるならレイテンシ6は必要です。
![]()
回路全体の動作は下の図ようになりました。
判明した結果をまとめると、以下のようになります。
リソース使用量はレイテンシによってあまり変化しない。したがって、クロック速度が間に合えば、レイテンシを遅くする必要はない。
浮動小数点数の乗算には8.5nsほど。加算には14~15nsかかる。
クロック100MHzならば乗算器のレイテンシは1でよく、200MHzならばレイテンシ2にする必要がある。
加算は遅く、14~15nsかかる。100MHzならばレイテンシ2、200MHzならばレイテンシ4、250MHzならばレイテンシ6は必要である。
クロックを速くしてもレイテンシがかさむし、消費電力が増えるので、クロックは100~200MHzくらいがちょうどよい。
![]()
なお、積和計算を行う場合は、加算の結果が出ないと積算もできないので、レイテンシが2ならばインターバルは2になります。加算の性能がシステム全体の性能に影響します。
今回はISEでやりましたが、HLSで作る場合でもツールにすべて任せるのではなく、どの程度のリソースと計算時間を使うかを頭の中で把握できていれば、高位合成でももっと良い回路が作れるようになると思います。
この成果をもとに、ある種の無線の回路を作ってみたところ、HLSだとインターバルに41クロックかかっていたのが、8クロックでできました。「この結果はここでは使わない」みたいなことが人間の頭ではわかるので、そういったことも含めての最適化なので、単純に5倍とは言えないのですが、最短のクロック数が何クロックであるかというゴールがわかるようになるので、HLSのディレクティブを指定するときにもやりがいが出るのではないかと思います。
これで、私も浮動小数点デビューできそうです。
Cosmo-Zのアクリルケース入り
2017.02.15
納品用のCosmo-Zをアクリルケースに納めました。
1つ目は18bit ADコンバータで拡張されたCosmo-Zです。
性能はなかなかのもので、無信号時のノイズは±5LSB程度(156μV程度)でした。
正弦波を入れてFFTでスペクトラムを見ても、歪率は-80~-90dBは行っていそうです。
![]()
もう一つは、全LEMOコネクタ化されたCosmo-Zです。
サクサクという感覚がたまりません。
FIRフィルタの実機テスト
2017.02.03
Cosmo-ZにFIRフィルタを入れて、実機でテストしています。
まずは、生の信号を見てください。

これは、ある高い周波数の正弦波をCosmo-Zでアンダーサンプリングし、デシメーションを行なった後、IQ検波した信号です。ADCの周波数と正弦波の周波数がずれているため、うなりの成分が位相が90度ずれた2つの正弦波として見えています。
信号の説明はともかく、ノイズが乗っています。
これに、帯域300kHzのFIRフィルタを通してみました。
パッと見、ノイズが減っています。
一部分を拡大してみますと、
28クロックほど遅延し、ノイズがかなり平滑化されているのがわかります。
周波数で見てみると、300kHzあたりで綺麗にカットオフされているのがわかります。
カットオフを600kHzと1.2MHzに変えてみると、
このように、狙ったとおりに帯域が広がっていくのがわかります。
![]()
係数を変えればHPFにもなります。
HPFを通すと、時系列でみたときの1つ1つの振幅がめちゃめちゃ協調されますね。
![]()
最後にバンドパスフィルタを試してみます。
FIRフィルタの設計は、「石川高専 山田洋士 研究室ホームページ」のページが便利です。望みの特性を入力すると、オンラインでFIRの計数を生成することができます。
41タップ、0.2~0.4ωc、Hamming窓のBPFを生成し、各係数を33554432倍してCoreGenに入れます。CoreGenで表示された周波数特性はこんな感じでした。
実際に動かしてみると、
黄色いのが入力信号、青が出力信号ですが、リップルの数も含めてぴたりと一致した特性が出ています。これが面白いものです。
BPFを通った信号というのは時系列でみると、どうしてもよくわからないものです。
![]()
CoreGenでFIRを作るとき、係数が動的に変えられるようにしてあるので、いろいろなフィルタを試すことができて、なかなか面白いものです。
XILINXのCoreGenでFIRフィルタを作る
2017.02.02
FIRフィルタというのがあります。乗算と加算を繰り返してLPFやHPFを作るディジタルフィルタの代表的なやつです。
で、XILINXユーザなら、CoreGeneratorを使ってディジタルフィルタを簡単に作れるはずなのですが、使い方や精度がよくわからないのでシミュレーションしてみました。
結果としては、XILINXのFIRフィルタは25bit精度の計数が利用できて、レートの変換も自由自在という素晴らしいものでした。
![]()
FIRは、入力と、遅延させたものにいろいろな係数をかけて積和したものです。
原理的には、こういう回路なのですが、

これを真面目に作ると和の部分が非常にたくさんの値を足し算しなければならないので、大変なことになります。
そこで、いろいろなアーキテクチャが用意されています。
![]()
フィルタの型
まず、Transposed Direct Formというアーキテクチャです。乗算と遅延の順序が逆になっています。これなら加算が重くならないので楽にできそうですね。

デフォルトは、Systolic Multiply-Accumulate(シストリック:収縮の意味)というアーキテクチャになっています。SystolicなFIRというのは
という構造をしています。上側の遅延が2段になっていて、下側の遅延が1段です。
他にはSerial Distribute Arithmetic型というのもあるそうですが、詳細は割愛します。
実装する型は、この画面で選択できます。

それぞれの型でどういう利点があるのかを調べたところ、基本的にはSystolic型が最も優れていて何でもできます。
FIRはたくさんの乗算器を使います。FIRの計数は対照的な形をしていることが多いのですが、Systolic型を選べば対称性を利用してフィルタの次数の約半分の数の乗算器で実装できます。Transpose型では、対称性を利用した乗算器の削減ができないようです。
![]()
フィルタの計数
CoreGenのFIRの画面で、最初に入力するのがフィルタの計数です。ここに入れる値で、次数と、ビット演算の精度が決まります。
フィルタの計数というのは、こういう感じの数値です。sync関数にしておけば、それなりのLPFが作れます。下の図は0.19ωcがカットオフのsinc関数です。

上の図は41次のFIRのものですが、シミュレーションしていてよくわからなくなるので、動作テストのために中央の7個の数字だけでやってみます。
値を32767倍して、

こんな感じです。すると、画面左側に周波数特性が表示されます。
なんとなく、0.19のあたりでカットオフになっていますね。
この係数を入力したら、その係数の絶対値をすべて足してみてください。17858+25520+30856+32767+30856+25520+17858=181235なので、これを表現するには18bit必要です。
CoreGenは入力信号数+18bitの内部精度で演算しようとします。入力が16bitなら生の出力は34bitになります。(※切り捨てたり丸めたりして必要なビット数だけを取り出すオプションはある)
また、7シリーズの乗算器は18bit×25bitなので、計数の精度をケチっても、大きくとっても、コストは一緒です。25bitまでは乗算器のコストは追加でかかりません。また、加算部分のビット数は(たぶん)48bitあるので、そう簡単に溢れることもありません。
必要な乗算器の数=係数の数ですが、対照的にすれば約半分になります。(奇数のときには(次数+1)/2になる)
重要なことは、
- 計算は整数で行われる。精度落ちしない。出力のときに切り捨てられる。
- 係数の絶対値の総和を入れるのに必要十分な精度が自動的に選択される
- 係数を25bit精度まで高めてもコストはかからない
- 係数を対称的にすると、乗算器の使用量が約半分になる
- 内部の演算精度を上げてもコストはかからない
です。
乗算器の数が増えないように注意しながら係数の精度を上げていったほうが良いでしょう。
![]()
係数の動的変更
XILINXのFIRの素晴らしいとことは、係数を動的に変更できるところです。基本的には、coef_ldを'1'にすると係数受け入れモードになって、coef_weを'1'にしたときにcoef_dinが書き込まれます。
Use Reloadable Coefficientsをチェックすると、この機能が使えるようになります。

注意しなければならないのは、最初に入力した係数を「対称的」にして、Coefficient StructureをInffered(推論)にすると、乗算器を減らしたアーキテクチャで作られてしまいます。こうなると、非対称な係数のフィルタにはならなくなります。
どんな係数でも再設定できるフィルタを作るには、Coefficient StructureをNon Symmetricにしておきます。もちろん、Non Symmetricにしておいて実際には対照的な係数を設定しても問題ありません。
また、Reloadableにすると、演算精度は入力ビット数+係数のビット数+log2(次数)になります。7次のFIRで、入力ビット数が16bit、係数が16bitなら、35bitになります。つまり、オーバーフローすることはありません。
係数を動的に変更するときのシミュレーション波形はこんな感じです。

なお、係数はフィルタの次数の数だけ入れます。今回は7次のFIRなので、
17858,25520,30856,32767,30856,25520,17858
と7個分の値を入れています。この値は後ろから入れます。つまり、この7個の数字はa(6),a(6),a(5),a(4),a(3),a(2),a(1),a(0)と解釈されます。
対称フィルタにした場合は、a(3),a(2),a(1),a(0)の順に中央の値から順に入れます。対称フィルタの場合は係数の数が少なくなるので注意してください。
![]()
演算の確認
それではインパルス応答を入れてみましょう。
ずっと0の信号を作って、ある1クロックだけ32767の信号を入れてみます。
11クロック遅れて、FIRの計数の値がそのまま出てきました。
もちろん、どんな値を入れてもオーバーフローはしません。十分なビット精度が確保されているためです。
![]()
FIRの計数はどうやって作ればいいのか
Excelを使ってSinc関数を作り、それを適当な係数をかけて整数にすればよいでしょう。
上の表の計数だと、0.19ωcに設定してあって、特性は下の図のようになります。

Reloadableにすると、係数が17bit精度、41タップ(6bit分)なので、39bitの精度が必要になります。
係数固定ならば、係数の絶対値の総和が299628で19bit必要になるので、35bitの精度となります。
この35bitという精度は、絶対にオーバーフローしないような安全なビット幅なので、実際に出力される値は3FFF8000程度が最大値になります。したがって、計算結果の下位19bitを切り捨てると、かなり小さな値しか出てこないことになります。
したがって、通常想定する大きさのパルスが入力されたときの最大値をあらかじめ見当を付けておき、途中の何ビットかを取り出すようにしないと、勿体ない結果となってしまいます。
このあたりは今後追求していきたいと思います。
![]()
まとめ
- 演算は整数で行われる
- 演算結果を単純に切り捨てると、普通は結果がかなり小さくなる
- 係数は25bitまではコストの増加はない
- 対称的なSystolicフィルタがお得
- Reloadableにするときには、フィルタの型によって係数を入れる順序が変わる。
- Reloadableにすると、演算精度がオーバーフローしないように増える。


























