FC2カウンター FPGAの部屋 2018年01月28日
FC2ブログ

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

FPGAの部屋

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

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

”Vivado HLS 2017.4 で片方が定数の場合の乗算の検討1(C シミュレーション)”の続き。

前回は、Vivado HLS 2017.4 で片方が定数の場合の乗算を乗算記号を使用した演算と 2 の n 乗の数に分解し、それを足し合わせることで乗算する演算との比較を C シミュレーションで行った。今回は、C コードの合成を行って、2 つの演算の比較を行う。

まずは、multi_test.h を元に戻す。つまり、任意精度固定小数点データ型の ap_fixed の量子化モードは 0 への切り捨て(AP_TRN_ZERO)で、オーバーフローモードは飽和(AP_SAT)にするということだ。
multi_test_9_180128.png

次に、multi_test.cpp を out1 の通常の乗算記号を使った out1 の演算だけにする。out2 は out1 の出力をコピーする。
multi_test_10_180128.png

これで C コードの合成を行った。なお、使用したFPGAは、PYNQ やZYBO Z7-20 を想定した xc7z020clg400-1 だ。
結果を示す。
multi_test_11_180128.png

Estmated は 8.30 ns だった。
Latency が 1 クロックなので 2 クロックで 1 出力だが、PIPELINE指示子があるので、Interval は 1 クロックになっている。FF は 12 個、LUT は 227 個使用している。飽和演算をしているので、組み合わせ回路では回路的にダメの様で 2 クロックに分けているのではないだろうか?

Analysis 画面を示す。
multi_test_12_180128.png

出力された Verilog HDL コードを見てみよう。
multi_test_13_180128.png

演算は、in1 を 3 ビット左シフトする。下2ビットは小数点部なので、3 ビットシフトするということは 2 を乗算するのに等しい。そこから in1 そのものを引いている。つまり、in1 の 1/4 (0.25 )を引いている。 2 - 0.25 = 1.75 倍ということになる。思ったより賢い。乗算をシフト+引き算で実現している。

それでは、量子化モードが負の無限大への切り捨て(AP_TRN)で、オーバーフローモードが折り返し(AP_WRAP)に変更する。
multi_test.h の下の 2 行のコメントを外して、上の 2 行をコメントアウトする。
multi_test_5_180127.png

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

今回は、Latency が 0 クロックなので、組み合わせ回路となっている。Estmated は 1.92 ns なので、500 MHz でも動作可能な数値だ。
組み合わせ回路なので、FF は 0 個、LUT はわずか 15 個となっている。ap_fixed の量子化モードは 0 への切り捨て(AP_TRN_ZERO)で、オーバーフローモードは飽和(AP_SAT)のモードの時と比べて、LUT は 15 / 227 ≒ 0.066 倍となり、7 % 弱となっている。
つまり、演算器のビット幅は増えても、量子化モードやオーバーフローモードの回路負担が大きいので、飽和演算をしたほうが回路規模は大きいようだ。この結果からではまだ何とも言えないだろうが、途中の計算では、無理にビット幅を落として飽和演算をするよりも、演算器のビット幅を増やして、桁あふれの無い演算をしたほうがリソース使用量は少ないという結論になった。

Analysis 画面を見てみよう。
multi_test_15_180128.png

組み合わせ回路なので、C0 ステートのみとなっているようだ。演算も1つだけでとってもシンプルだ。

出力された Verilog HDL コードを見てみよう。
multi_test_16_180128.png

ポート宣言を見ても組み合わせ回路なので、クロックとリセットが無い。
演算は、in1 を 3 ビット左シフトし、そこから in1 自体を引いているだけだ。
multi_test.v を貼っておく。

// ==============================================================
// RTL generated by Vivado(TM) HLS - High-Level Synthesis from C, C++ and SystemC
// Version: 2017.4
// Copyright (C) 1986-2017 Xilinx, Inc. All Rights Reserved.
// 
// ===========================================================

`timescale 1 ns / 1 ps 

(* CORE_GENERATION_INFO="multi_test,hls_ip_2017_4,{HLS_INPUT_TYPE=cxx,HLS_INPUT_FLOAT=0,HLS_INPUT_FIXED=1,HLS_INPUT_PART=xc7z020clg400-1,HLS_INPUT_CLOCK=10.000000,HLS_INPUT_ARCH=pipeline,HLS_SYN_CLOCK=1.915000,HLS_SYN_LAT=0,HLS_SYN_TPT=1,HLS_SYN_MEM=0,HLS_SYN_DSP=0,HLS_SYN_FF=0,HLS_SYN_LUT=15}" *)

module multi_test (
        ap_start,
        ap_done,
        ap_idle,
        ap_ready,
        in1_V,
        out1_V,
        out1_V_ap_vld,
        out2_V,
        out2_V_ap_vld,
        ap_return
);


input   ap_start;
output   ap_done;
output   ap_idle;
output   ap_ready;
input  [5:0] in1_V;
output  [5:0] out1_V;
output   out1_V_ap_vld;
output  [5:0] out2_V;
output   out2_V_ap_vld;
output  [31:0] ap_return;

reg out1_V_ap_vld;
reg out2_V_ap_vld;

wire   [4:0] tmp_fu_58_p1;
wire   [7:0] p_shl_fu_62_p3;
wire  signed [7:0] OP1_V_cast_fu_54_p1;
wire   [7:0] p_Val2_s_fu_70_p2;

always @ (*) begin
    if ((ap_start == 1'b1)) begin
        out1_V_ap_vld = 1'b1;
    end else begin
        out1_V_ap_vld = 1'b0;
    end
end

always @ (*) begin
    if ((ap_start == 1'b1)) begin
        out2_V_ap_vld = 1'b1;
    end else begin
        out2_V_ap_vld = 1'b0;
    end
end

assign OP1_V_cast_fu_54_p1 = $signed(in1_V);

assign ap_done = ap_start;

assign ap_idle = 1'b1;

assign ap_ready = ap_start;

assign ap_return = 32'd0;

assign out1_V = {{p_Val2_s_fu_70_p2[7:2]}};

assign out2_V = {{p_Val2_s_fu_70_p2[7:2]}};

assign p_Val2_s_fu_70_p2 = ($signed(p_shl_fu_62_p3) - $signed(OP1_V_cast_fu_54_p1));

assign p_shl_fu_62_p3 = {{tmp_fu_58_p1}, {3'd0}};

assign tmp_fu_58_p1 = in1_V[4:0];

endmodule //multi_test

  1. 2018年01月28日 08:26 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS 2017.4 で片方が定数の場合の乗算の検討1(C シミュレーション)

畳み込みニューラルネットワークの乗算を展開するため、片方が定数の乗算のリソース使用量を少なくできる方法を探っていく。ニューラルネットワークの重みは定数なので、変数にしておく必要はない。つまり、片方が定数ということで、乗算回路を簡単化することができるということだ。ASICでは重みが固定されてしまうので、決まりきった回路になって単一機能になってしまうが、FPGAは重みが変わったら、再コンフィギュレーションすればよいだけなので、デメリットにはならない。もし動作中に重みを変えたいときは、パーシャル・リコンフィギュレーションすれば良いだけである。ZynqやZynqMP でARMプロセッサからパーシャル・リコンフィギュレーションできれば自分で重みを変えられるので更に良いだろう。

さて最初のVivado HLS による乗数が定数の乗算の C サンプルコード(multi_test.cpp)を示す。

// multi_test.cpp
// 2018/01/23 by marsee
//

#include "multi_test.h"

int multi_test(ap_fixed_def in1,
        ap_fixed_def &out1, ap_fixed_def &out2){
#pragma HLS PIPELINE II=1
    out1 = in1 * (ap_fixed_def)1.75;
    //out2 = out1;

    out2 = in1/4 + in1/2 + in1;
    //out2 = (ap_fixed_def2)in1/4 + (ap_fixed_def2)in1/2 + (ap_fixed_def2)in1;
    //out1 = out2;

    return(0);
}


いろいろとコメントアウトしてある行は後で使用する。

つまり、乗数が整数値の乗算と、乗算をシフト+加算に直した演算だ。

ヘッダ・ファイルの multi_test.h を示す。

// multi_test.h
// 2018/01/23 by marsee
//

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

typedef ap_fixed<53, AP_TRN_ZERO, AP_SAT> ap_fixed_def;
typedef ap_fixed<63, AP_TRN_ZERO, AP_SAT> ap_fixed_def2;
//typedef ap_fixed<6, 4, AP_TRN, AP_WRAP> ap_fixed_def;
//typedef ap_fixed<7, 4, AP_TRN, AP_WRAP> ap_fixed_def2;

#endif



テストベンチの multi_test_tb.cpp を示す。

// multi_test.h
// 2018/01/23 by marsee
//

#include "multi_test.h"

int multi_test(ap_fixed_def in1,
        ap_fixed_def &out1, ap_fixed_def &out2);

int main(void){
    ap_fixed_def in1, in2, out1, out2;

    for(float v=0.25; v<=3.75; v+=0.25){
        in1 = (ap_fixed_def)v;
        multi_test(in1, out1, out2);
        printf("v = %f, out1 = %f, out2 = %f, float = %f", v, (float)out1, (float)out2, v*1.75);
        if(out1 != out2)
            printf(" error\n");
        else
            printf("\n");
    }

    return(0);
}


つまり、0.25 刻みに 0.25 ~ 3.75 までの量子化した数と 1.75 を乗算するということだ。
multi_out.cpp の out1 はin1 と 1.75 をそのまま乗算しているが、out2 は in1 に 1/4 を乗じた数(つまり 0.25乗)+ in1 に 1/2 を乗じた数(つまり 0.5 乗)+ in1 の演算となる。
out1 と out2 は同じ結果になるはずだ。つまりそのまま乗算してるか筆算の方式で乗算しているかの違いだ。
Vivado HLS 2017.4 のプロジェクトの multi_test を下に示す。
multi_test_1_180127.png

このままのソースコードで C シミュレーションを行ってみよう。結果を示す。
multi_test_2_180127.png

INFO: [SIM 2] *************** CSIM start ***************
INFO: [SIM 4] CSIM will launch GCC as the compiler.
Compiling ../../../multi_test_tb.cpp in debug mode
Compiling ../../../multi_test.cpp in debug mode
Generating csim.exe
v = 0.250000, out1 = 0.250000, out2 = 0.250000, float = 0.437500
v = 0.500000, out1 = 0.750000, out2 = 0.750000, float = 0.875000
v = 0.750000, out1 = 1.250000, out2 = 1.000000, float = 1.312500 error
v = 1.000000, out1 = 1.750000, out2 = 1.750000, float = 1.750000
v = 1.250000, out1 = 2.000000, out2 = 2.000000, float = 2.187500
v = 1.500000, out1 = 2.500000, out2 = 2.500000, float = 2.625000
v = 1.750000, out1 = 3.000000, out2 = 2.750000, float = 3.062500 error
v = 2.000000, out1 = 3.500000, out2 = 3.500000, float = 3.500000
v = 2.250000, out1 = 3.750000, out2 = 3.750000, float = 3.937500
v = 2.500000, out1 = 3.750000, out2 = 3.750000, float = 4.375000
v = 2.750000, out1 = 3.750000, out2 = 3.750000, float = 4.812500
v = 3.000000, out1 = 3.750000, out2 = 3.750000, float = 5.250000
v = 3.250000, out1 = 3.750000, out2 = 3.750000, float = 5.687500
v = 3.500000, out1 = 3.750000, out2 = 3.750000, float = 6.125000
v = 3.750000, out1 = 3.750000, out2 = 3.750000, float = 6.562500
INFO: [SIM 1] CSim done with 0 errors.
INFO: [SIM 3] *************** CSIM finish ***************


v = 0.75 と v = 1.75 のところで、out1 と out2 の値が違っている。これは、out2 では、演算ごとに飽和演算や丸めが行われるので、演算精度が足りないのではないか?と思われる。out1 では一発で乗算をしてしまうので、演算による誤差の蓄積は無いと考えられる。
それでも、浮動小数点数の演算と違って、整数部が3ビットなので、整数は3 ~ -4 までしか表せないので、飽和演算で飽和はしても値は差が大きくなる。これは元来、整数部のビットが足りないので仕方がないことだ。

さて、次に、out2 の途中の演算でもう1ビット小数部のビットを追加してみてはどうだろうか?
小数部を1ビット増やして 3 ビットとした ap_fixed_def2 で乗算をキャストしてみよう。
multi_test_3_180127.png

これで C シミュレーションを行った。結果を示す。
multi_test_4_180127.png

INFO: [SIM 2] *************** CSIM start ***************
INFO: [SIM 4] CSIM will launch GCC as the compiler.
Compiling ../../../multi_test.cpp in debug mode
Generating csim.exe
v = 0.250000, out1 = 0.250000, out2 = 0.250000, float = 0.437500
v = 0.500000, out1 = 0.750000, out2 = 0.750000, float = 0.875000
v = 0.750000, out1 = 1.250000, out2 = 1.250000, float = 1.312500
v = 1.000000, out1 = 1.750000, out2 = 1.750000, float = 1.750000
v = 1.250000, out1 = 2.000000, out2 = 2.000000, float = 2.187500
v = 1.500000, out1 = 2.500000, out2 = 2.500000, float = 2.625000
v = 1.750000, out1 = 3.000000, out2 = 3.000000, float = 3.062500
v = 2.000000, out1 = 3.500000, out2 = 3.500000, float = 3.500000
v = 2.250000, out1 = 3.750000, out2 = 3.750000, float = 3.937500
v = 2.500000, out1 = 3.750000, out2 = 3.750000, float = 4.375000
v = 2.750000, out1 = 3.750000, out2 = 3.750000, float = 4.812500
v = 3.000000, out1 = 3.750000, out2 = 3.750000, float = 5.250000
v = 3.250000, out1 = 3.750000, out2 = 3.750000, float = 5.687500
v = 3.500000, out1 = 3.750000, out2 = 3.750000, float = 6.125000
v = 3.750000, out1 = 3.750000, out2 = 3.750000, float = 6.562500
INFO: [SIM 1] CSim done with 0 errors.
INFO: [SIM 3] *************** CSIM finish ***************


今度は、out1 と out2 の値はすべて合っている。やはり桁落ちしていたようだ。

次に、現在は任意精度固定小数点データ型の ap_fixed の量子化モードは 0 への切り捨て(AP_TRN_ZERO)で、オーバーフローモードは飽和(AP_SAT)だが、これだと回路も大きくなるし、レイテンシも増える。回路が小さくなるのは、量子化モードが負の無限大への切り捨て(AP_TRN)で、オーバーフローモードが折り返し(AP_WRAP)だ。これでやってみよう。
この場合は、折り返してしまうので、ビット幅が足りないと演算結果が不正になってしまう。よって整数部を 1 ビット増やして、4 ビットにしてみよう。小数部は 2 ビットのままとする。
multi_test.h の下の 2 行のコメントを外して、上の 2 行をコメントアウトする。
multi_test_5_180127.png

out2 の記述はap_fixed_def2 で乗算をキャストしていない文に戻した。
multi_test_6_180127.png

これで、C シミュレーションを行った。結果を示す。
multi_test_7_180127.png

INFO: [SIM 2] *************** CSIM start ***************
INFO: [SIM 4] CSIM will launch GCC as the compiler.
Compiling ../../../multi_test_tb.cpp in debug mode
Compiling ../../../multi_test.cpp in debug mode
Generating csim.exe
v = 0.250000, out1 = 0.250000, out2 = 0.250000, float = 0.437500
v = 0.500000, out1 = 0.750000, out2 = 0.750000, float = 0.875000
v = 0.750000, out1 = 1.250000, out2 = 1.000000, float = 1.312500 error
v = 1.000000, out1 = 1.750000, out2 = 1.750000, float = 1.750000
v = 1.250000, out1 = 2.000000, out2 = 2.000000, float = 2.187500
v = 1.500000, out1 = 2.500000, out2 = 2.500000, float = 2.625000
v = 1.750000, out1 = 3.000000, out2 = 2.750000, float = 3.062500 error
v = 2.000000, out1 = 3.500000, out2 = 3.500000, float = 3.500000
v = 2.250000, out1 = 3.750000, out2 = 3.750000, float = 3.937500
v = 2.500000, out1 = 4.250000, out2 = 4.250000, float = 4.375000
v = 2.750000, out1 = 4.750000, out2 = 4.500000, float = 4.812500 error
v = 3.000000, out1 = 5.250000, out2 = 5.250000, float = 5.250000
v = 3.250000, out1 = 5.500000, out2 = 5.500000, float = 5.687500
v = 3.500000, out1 = 6.000000, out2 = 6.000000, float = 6.125000
v = 3.750000, out1 = 6.500000, out2 = 6.250000, float = 6.562500 error
INFO: [SIM 1] CSim done with 0 errors.
INFO: [SIM 3] *************** CSIM finish ***************


やはり、out1 と out2 が違ってい部分があるので、先ほど同様、小数部を1ビット増やして 3 ビットとした ap_fixed_def2 で乗算をキャストしてみよう。
multi_test_3_180127.png

これで、C シミュレーションをやり直した。結果を示す。
multi_test_8_180127.png

INFO: [SIM 2] *************** CSIM start ***************
INFO: [SIM 4] CSIM will launch GCC as the compiler.
Compiling ../../../multi_test.cpp in debug mode
Generating csim.exe
v = 0.250000, out1 = 0.250000, out2 = 0.250000, float = 0.437500
v = 0.500000, out1 = 0.750000, out2 = 0.750000, float = 0.875000
v = 0.750000, out1 = 1.250000, out2 = 1.250000, float = 1.312500
v = 1.000000, out1 = 1.750000, out2 = 1.750000, float = 1.750000
v = 1.250000, out1 = 2.000000, out2 = 2.000000, float = 2.187500
v = 1.500000, out1 = 2.500000, out2 = 2.500000, float = 2.625000
v = 1.750000, out1 = 3.000000, out2 = 3.000000, float = 3.062500
v = 2.000000, out1 = 3.500000, out2 = 3.500000, float = 3.500000
v = 2.250000, out1 = 3.750000, out2 = 3.750000, float = 3.937500
v = 2.500000, out1 = 4.250000, out2 = 4.250000, float = 4.375000
v = 2.750000, out1 = 4.750000, out2 = 4.750000, float = 4.812500
v = 3.000000, out1 = 5.250000, out2 = 5.250000, float = 5.250000
v = 3.250000, out1 = 5.500000, out2 = 5.500000, float = 5.687500
v = 3.500000, out1 = 6.000000, out2 = 6.000000, float = 6.125000
v = 3.750000, out1 = 6.500000, out2 = 6.500000, float = 6.562500
INFO: [SIM 1] CSim done with 0 errors.
INFO: [SIM 3] *************** CSIM finish ***************


これで、out1 と out2 の値が合った。
  1. 2018年01月28日 04:00 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0