[高位合成] IFプロトコルの話

# IFの説明
高位合成では複数のIFが用意されてあります。
なので今回はIFの説明になります。

## 概要
一口にIFと言っても複数あります。大きく分けると以下です。
- ポートレベルのIF
- バス(AXI)のIF
- ブロックレベルのIF

そのうちバス以外のIFについて説明します。バスは別途します。

早速説明する前に、そもそもポートレベルとブロックレベルのIFとは何を指しているのかを説明します。

#### ポートレベルのIFとは
関数の入出力信号のプロトコルになります。
例えば以下のコードがあった場合、

```c++
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```
プロトコル：プロトコルなし
説明：単純なデータ線のみの信号(つまりソースコードに書いた信号のみ)
例：
```c++
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;
}
```
![SCR-20250525-skeg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/790a826c-1680-4c11-91e0-4939649d8b66.png)

---

プラグマ：```#pragma HLS interface ap_vld port = xxx```
プロトコル：valid信号あり
説明：データ線にvalid信号を付与する
例：
```c++
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;
}
```
![SCR-20250525-smqd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/b584e0a8-4b4d-4ba0-8dbd-7ffc71893bf4.png)

---

プラグマ：```#pragma HLS interface ap_ovld port = xxx```
プロトコル：valid信号あり(入出力信号)
説明：出力のデータ線にvalid信号を付与され、入力のデータ線にはap_noneが適用される。
例：
```c++
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;
}
```
![SCR-20250525-ugiv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/a7eea6f1-ca3d-4d25-a55a-a9dffc3e2791.png)


---

プラグマ：```#pragma HLS interface ap_ack port = xxx```
プロトコル：ack信号あり
説明：
入力データに対しては出力のack信号が生成され、入力が読み出されたタイミングでアサートされる。
出力データに対しては入力のack信号が生成され、出力が読み出されたことを確認される。
例：
```c++
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;
}
```
![SCR-20250525-tvvd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/f9ff3542-2936-4f17-88f1-395631cb0d7c.png)

:::note warn
CoSimでは出力のack信号を使用するデザインは検証できないです
:::

---

プラグマ：```#pragma HLS interface ap_hs port = xxx```
プロトコル：今までの全部入りのプロトコル
説明：2方向によるハンドシェイク
例：
```c++
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;
}
```
![SCR-20250525-udfx.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/d6a9b9db-e5ca-46d8-af25-b8e282d3016e.png)

### メモリ系
配列データがこれに当てはまります。

プラグマ：```#pragma HLS interface bram port = xxx```
プロトコル：シングルポートのメモリアクセス
説明：RAMのインターフェースとして合成される
例：
```c++
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;
}
```
![SCR-20250526-bafu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/f90a0150-c780-4688-84b6-9f9aecab84c1.png)

---

プラグマ：```#pragma HLS interface ap_memory port = xxx```
プロトコル：インターフェースが分離されたメモリアクセス
説明：RAMのインターフェースとして合成される
例：
```c++
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;
}
```
![SCR-20250525-uomr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/0e2b12b9-0216-4d6f-9bc3-043899b2c40d.png)

以下のように読み取りと書き込みの両方があればデュアルポートとしてIFが生成されます。
```c++
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;
    }
}
```
![SCR-20250526-borj.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/a5aba8d7-9335-453a-9f25-e1c41f30df49.png)

:::note info
bramとap_memoryの違い

![SCR-20250526-bdpx.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/db09e3a0-bb6a-4337-b72e-65c9f3e74ec0.png)
:::


### ストリーム系
こちらも配列データがこれに当てはまります。

プラグマ：```#pragma HLS interface ap_fifo port = xxx```
プロトコル：FIFOアクセス
説明：FIFOのインターフェースとして合成される
例：
```c++
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;
}
```
![SCR-20250601-rsjx.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/fedbc996-74c9-420b-a1ad-a3424b38da83.png)

## ブロックレベルの制御プロトコル

プラグマ：```#pragma HLS interface ap_ctrl_chain port = retrun```
プロトコル：以下図

![SCR-20250601-sups.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/e19dcc1a-a363-47de-a512-39de30b87261.png)

説明：チェーン制御
例：
```c++
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;
}
```
![SCR-20250601-tfss.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/3f0255da-20ea-4a5b-9270-661bdeacc7c2.png)

---

プラグマ：```#pragma HLS interface ap_ctrl_hs port = retrun```
プロトコル：先ほどの```ap_ctrl_chain```のap_continue信号が常に1になる。
説明：シーケンシャル制御
例：
```c++
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;
}
```
![SCR-20250601-telx.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/2d048584-0b32-43fb-9b2d-08e880fc7ed1.png)


ここで```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```
プロトコル：プロトコルなし
説明：プロトコルがないため、常にパイプラインで処理したい場合などで有利
例：
```c++
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;
}
```
![SCR-20250601-tzcw.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/d805ec45-e246-4e35-b1ff-5eef82b58463.png)

IF編はこれにて終了。