Part2a - 2024.1 日本語 - XD100

Vitis チュートリアル: AI エンジン

Document ID
XD100
Release Date
2024-06-19
Version
2024.1 日本語

AI エンジンへの IIR フィルターのインプリメント - パート 2a

バージョン: Vitis 2024.1

下準備

パート 1a では、Emulation-SW モードを使用して、IIR フィルターの 2 次セクションの計算をベクター化することに焦点を当てました。パート 1a の式 (4) を使用すれば、8x12 の定数行列と 12x1 のベクター (連続する 8 つの入力と 4 つのステートを含有) を乗算することで、連続する 8 つの出力を計算できます。

図 1

AM009 の図 26 から、浮動小数点ベクター プロセッサは、E6 および E7 の 2 サイクルで、浮動小数点オペランドに対して 8 回の乗累算演算を実行できます。

注記: 図中の赤い破線矢印は、アキュムレータのフィードバック パスを示しています。この結果、8 つの浮動小数点出力を計算するには、理想的には最小で 12*2=24 サイクルが必要となります。

図 2

このセクション以降では、デザインを解析して最適化する典型的な手順を示しながら、レイテンシを最小化し、スループットを最大化してみます。

注記:

  • Julia スクリプト aie_iir_2a.jl は、指定された IIR フィルターの係数と、プログラムに必要なヘッダー ファイルを生成します。インパルス信号とフィルターの応答も生成します。

    • 生成されたヘッダー ファイルは、src ディレクトリに移動する必要があります。

    • 生成された *.dat ファイルは data ディレクトリに移動する必要があります。

  • Julia スクリプト check.jl は、aie_iir_2a.jl によって生成されたゴールデン インパルス応答と AI エンジンの出力との差を計算します。

カーネル コード

最初の手順では、カーネル コードを次のように使用します。

template<unsigned id>
void SecondOrderSection(
	adf::input_buffer<float> & __restrict idata,	// 8 input samples per iteration
	adf::output_buffer<float> & __restrict odata,	// 8 output samples per iteration
    const float (&C)[96]	// RTP port for coefficient matrix
) {

	static Vector8f state_reg = aie::zeros<float, 8>();	// clear states

	// input/output iterators
	auto inIter = aie::begin_vector<8>(idata);
	auto outIter = aie::begin_vector<8>(odata);

	Vector8f xreg_hi = *inIter++;		// fetch input samples
	Vector16f xreg = aie::concat(state_reg, xreg_hi);	// xreg[4]: ym2; xreg[5]: ym1; xreg[6]: xm2; xreg[7]: xm1; xreg[8:15]: x0:x7
	Vector8f coeff;
	VAcc8f acc = aie::zeros<accfloat, 8>();

	for (auto i = 0; i < 12; i++) {
		coeff = aie::load_v<8>(&C[8 * i]);
		float xval = xreg[i + 4];
		acc = aie::mac(acc, coeff, xval);
	} // end for (auto i = 0; i < 12; i++)

	Vector8f yout = acc;	// transfer accumulator register to vector register to update states

	// update states
	state_reg = xreg_hi;
	state_reg[4] = yout[6];
	state_reg[5] = yout[7];

	*outIter++ = yout;

} // end SecondOrderSection()

for ループは、係数行列の各列を xreg のエレメントでスケーリングし、結果を累積します。これは、式 (4) の行列とベクター乗算を実行します。

テストベンチ コード

#include "kernel.hpp"
#include "graph.hpp"
#include "C1.h"

using namespace std;

using namespace adf;

// specify the dataflow graph (DFG)
the_graph my_graph;

const unsigned num_pts = 256;			// number of sample points in "input.dat"
const unsigned num_iterations = num_pts/8;	// number of iterations to run

// main simulation program
int main() {

	my_graph.init();				// load the DFG into the AI Engine array, establish     connectivity, etc.

	my_graph.update(my_graph.cmtx1, C1, 96);	// transfer coefficients

	my_graph.run(num_iterations);	// run the DFG for the specified number of iterations

	my_graph.end();					// terminate AI Engine processing

	return (0);

} // end main()

テストベンチは、次を実行します。

  • グラフを初期化。

  • フィルター係数をロード。

  • グラフを 32 回実行。

  • すべての処理を終了。

解析

まず、Vitis Components ペインの Settings の下にある launch.json ファイルを開きます。Part2a_aiesim_1 を選択して AIE Simulator パラメーターの Enable Profile チェック ボックスをオンにします。ビルドしてから、シミュレーションを実行します。

図 3

シミュレーション完了後、次を実行すると、結果の良し悪しをチェックできます。

$ julia check.jl aie

maximum(abs.(err))eps(Float32) より小さい場合、結果は「良好」です。

FLOW ペインでプロファイラー結果を確認するには、AIE SIMULATOR / HARDWARE の下で、REPORTS (Debug アイコンの下) を展開し、Profile をクリックします。

図 4

AIE SIMULATION ペインで Total Function Time をクリックすると、各関数で消費されたサイクル数が表示されます。

図 5

注記: カーネル関数 SecondOrderSection<1> が 32 回実行され、2,313 サイクル間実行されました。各関数呼び出しは、2,313/32 = 72.28 サイクルを消費しました。最小の関数時間は 72 サイクル、最大は 81 サイクルです。これは、最初の呼び出しが 9 サイクル多く消費したことを意味します (81 + 31 * 72 = 2,313)。

もう 1 つ注目する必要があるのは、my_graph.run() を呼び出し、SecondOrderSection<1> を呼び出す最上位の main 関数です。Total Function + Descendants Time (cycles) 列は、その関数で消費されたサイクル数を示し、その関数内で呼び出されたほかのすべてのルーチンを含みます。これには、ヒープとスタックの設定、初期化、実際の処理などが含まれます。このインプリメンテーションでは、256 サンプルを処理するのに 4579 サイクル、つまり 4579/256 = 17.89 サイクル/サンプルが使用されました。AI エンジンが 1 GHz クロックで動作する場合、スループットは 1e9 サイクル/秒 ÷ 17.89 サイクル/サンプル = 55.897 Msamples/秒となります。

注記: 主な処理は SecondOrderSection<1> で実行され、2,313 サイクルを消費します。このため、サンプル処理には 4,579 - 2,313 = 2,266 の「オーバーヘッド」サイクルは使用されません。

生成されたアセンブリ コードを表示するには、Profile Details をクリックします。

図 6

VFPMAC アセンブラーのニーモニックが表示されるところまでスクロールダウンします。

図 7

カーネル コードの次の文は、VFPMAC ニーモニック (ベクター浮動小数点の乗累算演算) を生成します。また、for ループが展開され、2 サイクルの浮動小数点累積レイテンシを含めるため、各 VFPMAC 間には NOP (演算なし) があります。

acc = aie::mac(acc, coeff, xval);   // acc[] += coeff[] * xval

VFPMAC は入力バッファーとして 1024 ビットの y レジスタを使用します (AM009 の表 9 を参照)。

図 8

ya レジスタは、4 つの 256 ビットの wr[0:3] レジスタで構成されます。この例では、VLDA (vector load A) というニーモニックを使って、wr0 レジスタを係数行列の列で更新されます。VLDA ニーモニックは、8 つの浮動小数点値をデータ メモリからベクター レジスタに転送します。この例では、VLDA ニーモニック (wr0 にデータをロード) から、VFPMAC でデータが計算に使用されるまで、7 ~ 8 サイクルのレイテンシがあります。

まとめ

for ループと AI エンジン API を使用して、浮動小数点の 8x12 行列と 12x1 ベクター乗算を 73 サイクルで計算する方法を示しました。また、生成されたプログラムの統計情報 (消費サイクルなど) を表示し、生成されたアセンブラー コードを調べる方法も紹介しました。

パート 2b では、プログラムをさらに最適化し、すべてのサイクルで乗累算演算を達成します。

サポート

GitHub 問題は、リクエストやバグの追跡に使用します。質問については、forums.xilinx.com を参照してください。

Copyright © 2020-2024 Advanced Micro Devices, Inc

Terms and Conditions