デザイン解析 に示したように、1 サイクルごとに 1 つの入力データの場合、スループットを最大にするために 4 つのカーネルを並行実行する必要があります。この 1 つの方法は、32 個の係数を 4 つのカーネルに分割し、データを 4 つのカーネルにブロードキャストすることです。カーネルからの部分的な累積結果をカスケードして、最後のカーネルに結果を生成できます。次の図に、このインプリメンテーションを示します。
図 1. 4 つのカーネルへにデータをブロードキャスト
初期データの一部は、後続のカーネルで破棄されることに注意してください。たとえば、2 番目のカーネルは最初の 8 つの入力を破棄します。
aie::sliding_mul
に 4 つのレーンと 8 つのポイントが選択されます。データの読み出しおよび書き込みは、計算と交互に実行されます。
最初のカーネルのコードは次のとおりです。
alignas(aie::vector_decl_align) static cint16 eq_coef0[8]={{1,2},{3,4},...};
//For storing data between graph iterations
static aie::vector<cint16,16> delay_line;
__attribute__((noinline)) void fir_32tap_core0(input_stream<cint16> * sig_in,
output_stream<cacc48> * cascadeout){
const cint16_t * restrict coeff = eq_coef0;
const aie::vector<cint16,8> coe = aie::load_v<8>(coeff);
aie::vector<cint16,16> buff = delay_line;
aie::accum<cacc48,4> acc;
const unsigned LSIZE = (SAMPLES/4/4); // assuming samples is integer power of 2 and greater than 16
main_loop:for (unsigned int i = 0; i < LSIZE; ++i)
chess_prepare_for_pipelining
{
//8 MAC produce 4 partial output
buff.insert(2,readincr_v<4>(sig_in));
acc = aie::sliding_mul<4,8>(coe,0,buff,0);
writeincr(cascadeout,acc);
//8 MAC produce 4 partial output
buff.insert(3,readincr_v<4>(sig_in));
acc = aie::sliding_mul<4,8>(coe,0,buff,4);
writeincr(cascadeout,acc);
buff.insert(0,readincr_v<4>(sig_in));
acc = aie::sliding_mul<4,8>(coe,0,buff,8);
writeincr(cascadeout,acc);
buff.insert(1,readincr_v<4>(sig_in));
acc = aie::sliding_mul<4,8>(coe,0,buff,12);
writeincr(cascadeout,acc);
}
delay_line = buff;
}
void fir_32tap_core0_init(){
// Drop samples if not first block
int const Delay = 0;
for (int i = 0; i < Delay; ++i){
get_ss(0);
}
//initialize data
for (int i=0;i<8;i++){
int tmp=get_ss(0);
delay_line.set(*(cint16*)&tmp,i);
}
};
注記:
-
__attribute__((noinline))
: 関数階層を保持します (オプション)。 -
chess_prepare_for_pipelining
: ツールで自動パイプライン処理を実行できるため、オプションです。 - 各
aie::sliding_mul<4,8>
は 4 レーン 8 ポイント MAC を乗算し、部分的な結果をカスケード チェーンを介して次のカーネルに送信します。 - データ
buff
は、aie::sliding_mul
のdata_start
パラメーターから読み出されます。カーネル コードが末尾に到達すると、循環して先頭に戻ります。
コンパイルのレポートは
Work/aie/<COL_ROW>/<COL_ROW>.log
に生成されます。詳細レポートを生成するには、-v
オプションを使用します。ログで do-loop
などのキーワードを検索し、ループの開始間隔を特定します。次のログ ファイルの例では、ループの開始間隔が 16 であることがわかります。(resume algo) -> after folding: 16 (folded over 1 iterations)
-> HW do-loop #128 in ".../Vitis/2023.2/aietools/include/adf/stream/me/stream_utils.h", line 1192: (loop #3) : 16 cycles
ヒント: ログ ファイルでループに最後にレポートされているサイクルを取得します。
上記のカーネル コードは、部分的な出力を生成するのに約 16 (cycles) /
16 (partial results) = 1 cycle
の時間がかかります。
ほかの 3 つのカーネルも同様です。2 番目のカーネルのコードは次のとおりです。
alignas(aie::vector_decl_align) static cint16 eq_coef2[8]={{17,18},{19,20},...};
//For storing data between graph iterations
alignas(aie::vector_decl_align) static aie::vector<cint16,16> delay_line;
__attribute__((noinline)) void fir_32tap_core1(input_stream<cint16> * sig_in, input_stream<cacc48> * cascadein,
output_stream<cacc48> * cascadeout){
const aie::vector<cint16,8> coe = aie::load_v<8>(eq_coef1);
aie::vector<cint16,16> buff = delay_line;
aie::accum<cacc48,4> acc;
const unsigned LSIZE = (SAMPLES/4/4); // assuming samples is integer power of 2 and greater than 16
for (unsigned int i = 0; i < LSIZE; ++i)
chess_prepare_for_pipelining
{
//8 MAC produce 4 partial output
acc = readincr_v4(cascadein);
buff.insert(2,readincr_v<4>(sig_in));
acc = aie::sliding_mac<4,8>(acc,coe,0,buff,0);
writeincr_v4(cascadeout,acc);
acc = readincr_v4(cascadein);
buff.insert(3,readincr_v<4>(sig_in));
acc = aie::sliding_mac<4,8>(acc,coe,0,buff,4);
writeincr_v4(cascadeout,acc);
acc = readincr_v4(cascadein);
buff.insert(0,readincr_v<4>(sig_in));
acc = aie::sliding_mac<4,8>(acc,coe,0,buff,8);
writeincr_v4(cascadeout,acc);
acc = readincr_v4(cascadein);
buff.insert(1,readincr_v<4>(sig_in));
acc = aie::sliding_mac<4,8>(acc,coe,0,buff,12);
writeincr_v4(cascadeout,acc);
}
delay_line = buff;
}
void fir_32tap_core1_init()
{
// Drop samples if not first block
int const Delay = 8;
for (int i = 0; i < Delay; ++i){
get_ss(0);
}
//initialize data
for (int i=0;i<8;i++){
int tmp=get_ss(0);
delay_line.set(*(cint16*)&tmp,i);
}
};
3 番目のカーネルのコードは次のとおりです。
alignas(aie::vector_decl_align) static cint16 eq_coef2[8]={{33,34},{35,36},...};
//For storing data between graph iterations
alignas(aie::vector_decl_align) static aie::vector<cint16,16> delay_line;
__attribute__((noinline)) void fir_32tap_core2(input_stream<cint16> * sig_in, input_stream<cacc48> * cascadein,
output_stream<cacc48> * cascadeout){
const aie::vector<cint16,8> coe = aie::load_v<8>(eq_coef2);
aie::vector<cint16,16> buff = delay_line;
aie::accum<cacc48,4> acc;
const unsigned LSIZE = (SAMPLES/4/4); // assuming samples is integer power of 2 and greater than 16
for (unsigned int i = 0; i < LSIZE; ++i)
chess_prepare_for_pipelining
{
//8 MAC produce 4 partial output
acc = readincr_v4(cascadein);
buff.insert(2,readincr_v<4>(sig_in));
acc = aie::sliding_mac<4,8>(acc,coe,0,buff,0);
writeincr_v4(cascadeout,acc);
acc = readincr_v4(cascadein);
buff.insert(3,readincr_v<4>(sig_in));
acc = aie::sliding_mac<4,8>(acc,coe,0,buff,4);
writeincr_v4(cascadeout,acc);
acc = readincr_v4(cascadein);
buff.insert(0,readincr_v<4>(sig_in));
acc = aie::sliding_mac<4,8>(acc,coe,0,buff,8);
writeincr_v4(cascadeout,acc);
acc = readincr_v4(cascadein);
buff.insert(1,readincr_v<4>(sig_in));
acc = aie::sliding_mac<4,8>(acc,coe,0,buff,12);
writeincr_v4(cascadeout,acc);
}
delay_line = buff;
}
void fir_32tap_core2_init(){
// Drop samples if not first block
int const Delay = 16;
for (int i = 0; i < Delay; ++i)
{
get_ss(0);
}
//initialize data
for (int i=0;i<8;i++){
int tmp=get_ss(0);
delay_line.set(*(cint16*)&tmp,i);
}
};
最後のカーネルのコードは次のとおりです。
alignas(aie::vector_decl_align) static cint16 eq_coef3[8]={{49,50},{51,52},...};
//For storing data between graph iterations
alignas(aie::vector_decl_align) static aie::vector<cint16,16> delay_line;
__attribute__((noinline)) void fir_32tap_core3(input_stream<cint16> * sig_in, input_stream<cacc48> * cascadein,
output_stream<cint16> * data_out){
const aie::vector<cint16,8> coe = aie::load_v<8>(eq_coef3);
aie::vector<cint16,16> buff = delay_line;
aie::accum<cacc48,4> acc;
const unsigned LSIZE = (SAMPLES/4/4); // assuming samples is integer power of 2 and greater than 16
for (unsigned int i = 0; i < LSIZE; ++i)
chess_prepare_for_pipelining
{
//8 MAC produce 4 output
acc = readincr_v4(cascadein);
buff.insert(2,readincr_v<4>(sig_in));
acc = aie::sliding_mac<4,8>(acc,coe,0,buff,0);
writeincr_v4(data_out,acc.to_vector<cint16>(SHIFT));
acc = readincr_v4(cascadein);
buff.insert(3,readincr_v<4>(sig_in));
acc = aie::sliding_mac<4,8>(acc,coe,0,buff,4);
writeincr_v4(data_out,acc.to_vector<cint16>(SHIFT));
acc = readincr_v4(cascadein);
buff.insert(0,readincr_v<4>(sig_in));
acc = aie::sliding_mac<4,8>(acc,coe,0,buff,8);
writeincr_v4(data_out,acc.to_vector<cint16>(SHIFT));
acc = readincr_v4(cascadein);
buff.insert(1,readincr_v<4>(sig_in));
acc = aie::sliding_mac<4,8>(acc,coe,0,buff,12);
writeincr_v4(data_out,acc.to_vector<cint16>(SHIFT));
}
delay_line = buff;
}
void fir_32tap_core3_init()
{
// Drop samples if not first block
int const Delay = 24;
for (int i = 0; i < Delay; ++i){
get_ss(0);
}
//initialize data
for (int i=0;i<8;i++){
int tmp=get_ss(0);
delay_line.set(*(cint16*)&tmp,i);
}
};
最後のカーネルが acc.to_vector<cint16>(SHIFT)
を使用して結果を出力ストリームに書き込みます。
各カーネルが部分的な出力を生成するのに 1 サイクルかかります。カーネルが同時に動作すると、システム パフォーマンスは 1 サイクルで 1 つの出力を生成するので、デザインの目標が達成されます。
グラフの構築、ストリーム ブロードキャスト、DMA FIFO 挿入、シミュレーションおよびハードウェアでのプロファイリング、システム デザインで発生する可能性のあるデザイン ストールおよびデッドロック解析の詳細は、 『AI エンジン ツールおよびフロー ユーザー ガイド』 (UG1076) を参照してください。