C++ クラスは、Vitis HLS での合成で完全にサポートされています。合成の最上位は、関数である必要があります。ク
ラスは、合成では最上位にできません。クラスのメンバー関数を合成するには、クラス自体を関数にインスタンシエ ートする必要があります。最上位クラスは、単にテストベンチにインスタンシエートしないようにしてください。次 のコード例は、CFirクラス (次で説明するヘッダーファイルで定義) がどのように最上位関数cpp_FIRにインスタ ンシエートされ、FIR フィルターのインプリメントに使用されるかを示しています。
#include "cpp_FIR.h"
// Top-level function with class instantiated data_t cpp_FIR(data_t x)
{ static CFir<coef_t, data_t, acc_t> fir1;
cout << fir1;
return fir1(x);
}
重要: クラスおよびクラス メンバー関数は、合成では最上位にできません。クラスは最上位関数にインスタンシエー トする必要があります。
上記の C++ FIR フィルターの例で、デザインをインプリメントするために使用されるクラスを調べる前に、Vitis HLS
では合成中に標準出力ストリームの cout が無視されることに注意してください。Vitis HLS で合成されると、次のよ うな警告メッセージが表示されます。
INFO [SYNCHK-101] Discarding unsynthesizable system call:
'std::ostream::operator<<' (cpp_FIR.h:108)
INFO [SYNCHK-101] Discarding unsynthesizable system call:
'std::ostream::operator<<' (cpp_FIR.h:108)
INFO [SYNCHK-101] Discarding unsynthesizable system call: 'std::operator<<
<std::char_traits<char> >' (cpp_FIR.h:110)
次のコード例に示す cpp_FIR.h ヘッダー ファイルには、CFir クラスの定義およびそれに関連するメンバー関数が 含まれています。この例では、演算子のメンバー関数 () および << はオーバーロードされる演算子です。どちらも
main アルゴリズムを実行するために使用され、cout と共に使用すると C シミュレーション中に表示されるデータを
フォーマットできます。
#include <fstream>
#include <iostream>
#include <iomanip>
#include <cstdlib>
using namespace std;
#define N 85
typedef int coef_t;
typedef int data_t;
typedef int acc_t;
// Class CFir definition
template<class coef_T, class data_T, class acc_T>
class CFir { protected:
static const coef_T c[N];
data_T shift_reg[N-1];
private:
public:
data_T operator()(data_T x);
template<class coef_TT, class data_TT, class acc_TT>
friend ostream&
operator<<(ostream& o, const CFir<coef_TT, data_TT, acc_TT> &f);
};
// Load FIR coefficients
template<class coef_T, class data_T, class acc_T>
const coef_T CFir<coef_T, data_T, acc_T>::c[N] = { #include "cpp_FIR.h"
};
// FIR main algorithm
template<class coef_T, class data_T, class acc_T>
data_T CFir<coef_T, data_T, acc_T>::operator()(data_T x) {
int i;
acc_t acc = 0;
data_t m;
loop: for (i = N-1; i >= 0; i--) { if (i == 0) {
m = x;
shift_reg[0] = x;
} else {
m = shift_reg[i-1];
if (i != (N-1))
shift_reg[i] = shift_reg[i - 1];
} acc += m * c[i];
} return acc;
}
// Operator for displaying results
template<class coef_T, class data_T, class acc_T>
ostream& operator<<(ostream& o, const CFir<coef_T, data_T, acc_T> &f) { for (int i = 0; i < (sizeof(f.shift_reg)/sizeof(data_T)); i++) {
o << shift_reg[ << i << ]= << f.shift_reg[i] << endl;
} o << ______________ << endl;
return o;
}
data_t cpp_FIR(data_t x);
次のコード例に示される C++ FIR フィルターのテストベンチは、最上位関数 cpp_FIR がどのように呼び出されて検 証されるか示しています。この例には、Vitis HLS 合成用にテストベンチの重要な属性が含まれます。
• 出力結果は、既知の良い値に対して比較されます。
• 結果が正しいと確認されれば、テストベンチは 0 を返します。
#include "cpp_FIR.h"
int main() { ofstream result;
data_t output;
int retval=0;
// Open a file to saves the results result.open(result.dat);
// Apply stimuli, call the top-level function and saves the results for (int i = 0; i <= 250; i++)
{ output = cpp_FIR(i);
result << setw(10) << i;
result << setw(20) << output;
result << endl;
} result.close();
// Compare the results file with the golden results
retval = system(diff --brief -w result.dat result.golden.dat);
if (retval != 0) {
printf(Test failed !!!\n);
retval=1;
} else {
printf(Test passed !\n);
}
// Return 0 if the test return retval;
}
cpp_FIR の C++ テストベンチ
指示子をクラスで定義したオブジェクトに適用するには、次の手順に従ってください。
1. クラスが定義されているファイル (通常はヘッダー ファイル) を開きます。
2. [Directives] タブを使用して指示子を適用します。
関数と同様、1 つのクラスのインスタンスはすべて 同じ最適化が適用されます。
コンストラクター、デストラクター、および仮想関数
クラス コンストラクターおよびデストラクターは、クラス オブジェクトが宣言されると含まれて合成されます。
Vitis HLS がエラボレーション中に関数を静的に決定できる場合、仮想関数は、抽象的なものも含め、合成でサポート されます。次の場合、Vitis HLS では仮想関数が合成でサポートされません。
• 仮想関数はマルチレイヤー インヘリタンス クラス階層で定義できますが、シングル インヘリタンスを使用しての み定義できます。
• 動的なポリモーフィズムは、ポインターオブジェクトがコンパイル時に決定できる場合にのみサポートされます。
たとえば、このようなポインターは if-else やループ文では使用できません。
• STL コンテナーは、オブジェクトのポインターを含め、ポリモーフィズム関数を呼び出すためには使用できませ ん。次に例を示します。
vector<base *> base_ptrs(10);
//Push_back some base ptrs to vector.
for (int i = 0; i < base_ptrs.size(); ++i) {
//Static elaboration cannot resolve base_ptrs[i] to actual data type.
base_ptrs[i]->virtual_function();
}
• Vitis HLS では、base オブジェクト ポインターがグローバル変数の場合はサポートされません。次に例を示しま す。
Base *base_ptr;
void func() { ...
base_prt->virtual_function();
...}
• base オブジェクトポインターはクラス定義のメンバー変数にはできません。次に例を示します。
// Static elaboration cannot bind base object pointer with correct data type.
class A { ...
Base *base_ptr;
void set_base(Base *base_ptr);
void some_func();
...};
void A::set_base(Base *ptr) { this.base_ptr = ptr;
}
void A::some_func() { â¦.
base_ptr->virtual_function();
â¦.}
• base オブジェクト ポインターまたはリファレンスがコンストラクターの関数パラメーター リストにある場合は、
Vitis HLS でそれは変換されません。これについては、ISO C++ 規格のセクション 12.7 に記述されています。ビヘ
イビアーは定義されないこともあります。
class A { A(Base *b) {
b-> virtual _ function ();
}};
グローバル変数およびクラス
ザイリンクスでは、クラスでグローバル変数を使用することは推奨していません。使用すると、一部の最適化が実行 されないことがあります。次のコード例では、フィルターのコンポーネントを作成するのにクラスが使用されていま す (polyd_cell クラスはシフト、乗算、累算を実行するコンポーネントとして使用されます)。
typedef long long acc_t;
typedef int mult_t;
typedef char data_t;
typedef char coef_t;
#define TAPS 3
#define PHASES 4
#define DATA_SAMPLES 256
#define CELL_SAMPLES 12
// Use k on line 73 static int k;
template <typename T0, typename T1, typename T2, typename T3, int N>
class polyd_cell { private:
public:
T0 areg;
T0 breg;
T2 mreg;
T1 preg;
T0 shift[N];
int k; //line 73 T0 shift_output;
void exec(T1 *pcout, T0 *dataOut, T1 pcin, T3 coeff, T0 data, int col) {
Function_label0:;
if (col==0) {
SHIFT:for (k = N-1; k >= 0; --k) { if (k > 0)
shift[k] = shift[k-1];
else
shift[k] = data;
}
*dataOut = shift_output;
shift_output = shift[N-1];
}
*pcout = (shift[4*col]* coeff) + pcin;
} };
// Top-level function with class instantiated void cpp_class_data (
acc_t *dataOut,
coef_t coeff1[PHASES][TAPS], coef_t coeff2[PHASES][TAPS], data_t dataIn[DATA_SAMPLES], int row
) {
acc_t pcin0 = 0;
acc_t pcout0, pcout1;
data_t dout0, dout1;
int col;
static acc_t accum=0;
static int sample_count = 0;
static polyd_cell<data_t, acc_t, mult_t, coef_t, CELL_SAMPLES>
polyd_cell0;
static polyd_cell<data_t, acc_t, mult_t, coef_t, CELL_SAMPLES>
polyd_cell1;
COL:for (col = 0; col <= TAPS-1; ++col) {
polyd_cell0.exec(&pcout0,&dout0,pcin0,coeff1[row]
[col],dataIn[sample_count], col);
polyd_cell1.exec(&pcout1,&dout1,pcout0,coeff2[row][col],dout0,col);
if ((row==0) && (col==2)) { *dataOut = accum;
accum = pcout1;
} else {
accum = pcout1 + accum;
}
} sample_count++;
}
polyd_cell クラス内には、データをシフトするための SHIFT ループがあります。SHIFT ループで使用されるルー プ インデックスの k が削除され、k のグローバル インデックスに置換されると (前の例でも記述していましたが static int k と記述されてコメントアウトされていました)、Vitis HLS では polyd_cell クラスの使用されるル ープまたは関数がパイプライン処理できなくなります。Vitis HLS では、次のようなメッセージが表示されます。
@W [XFORM-503] Cannot unroll loop 'SHIFT' in function 'polyd_cell<char, long long,
int, char, 12>::exec' completely: variable loop bound.
ループ インデックスにはグローバル変数以外のローカル変数を使用すると、Vitis HLS ですべての最適化が実行され ます。
テンプレート
Vitis HLS では合成用に C++ のテンプレートの使用がサポートされます。Vitis では、最上位関数のテンプレートはサ ポートされません。
重要: 最上位関数にはテンプレートを使用できません。
テンプレートを使用した固有のインスタンスの作成
テンプレート関数のスタティック変数は、テンプレート引数の異なる値に対してそれぞれ複製されます。
template<int NC, int K>
void startK(int* dout) { static int acc=0;
acc += K;
*dout = acc;
}
void foo(int* dout) { startK<0,1> (dout);
}
void goo(int* dout) { startK<1,1> (dout);
}
int main() { int dout0,dout1;
for (int i=0;i<10;i++) { foo(&dout0);
goo(&dout1);
cout <<"dout0/1 = "<<dout0<<" / "<<dout1<<endl;
} return 0;
}
再帰関数でのテンプレートの使用
テンプレートは標準 C 合成ではサポートされない再帰関数をインプリメントするために使用することもできます。
次のコード例では、末尾再帰のフィボナッチ アルゴリズムをインプリメントするために、テンプレート化された struct が使用されています。合成を実行するためには、再帰の最終呼び出しをインプリメントするのに終端クラス を使用する必要があります。この場合、テンプレート サイズは 1 が使用されます。
//Tail recursive call
template<data_t N> struct fibon_s { template<typename T>
static T fibon_f(T a, T b) {
return fibon_s<N-1>::fibon_f(b, (a+b));
}};
// Termination condition
template<> struct fibon_s<1> { template<typename T>
static T fibon_f(T a, T b) { return b;
}};
void cpp_template(data_t a, data_t b, data_t &dout){
dout = fibon_s<FIB_N>::fibon_f(a,b);
}
アサーション
C の assert マクロは、範囲情報をアサートする場合に合成でサポートされます。たとえば、変数とループ境界の上限 を指定できます。
ループ境界が可変である場合、Vitis HLS ではそのループの反復すべてのレイテンシを判断できず、レイテンシがクエ スチョン マーク (?) で表示されます。tripcount 指示子を使用して Vitis HLS にループ境界の情報を渡すことはで きますが、この情報はレポート目的にのみ使用され、合成結果には影響しません (tripcount指示子の有無に関係な く同じサイズのハードウェアが作成される)。
次のコード例は、assert を使用して Vitis HLS に変数の最大範囲を伝え、さらに適したハードウェアを作成するために どのように assert を使用するかを示しています。
アサーションを使用する前に、assert マクロを定義するヘッダー ファイルを含める必要があります。この例では、
これはヘッダー ファイルに含まれます。
#ifndef _loop_sequential_assert_H_
#define _loop_sequential_assert_H_
#include <stdio.h>
#include <assert.h>
#include ap_cint.h
#define N 32
typedef int8 din_t;
typedef int13 dout_t;
typedef uint8 dsel_t;
void loop_sequential_assert(din_t A[N], din_t B[N], dout_t X[N], dout_t Y[N], dsel_t
xlimit, dsel_t ylimit);
#endif
main コードの各ループの前に、次の 2 つの assert 文が記述されています。
assert(xlimit<32);
...
assert(ylimit<16);
...
これらのアサーションは、次を実行します。
• アサーションが偽で値が指定の値よりも大きい場合、C シミュレーションでエラーが発生します。このため、合成 前に C コードをシミュレーションすることが重要です。合成前にデザインが有効であることを確認してくださ い。
• この変数の範囲がこの値を超えないことを Vitis HLS に伝えます。この情報は、RTL の変数のサイズ (この場合は ループの反復回数) を最適化するために使用できます。
次のコード例に、これらの assert 文を示します。
#include "loop_sequential_assert.h"
void loop_sequential_assert(din_t A[N], din_t B[N], dout_t X[N], dout_t Y[N], dsel_t
xlimit, dsel_t ylimit) { dout_t X_accum=0;
dout_t Y_accum=0;
int i,j;
assert(xlimit<32);
SUM_X:for (i=0;i<=xlimit; i++) { X_accum += A[i];
X[i] = X_accum;
}
assert(ylimit<16);
SUM_Y:for (i=0;i<=ylimit; i++) { Y_accum += B[i];
Y[i] = Y_accum;
}}
このコードは assert マクロを除けば ループの並列処理 と同じですが、合成後の合成レポートには 2 つの重要な相 違点があります。
assert マクロを使用しない場合、レポートは次のようになります。ループ境界が d_sel 型の 8 ビット変数であるた め、ループのトリップカウントは 1 ~ 256 の間で変化する可能性があります。
* Loop Latency:
+---+---+---+
|Target II |Trip Count |Pipelined | +---+---+---+
|- SUM_X |1 ~ 256 |no | |- SUM_Y |1 ~ 256 |no | +---+---+---+