FC2カウンター FPGAの部屋 2018年06月08日

FPGAやCPLDの話題やFPGA用のツールの話題などです。 マニアックです。 日記も書きます。

FPGAの部屋

FPGAの部屋の有用と思われるコンテンツのまとめサイトを作りました。Xilinx ISEの初心者の方には、FPGAリテラシーおよびチュートリアルのページをお勧めいたします。

Vivado HLS の任意精度固定小数点データ型の飽和演算

Kerasを勉強して、Keras でMNISTのCNNのパラメータを抽出して、Vivado HLS で自作テンプレートを使ってCNN を実装しているが、現在の自作テンプレートは飽和演算をサポートしていない。これは、飽和演算をサポートするよりは、演算によって取りうる値域すべてをカバーする演算用のビット長を与えたほうが、リソース使用量が小さいという実証データに基づいていた。ただし、特に、全結合層に言えることだが、演算結果の値域をカバーするような演算ビット幅を与えても、途中の計算でオーバーフローしてしまう可能性がある。つまり、整数部4ビットでは、+7.XXX ~ -8.XXX の数を表せるが、最終的に +4.XXX でもそれは、+36.XXX から +32.XXX を引いた結果かもしれない?その場合には、整数部4ビットで演算させる場合は、途中の演算で破綻している訳である。それでもたま~にであれば、それも量子化による誤差の一部として認めても良いと思っている。あくまでもリソース使用量との兼ね合いで。。。(ちなみに、Vivado HLSでは自由にビット長を選べるので、各層ごとに演算ビット長を変更している)
今度、飽和演算をサポートしたCNN とサポートしていないCNN の精度の差を比べてみようと思っている。

さて、全結合層は演算自体が多いので、演算自体に飽和演算をサポートするが、畳み込み層は1回の演算数が今のところ25個なので、重みの最大値、最小値から演算の途中の最大値を導出することができる。よって、正しい値になるような演算ビット長を推測しやすい。そのような演算ビット長で演算はするのだが、次の層に送る場合に飽和演算をサポートするとよりリソース使用量が削減される可能性がある。そのような演算をサポートするために飽和演算をサポートしない演算の結果を飽和演算しながら、丸めたい。
と言う訳で今日のブログにつながる訳である。

さて、まずは、sat_test1.h を示す。

// sat_test1.h
// 2018/06/07 by marsee
//

#ifndef __SAT_TEST1_H__
#define __SAT_TEST1_H__

#include <ap_fixed.h>

typedef ap_fixed<53, AP_TRN, AP_WRAP> ap_fixed_inout_def;
typedef ap_fixed<53, AP_TRN, AP_SAT> ap_fixed_sat_def; // 飽和演算
typedef ap_fixed<75, AP_TRN, AP_WRAP> ap_fixed_mid_def;

#endif


次に、sat_test1.cpp を示す。合成する気はさらさらないので、途中で printf() を使用している。

// sat_test1.cpp
// 2018/06/07 by marsee
//

#include "sat_test1.h"

int sat_test1(ap_fixed_inout_def in0, ap_fixed_inout_def in1,
        ap_fixed_inout_def &out){
    ap_fixed_mid_def calc;

    calc = ap_fixed_mid_def(in0) * ap_fixed_mid_def(in1);
    printf("calc = %f\n"float(calc));
    out = ap_fixed_inout_def(ap_fixed_sat_def(calc));

    return(0);
}


最後にテストベンチの sat_test1_tb.cpp を示す。

// sat_test1_tb.cpp
// 2018/06/07 by marsee
//

#include "sat_test1.h"

int sat_test1(ap_fixed_inout_def in0, ap_fixed_inout_def in1,
        ap_fixed_inout_def &out);

int main(){
    ap_fixed_inout_def in0_0, in1_0;
    ap_fixed_inout_def in0_1, in1_1;
    ap_fixed_inout_def out0, out1;

    in0_0 = 1.5;
    in1_0 = 1.5;
    sat_test1(in0_0, in1_0, out0);
    printf("in0_0 = %f, in1_0 = %f, out = %f\n",
        float(in0_0), float(in1_0), float(out0));

    in0_1 = 3.0;
    in1_1 = 3.0;
    sat_test1(in0_1, in1_1, out1);
    printf("in0_1 = %f, in1_1 = %f, out = %f\n",
        float(in0_1), float(in1_1), float(out1));

    in0_1 = -3.0;
    in1_1 = 3.0;
    sat_test1(in0_1, in1_1, out1);
    printf("in0_1 = %f, in1_1 = %f, out = %f\n",
        float(in0_1), float(in1_1), float(out1));

    return(0);
}


C シミュレーションの結果を示す。
sat_test1_1_180608.png

1.5 x 1.5 = 2.25 は演算後のビット長が定義したビット長に収まっているので、これは正しい。
3.0 x 3.0 = 3.75 は途中の結果は 9.0 だけど、飽和演算して、3.75 に丸められているので、問題無い様だ。
-3.0 x 3.0 = -4.00 は途中の結果は -9.0 だけど、飽和演算して、-4.00 に丸められているので、問題無い様だ。
  1. 2018年06月08日 04:35 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0