IFの説明
高位合成では複数のIFが用意されてあります。
なので今回はIFの説明になります。
概要
一口にIFと言っても複数あります。大きく分けると以下です。
- ポートレベルのIF
- バス(AXI)のIF
- ブロックレベルのIF
そのうちバス以外のIFについて説明します。バスは別途します。
早速説明する前に、そもそもポートレベルとブロックレベルのIFとは何を指しているのかを説明します。
ポートレベルのIFとは
関数の入出力信号のプロトコルになります。
例えば以下のコードがあった場合、
void function(int a, int b, int* c)
{
}
aとbとcに対するプロトコルになります。
このように普通に書くとプロトコルなしの信号になります。言い換えるとデータ線のみになります。
ブロックレベルのIFとは
モジュールに対しての制御プロトコルになります。
先ほどと同じコードを例にすると、こちらはfuctionモジュールに対してのプロトコルになります。
ポートレベルでのプロトコル
ポートレベルのIFですが、大きく3つのカテゴリーがあります。
- レジスタ
- ストリーム
- メモリ
順に説明してきます。
また、プロトコルの指定の仕方ですが、#pragma HLS interface
で指定します。
レジスタ系
スカラーデータがこれに当てはまります。
プラグマ:#pragma HLS interface ap_none port = xxx
プロトコル:プロトコルなし
説明:単純なデータ線のみの信号(つまりソースコードに書いた信号のみ)
例:
void function(uint16_t a, uint16_t* b)
{
#pragma HLS interface ap_none port = a
#pragma HLS interface ap_none port = b
#pragma HLS interface ap_ctrl_none port = return
*b = a * 10;
}
プラグマ:#pragma HLS interface ap_vld port = xxx
プロトコル:valid信号あり
説明:データ線にvalid信号を付与する
例:
void function(uint16_t a, uint16_t *b)
{
#pragma HLS interface ap_vld port = a
#pragma HLS interface ap_vld port = b
#pragma HLS interface ap_ctrl_none port = return
*b = a * 10;
}
プラグマ:#pragma HLS interface ap_ovld port = xxx
プロトコル:valid信号あり(入出力信号)
説明:出力のデータ線にvalid信号を付与され、入力のデータ線にはap_noneが適用される。
例:
void function(uint16_t a, uint16_t *b)
{
#pragma HLS interface ap_none port = a
#pragma HLS interface ap_ovld port = b
#pragma HLS interface ap_ctrl_none port = return
*b = a * *b;
}
プラグマ:#pragma HLS interface ap_ack port = xxx
プロトコル:ack信号あり
説明:
入力データに対しては出力のack信号が生成され、入力が読み出されたタイミングでアサートされる。
出力データに対しては入力のack信号が生成され、出力が読み出されたことを確認される。
例:
void function(uint16_t a, uint16_t *b)
{
#pragma HLS interface ap_ack port = a
#pragma HLS interface ap_ack port = b
#pragma HLS interface ap_ctrl_none port = return
*b = a * 10;
}
CoSimでは出力のack信号を使用するデザインは検証できないです
プラグマ:#pragma HLS interface ap_hs port = xxx
プロトコル:今までの全部入りのプロトコル
説明:2方向によるハンドシェイク
例:
void function(uint16_t a, uint16_t *b, uint16_t *c)
{
#pragma HLS interface ap_hs port = a
#pragma HLS interface ap_hs port = b
#pragma HLS interface ap_hs port = c
#pragma HLS interface ap_none port = return
*b = a * *c;
*c = a;
}
メモリ系
配列データがこれに当てはまります。
プラグマ:#pragma HLS interface bram port = xxx
プロトコル:シングルポートのメモリアクセス
説明:RAMのインターフェースとして合成される
例:
void function(uint16_t a[10], uint16_t *b)
{
#pragma HLS interface bram port = a
#pragma HLS interface ap_none port = b
#pragma HLS interface ap_ctrl_hs port = return
uint16_t sum = 0;
for (int i = 0; i < 10; i++) {
#pragma HLS pipeline ii = 1
sum += a[i];
}
*b = sum;
}
プラグマ:#pragma HLS interface ap_memory port = xxx
プロトコル:インターフェースが分離されたメモリアクセス
説明:RAMのインターフェースとして合成される
例:
void function(uint16_t a[10], uint16_t *b)
{
#pragma HLS interface ap_memory port = a
#pragma HLS interface ap_none port = b
#pragma HLS interface ap_ctrl_hs port = return
uint16_t sum = 0;
for (int i = 0; i < 10; i++) {
#pragma HLS pipeline ii = 1
sum += a[i];
}
*b = sum;
}
以下のように読み取りと書き込みの両方があればデュアルポートとしてIFが生成されます。
void function(uint16_t a[10])
{
#pragma HLS interface ap_memory port = a
#pragma HLS interface ap_ctrl_hs port = return
for (int i = 0; i < 10; i++) {
#pragma HLS pipeline ii = 1
a[i] = a[i] + 10;
}
}
ストリーム系
こちらも配列データがこれに当てはまります。
プラグマ:#pragma HLS interface ap_fifo port = xxx
プロトコル:FIFOアクセス
説明:FIFOのインターフェースとして合成される
例:
void IfRegister(uint16_t a[5], uint16_t *b)
#pragma HLS interface ap_fifo port = a
#pragma HLS interface ap_vld port = b
#pragma HLS interface ap_ctrl_hs port = return
uint16_t sum = 0;
for (uint16_t i = 0; i < FIFO_DEPTH; i++) {
sum += a[i];
}
*b = sum;
}
ブロックレベルの制御プロトコル
プラグマ:#pragma HLS interface ap_ctrl_chain port = retrun
プロトコル:以下図
説明:チェーン制御
例:
void function(uint16_t a[5], uint16_t b[5], uint16_t* c)
{
#pragma HLS interface ap_memory port = a
#pragma HLS interface ap_memory port = b
#pragma HLS interface ap_memory port = c
#pragma HLS interface ap_ctrl_chain port = return
static uint16_t sum = 0;
for (int i = 0; i < 5; ++i) {
sum = sum + a[i] + b[i];
}
*c = sum;
}
プラグマ:#pragma HLS interface ap_ctrl_hs port = retrun
プロトコル:先ほどのap_ctrl_chain
のap_continue信号が常に1になる。
説明:シーケンシャル制御
例:
void function(uint16_t a[5], uint16_t b[5], uint16_t* c)
{
#pragma HLS interface ap_memory port = a
#pragma HLS interface ap_memory port = b
#pragma HLS interface ap_memory port = c
#pragma HLS interface ap_ctrl_hs port = return
static uint16_t sum = 0;
for (int i = 0; i < 5; ++i) {
sum = sum + a[i] + b[i];
}
*c = sum;
}
ここでap_ctrl_chain
との違いですが、ざっくり以下です。
動作イメージ
・ap_ctrl_chain
① 前段モジュールがap_startを出力
② 本モジュールが処理し、ap_doneを出力
③ 後段モジュールがap_continueを出力することでチェーン継続
・ap_ctrl_hs
① ソフト or 他モジュールがap_startを出力
② 本モジュールが処理し、ap_doneを出力
③ 再度ap_startを受け処理を開始
まとめ:
ap_ctrl_chainは自律的に各モジュール同士が連携し常に動作するイメージ。
ap_ctrl_hsはどこかからトリガーを受けて単発で動くイメージ。
プラグマ:#pragma HLS interface ap_ctrl_none port = retrun
プロトコル:プロトコルなし
説明:プロトコルがないため、常にパイプラインで処理したい場合などで有利
例:
void function(uint16_t a, uint16_t b, uint16_t* c)
{
#pragma HLS interface ap_none port = a
#pragma HLS interface ap_none port = b
#pragma HLS interface ap_none port = c
#pragma HLS interface ap_ctrl_none port = return
uint16_t sum = 0;
sum = a + b;
*c = sum;
}
IF編はこれにて終了。