Vivado HLS 2017.4 で片方が定数の場合の乗算の検討3(C コードの合成2)”の続き。

前回は、片方が定数のときの乗算は、片方が定数と定義して、乗算記号の * を使ったほうがリソース使用量点から言っても効率が良いということになった。今回は、乗算記号の * を使用して、畳み込みニューラルネットワークの畳み込み演算の重みを定数と置いたときの乗算を検討してみよう。

Vivado HLS 2017.4 で、multi_test2 プロジェクトを作成した。
15850d03.png


multi_test2.h を貼っておく。

// multi_test2.h
// 2018/01/30 by marsee
//

#ifndef __multi_test_H__
#define __multi_test_H__
#include <ap_fixed.h>

typedef ap_ufixed<80, AP_TRN, AP_WRAP> ap_ufixed_in;
typedef ap_fixed<91, AP_TRN, AP_WRAP> ap_fixed_weight;
typedef ap_fixed<171, AP_TRN, AP_WRAP> ap_fixed_multi;
typedef ap_fixed<166, AP_TRN_ZERO, AP_SAT> ap_fixed_add;

#endif


multi_test2.cpp を貼っておく。

// multi_test2.cpp
// 2018/01/30 by marsee
//

#include "multi_test2.h"

int multi_test2(ap_ufixed_in in[25], ap_fixed_add &out){
#pragma HLS PIPELINE II=1

    out = (ap_fixed_multi)(in[0]*(const ap_fixed_weight)0.765625) +
            (ap_fixed_multi)(in[1]*(const ap_fixed_weight)0.66015625) +
            (ap_fixed_multi)(in[2]*(const ap_fixed_weight)0.59375) +
            (ap_fixed_multi)(in[3]*(const ap_fixed_weight)0.5546875) +
            (ap_fixed_multi)(in[4]*(const ap_fixed_weight)0.3671875) +
            (ap_fixed_multi)(in[5]*(const ap_fixed_weight)0.58203125) +
            (ap_fixed_multi)(in[6]*(const ap_fixed_weight)0.4140625) +
            (ap_fixed_multi)(in[7]*(const ap_fixed_weight)0.31640625) +
            (ap_fixed_multi)(in[8]*(const ap_fixed_weight)0.3515625) +
            (ap_fixed_multi)(in[9]*(const ap_fixed_weight)0.33203125) +
            (ap_fixed_multi)(in[10]*(const ap_fixed_weight)0.58984375) +
            (ap_fixed_multi)(in[11]*(const ap_fixed_weight)0.4609375) +
            (ap_fixed_multi)(in[12]*(const ap_fixed_weight)(-0.23828125))+
            (ap_fixed_multi)(in[13]*(const ap_fixed_weight)(-0.09765625)) +
            (ap_fixed_multi)(in[14]*(const ap_fixed_weight)0.234375) +
            (ap_fixed_multi)(in[15]*(const ap_fixed_weight)0.79296875) +
            (ap_fixed_multi)(in[16]*(const ap_fixed_weight)0.31640625) +
            (ap_fixed_multi)(in[17]*(const ap_fixed_weight)0.0390625) +
            (ap_fixed_multi)(in[18]*(const ap_fixed_weight)0.35546875) +
            (ap_fixed_multi)(in[19]*(const ap_fixed_weight)0.42578125) +
            (ap_fixed_multi)(in[20]*(const ap_fixed_weight)0.6328125) +
            (ap_fixed_multi)(in[21]*(const ap_fixed_weight)0.65234375) +
            (ap_fixed_multi)(in[22]*(const ap_fixed_weight)0.6875) +
            (ap_fixed_multi)(in[23]*(const ap_fixed_weight)0.70703125) +
            (ap_fixed_multi)(in[24]*(const ap_fixed_weight)0.6796875);

    return(0);
}


multi_test2_tb.cpp を貼っておく。

// multi_test2_tb.h
// 2018/01/30 by marsee
//

#include "multi_test2.h"

int multi_test2(ap_ufixed_in in[25], ap_fixed_add &out);

int main(void){
    ap_ufixed_in in[25];
    ap_fixed_add out;
    ap_ufixed_in v = 0.5;

    for(int i=0; i<25; i=i++){
        in[i] = (ap_ufixed_in)v;
        v += (ap_ufixed_in)0.00390625;
        printf("in[%d] = %f\n", i, (float)v);
    }

    multi_test2(in, out);

    printf("out = %f\n", (float)out);

    return(0);
}


C シミュレーションを行った。
eddc6e21.png

INFO: [SIM 2] *************** CSIM start ***************
INFO: [SIM 4] CSIM will launch GCC as the compiler.
Compiling ../../../multi_test2_tb.cpp in debug mode
Generating csim.exe
in[0] = 0.503906
in[1] = 0.507813
in[2] = 0.511719
in[3] = 0.515625
in[4] = 0.519531
in[5] = 0.523438
in[6] = 0.527344
in[7] = 0.531250
in[8] = 0.535156
in[9] = 0.539063
in[10] = 0.542969
in[11] = 0.546875
in[12] = 0.550781
in[13] = 0.554688
in[14] = 0.558594
in[15] = 0.562500
in[16] = 0.566406
in[17] = 0.570313
in[18] = 0.574219
in[19] = 0.578125
in[20] = 0.582031
in[21] = 0.585938
in[22] = 0.589844
in[23] = 0.593750
in[24] = 0.597656
out = 6.113281
INFO: [SIM 1] CSim done with 0 errors.
INFO: [SIM 3] *************** CSIM finish ***************


C コードの合成を行った。結果を示す。
multi_test_32_180131.png

Latency が 15 クロックは良いのだが、Interval が 13 クロックになっている。
リソース使用量は、DSP48E が 22 個、FF が 295 個、LUT が 581 個となった。

Analysis 画面を示す。
ac1864d8.png


in_V の read が横にずらっと並んでしまっている。これでは要求仕様を満たさない。Interval は 1 クロックにしたい。

multi_test2.v を見てみよう。
bacebebf.png


in_V はメモリ・インターフェースになっている。

それでは、入力 in の配列をバラバラにするにはどうするといえば、array partition 指示子で complete オプションを付ければ良い。

#pragma HLS ARRAY_PARTITION variable=in complete dim=1


cdad529a.png


これで、C コードの合成を行った。結果を示す。
multi_test_36_180131.png

Latency は 12 クロックで 15 クロックより少なくなった。Interval は 1 クロックで 1 クロックごとに処理することができる。これは希望通り。
リソース使用量は、DSP48E は 22 個で同じだが、 FF が約 5.7 倍の 1685 個、LUT が約 1.7 倍の 988 個だった。

multi_test.v を見ると、in の配列が array partition complete でバラバラになり、in_0_V から in_24_V までの入力ポートがインスタンスされているのが分かる。
e94ea7b2.png


さて、次は、DSP48E を使用しないようにしたい。それは、すべての畳み込み演算をDSP48E を使用して行うとDSP48E が足りなくなるからだ。以前はLUTに演算を割り振る方法は分からなかったのだが、今回、いろいろとトライするうちに、その方法を見つけるkとことができた。それは、RESOURCE 指示子で core = AddSub を指定することだ。
早速、試してみよう。

#pragma HLS RESOURCE variable=out core=AddSub


03253e6a.png


C コードの合成を行った。結果を示す。
multi_test_39_180131.png

Latency は 8 クロックで、Interval は 1 クロックだった。Latency がさらに縮まった。
リソース使用量は、DSP48E は使用しなかったが、その代わり FF が 1305 個、LUT が 2251 個使用されている。

Analysis 画面を示す。
6578e082.png


read が縦に並んで 1 クロックで行っているのが分かる。

multi_test2.v を示す。前回同様に、in_0_V から in_24_V までの入力ポートがインスタンスされているのが分かる。
16a02dc3.png