1.7K Views
January 26, 24
スライド概要
フィックスターズならではの「FPGA」に関する高速化手法、 効率的な開発ノウハウ、苦労話などについてお話しいたします。
<講演内容>
1、Vitis HLS で追加された新しい構文を試してみる
Vitis HLS (旧 Vivado HLS) は AMD Xilinx 社の出している高位合成ツールですが、現在もアップデートが続いています。
Vivado HLS の時代は、通常の C コードに対して #pragma を付与することで挙動をカスタマイズできていました。
一方、Vitis HLS だと Vivado HLS と同じような方式に加え、hls::stream_of_blocks, hls::task 等、より細かく挙動をカスタマイズできる記述方式が追加されています。
本セミナーではこれらの機能について紹介し、より効率の良い HDL を生成するための方法について解説します。
2、格安FPGAで始めるFPGA Ethernet: UDPオーディオ編
※資料掲載先(格安FPGAで始めるFPGA Ethernet: UDPオーディオ編):
https://github.com/ciniml/fpga_seminar_slides/blob/main/20230927_udp_audio/fpga_seminar_udp_audio.pdf
弊社 FPGAセミナー vol.19( https://news.fixstars.com/2990/ ) のLTなどで紹介した、
秋月電子通商などで購入可能な格安FPGAボード「Tang Primer 20K」を使った、
FPGAでのEthernetおよびUDP通信を使ったシステムの設計について解説します。
CPUを用いずにFPGAの論理回路のみでUDPのパケットの送受信を行い、
PCから送信された音声データをFPGA上で処理したのち、
Tang Primer 20Kに搭載されているI2S DAC経由でスピーカーに出力します。
FPGA上での音声データ処理の実装方法についても解説予定です。
・当社技術ブログ 記事: https://proc-cpuinfo.fixstars.com/
・フィックスターズグループ/セミナー一覧: https://www.fixstars.com/ja/seminar
・フィックスターズのFPGAシステム開発: https://www.fixstars.com/ja/services/fpga
フィックスターズは、コンピュータの性能を最大限に引き出すソフトウェア開発のスペシャリストです。車載、産業機器、金融、医療など、幅広い分野での開発経験があります。また、ディープラーニングや機械学習などの最先端技術にも力を入れています。 並列化や最適化技術を駆使して、マルチコアCPU、GPU、FPGA、量子アニーリングマシンなど、さまざまなハードウェアでソフトウェアを高速化するサービスを提供しています。さらに、長年の経験から培ったハードウェアの知識と最適化ノウハウを活かし、高精度で高性能なアルゴリズムの開発も行っています。 ・開催セミナー一覧:https://www.fixstars.com/ja/seminar ・技術ブログ :https://proc-cpuinfo.fixstars.com/
実践的!FPGA開発セミナー vol.26 2023/09/27 18:00~ Copyright© Fixstars Group 1
Vitis HLS で追加された 新しい構文を試してみる Copyright© Fixstars Group 2
Who I am Yuki MATSUDA 写真 松田 裕貴 ソリューション第四事業部 リードエンジニア Copyright© Fixstars Group
自己紹介 ● 松田裕貴 ○ 2016 年 4 月に Fixstars に入社 ○ ハードウェア開発をメインに、ソフトウェア開発なども担当 ■ FPGA による画像処理・ネットワーク処理 ■ CPU / GPU 上の処理の高速化 Copyright© Fixstars Group 4
本日のセミナーについて ● Vitis HLS (旧 Vivado HLS) は現在も 機能追加の続いている AMD 製の高位合成ツール ● ○ 更新情報: https://japan.xilinx.com/products/design-tools/vitis/vitis-whats-new.html ○ 公式doc: https://docs.xilinx.com/r/ja-JP/ug1399-vitis-hls Vivado HLS 時代の書き方でも機能を実現するには十分でも、 最近追加された構文を使用すると、効率の良い HW を作れる場合があります ● 最近追加された機能のうち、いくつかをピックアップして紹介します Copyright© Fixstars Group 5
本日紹介する機能 ● hls::stream_of_blocks ○ ● hls::task ○ ● タスク並列を行う際のタスク間のデータ交換の拡張 タスク並列を C コード上で明示的に指示できるようにする追加機能 hls::burst_maxi ○ HLS が行う AXI Memory-Mapped (AXI MM) のメモリアクセス方式を 明示的に指示できる構文 ● その他いくつか細かいもの Copyright© Fixstars Group 6
タスク並列とは ● HLS カーネルの中で複数のタスク (処理) を並列に実行すること ○ 1 つの処理を小さなタスクとして分割することで、 タスク間の並列実行・タスク内のスケジュールの簡易化など様々なメリット ● 以下は一番よく見るタスク並列の例 ○ #pragma HLS dataflow で reader, proc, writer のタスク並列を指示する top reader proc writer Copyright© Fixstars Group void top(const int* x[N], int* y[N]) { int t0, t1[N]; #pragma HLS dataflow reader(x, t0); proc(t0, t1); writer(t1, y); } 7
タスク間のインターフェース ● PIPO (Ping-Pong) ○ ○ ● ある程度のサイズの ○ FIFO を用いてデータを渡す ダブルバッファでデータを渡す ○ シーケンシャルな バッファ領域に対して アクセスパターンのみサポート 任意のアクセスパターンをサポート ○ FIFO (First-In First-Out) ■ ランダムアクセス ■ 複数回のアクセス ○ producer の書き込み後、 データは即座に consumer に渡る ○ リソースは軽い ブロック化されたアクセスになるので ■ レイテンシは長い ○ デッドロック回避・性能達成など 目的に合わせて深さは調整 バッファを複製するため リソースは重い buf0 producer consumer producer consumer buf1 Copyright© Fixstars Group 8
タスク間のインターフェース (コード例, あくまで一例)
●
PIPO (Ping-Pong)
●
FIFO (First-In First-Out)
○
普通の配列アクセスで書ける
○
hls::stream<T> で明示的に使用できる
○
関数の終了タイミングで所有権が移動
○
read(), write() の呼び出しタイミングで
■
コーディングスタイルが制約される
void proc(const int t0[N],
int t1[N]) {
for (int i = 0; i < N; i++) {
t1[i] = t0[i];
}
}
void top(const int* x[N],
int* y[N]) {
int t0[N], t1[N];
#pragma HLS dataflow
reader(x, t0);
proc(t0, t1);
writer(t1, y);
}
即座にデータが移動する
void proc(hls::stream<int>& s0,
hls::stream<int>& s1) {
for (int i = 0; i < N; i++) {
s1.write(s0.read());
}
}
void top(const int* x[N],
int* y[N]) {
hls::stream<int> s0, s1;
#pragma HLS dataflow
reader(x, s0);
proc(s0, s1);
writer(s1, y);
}
Copyright© Fixstars Group
9
hls::stream_of_blocks
●
PIPO 形式のバッファリングで、データの受け渡しタイミングを
明示的に制御できるようにしたもの (関数終了時に限らない)
●
C++ 標準のロックのような使い方をする
○
write_lock, read_lock オブジェクトのコンストラクタでロック取得、デストラクタで解放
○
ロックオブジェクトがそのまま配列として使える
using block_t = int[M];
void proc(hls::stream_of_blocks<block_t>& s0,
hls::stream_of_blocks<block_t>& s1) {
for (int i = 0; i < N; i += M) {
hls::read_lock<block_t> b0(s0);
hls::write_lock<block_t> b1(s1);
for (int j = 0; j < M; j++) {
b1[j] = b0[j];
}
}
}
Copyright© Fixstars Group
10
hls::stream_of_blocks: ユースケース ● 行列積 ○ データ再利用が多いので PIPO だと嬉しい ○ DDR アクセスを減らすためにブロック単位の read/write 処理 ○ ブロック内のデータに対して行列積が実行される ■ O(n^2) のブロック領域に対し、読み出しは O(n^3) 回 行列積のブロック化 Copyright© Fixstars Group 11
hls::stream_of_blocks: ユースケース (実際のコード) ● ● 実際のコード例 ○ データ読み出し部 (右図) ○ 処理部 (下図, 内部ループのみ抜粋) 関数内で PIPO 形式で渡せるから便利 Copyright© Fixstars Group 12
タスク並列時のシミュレーションモデル
●
従来の #pragma HLS dataflow を使った形式だと、
Csim, Verilog HDL で挙動が異なっていた
○
C sim 時: reader, proc, writer の順に逐次実行。FIFO 深さは無限扱い
○
Cosim 時: reader, proc, writer は同時に動く
void top(const int* x[N],
int* y[N]) {
hls::stream<int> s0, s1;
#pragma HLS dataflow
reader(x, s0);
proc(s0, s1);
writer(s1, y);
}
Copyright© Fixstars Group
13
hls::task ● C コード上でタスク並列を明示的に使用するための新しい構文 ● hls::task t(func, …args); の形式でタスクを生成する ● タスクは dataflow がなくても タスク並列で実行される ● 右のコードを動かしたところ、 C simulation 時に inc, add が 同時に動いていた ○ マルチスレッドのような挙動 Copyright© Fixstars Group 14
hls::task: 注意事項 ● hls::task で実行する関数は 自動的に無限ループで実行される ● ○ data driven な処理になる ○ 多分タスクの制御のための HW リソースが減っていると思うが、こちらは未確認 また、以下の制約がある ○ タスクにできる関数の入出力は hls::stream または hls::stream_of_blocks のみ ■ スカラ変数などのパラメータもストリームで入れる必要がある Copyright© Fixstars Group 15
hls::task: ユースケース ● dataflow で hls::stream, hls::stream_of_blocks を使っている モジュール全般に利用可能 ● フィードバックがある回路を作るとき等、 C simulation では正しい結果が得られないような場合には 積極的に採用したい Copyright© Fixstars Group 16
AXI のバースト転送 ● ● AXI MM はバースト転送をサポートしている ○ バースト転送: 1つのアドレスリクエストに対し、複数のデータワードを送れること ○ シーケンシャルなアクセスのみがバースト転送にできる バースト転送が可能かどうかの判断はツールが行っていたが、 burst_maxi を使用すると明示的にバースト転送を指示できる https://docs.xilinx.com/r/ja-JP/ug1399-vitis-hls/AXI-バースト転送 Copyright© Fixstars Group 17
通常のメモリアクセスコード
●
普通に C style でメモリアクセスを書くと、
ツールによりバースト推論が実行される
void reader(const int* x,
hls::stream<int>& stm_x,
int size) {
for (int i = 0; i < size; i++) {
stm_x.write(x[i]);
}
}
●
アクセスパターンが複雑な場合など、
うまくバースト推論が実行されないケースもある
Copyright© Fixstars Group
18
hls::burst_maxi
●
どこまでがバースト転送かを明示的に指示する API
●
読み出しの場合
○
read_request(offset, length) で offset から length 個の読み出しリクエスト
○
read() で値を取得
void reader(hls::burst_maxi<int>& x,
hls::stream<int>& stm_x,
int size) {
x.read_request(0, size);
for (int i = 0; i < size; i++) {
stm_x.write(x.read());
}
}
Copyright© Fixstars Group
19
hls::burst_maxi (write)
●
書き込みの場合
○
write_request(offset, length) で offset から length 個の書き込みリクエスト
○
write(val, byte_en_mask=-1) でデータ送信
■
○
通常のメモリアクセスだと byte_en_mask は設定できないと思うので便利そう
write_response でデータ送信の完了待ち
void writer(hls::burst_maxi<int>& y,
hls::stream<int>& stm_y,
int size) {
y.write_request(0, size);
for (int i = 0; i < size; i++) {
y.write(stm_y.read());
}
y.write_response();
}
Copyright© Fixstars Group
20
hls::burst_maxi: ユースケース ● 画像の切り出し処理 ○ 二次元ループを使用する ○ 通常のコードでもバースト転送自体はされるのだが、 ループ間にオーバーヘッドが載る Copyright© Fixstars Group 21
hls::burst_maxi: ユースケース ● 画像の切り出し処理 ○ 最初にリクエストをまとめて発行 ○ 後は read/write を連続して行うだけ Copyright© Fixstars Group 22
おまけ1: hls::vector ● hls::vector ○ std::vector のようなメモリ確保ではなく、 SIMD のためのベクトル命令の意 ○ hls::vector<int, 32> とかやると 32 個の int が格納される ■ ○ AXI MM のインターフェースにもそのまま使用可能 +, - 等基本的な element wise op のサポートがあるので、コードを楽に書きたければ Copyright© Fixstars Group 23
おまけ2: hls::split, hls::merge ● AXI Stream に対する分岐/合流処理 ● round robin, load balancing の 2つのアルゴリズムサポート ● ワードが入力されるたびに、round robin or load balancing で分岐が走る ○ tlast までなどの柔軟な分割に期待 split func0 func0 func Copyright© Fixstars Group merge 24
まとめ ● Vitis HLS の最近のアップデートで追加されている いくつかの構文について紹介した ● ○ stream_of_blocks: PIPO 形式のデータ交換を明示的に行える構文 ○ task: C コード上に明示的にタスク並列処理を記載できる構文 ○ burst_maxi: C コード上から明示的にバースト転送を行える構文 便利な機能など多々あるので、良い HLS ライフを Copyright© Fixstars Group 25
Thank you! お問い合わせ窓口 : [email protected] Copyright © Fixstars Group 26