[高位合成] C言語で組み合わせ回路と順序回路はどうやって作るのか？

# はじめに

初歩の説明ということでまずはこのテーマで。

# 前提
以下の経験がある前提で話を進めます。
- FPGAを触ったことがある。
具体的なレベル感としては組み合わせ回路や順序回路とかがわかれば大丈夫です。
- C/C++を少し触ったことがある。
書いてあることがなんとなくわかるレベルであれば大丈夫です。

# 動作環境
PC：M4 Macbook Pro(Sequoia 15.1)
IDE：VitisHLS 2023.1

※皆さんはWindows PCでやってくださいね

https://github.com/ichi4096/vivado-on-silicon-mac

:::note info
Vitis Unified IDEという最新のIDEがありますが、今回はそれを使用しません。
割と最近でてきたIDEなのでバグがあるのかなと思ってまだほとんど使用したことないです。
2024Verなら大分安定しているのかな？
:::

# ソースコード
全体のソースコードは以下です。

https://github.com/kiiisy/VitisHLS_sample


# 基本的な説明
本題入る前に、「C言語でどのようにFPGAを開発するのか」という基本的な説明を軽くします。

まず開発フローですが、以下のような工程で進めます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/78553dfd-cddd-484f-a475-35a32c7253c0.png)

| 工程           | 説明                              |
|:-------------:|:---------------------------------:|
| C Simulation  | C言語レベルでのシミュレーションを行う   |
| C Synthesis   | 合成を行う(この時点でRTLが生成される)  |
| CoSimulation  | FPGAレベルでのシミュレーションを行う   |

次に、「C言語のコードがどうVerilogに紐づくのか」です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/115c88e0-a218-4995-b3ce-607c64325a2e.png)

クロックやリセットはどうなるの？と思いますが、これらは全部ツール側で**いい感じ**にしてくれます。
なので、基本的にはあまり意識しなくていいです。
あと、高位合成の特徴の一つとして、```#pragma```というのを使って細かな命令を与えることができます。

では、実際に作っていきましょう。

# 組み合わせ回路

以下の組み合わせ回路を例とします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/cf61e4c4-080a-4727-920a-c962c863c62e.png)

### ソースコード

```c++
void CombinationalCircuit(bool a, bool b, bool c, bool *d)
{
#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_none port = d
#pragma HLS INTERFACE ap_ctrl_none port = return

    *d = a && b && c;
}

```

### 解説
- 入出力について
関数の引数が入出力になります。型についてはC言語の型をそのまま記載できます。
C/C++の**値渡し**をしたら**入力**になり、**ポインタ渡し**にしたら**出力**となります。
そしてTOP階層の関数には #pramga の定義を記載します。
```HLS INTERFACE```プラグマはIFのプロトコルなどを設定するものになります。
今回の```ap_none/ap_ctrl_none```はプロトコルなしの純粋なIFという意味になります。
- 処理について
基本的にはC/C++と同じです。
今回の場合だとAND回路を作りたいので、```&&```演算子を記載するだけです。

### 合成結果

![SCR-20250419-nryn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/9b6779b5-df02-4abb-b0c0-220f5a2a349f.png)

組み合わせ回路のみなので、FFが使用されず、LUTのみ使用されています。


![SCR-20250419-nsxr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/1e0dd101-f9a9-4d76-a2e5-ed3df4a10518.png)

入出力も意図した通りになっています。

### シミュレーション

シミュレーションのソースコードは制限なしでフルのC/C++機能を使用できます。

```c++
void GoldenModel(bool a, bool b, bool c, bool *d)
{
    *d = a && b && c;
}

int main()
{
    std::vector<bool> a_values = {true, false};
    std::vector<bool> b_values = {true, false};
    std::vector<bool> c_values = {true, false};
    bool d, d_golden;

    for (bool a : a_values)
    {
        for (bool b : b_values)
        {
            for (bool c : c_values)
            {
                CombinationalCircuit(a, b, c, &d);
                GoldenModel(a, b, c, &d_golden);

                if (d == d_golden)
                {
                    std::cout << "Test passed!" << std::endl;
                }
                else
                {
                    std::cout << "Test failed!" << std::endl;
                }
            }
        }
    }

    return 0;
}
```

![SCR-20250419-oahk.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/8def9e2d-414c-4177-86b7-79891a681103.png)

期待通りに最初だけTrueが出力され、その後はFalseが出力されていますね。

# 順序回路

以下の順序回路を例とします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/3b080d8b-28ec-4956-a9d7-e5f1cbd6ef0d.png)

### ソースコード

```c++
#include "sequential_circuit.h"

void SequentialCircuit(bool a, bool *c1, bool *c2)
{
#pragma HLS INTERFACE ap_none port = a
#pragma HLS INTERFACE ap_none port = c1
#pragma HLS INTERFACE ap_none port = c2
#pragma HLS INTERFACE ap_ctrl_none port = return

    static bool ff1 = false;
    static bool ff2 = false;

    ff2 = ff1;
    ff1 = a;

    *c1 = ff1;
    *c2 = ff2;
}

```

### 解説
- 入出力について
順序回路なので、クロック&リセット信号がいるのでは？と思いますが、これは意識しなくても大丈夫です。
ツール側が上手いことやってくれます。
- 処理について
まず、static宣言ですがこれを付与することによりその変数がFFとして定義されます。RTLで言えばregになります。
付与しなくてもFFになることはありますが、それは**ツール任せ**という結果になります。
また、staticの初期化ですがこの書き方をするとPORになります。

### 合成結果

![SCR-20250426-pdzw.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/76e30f69-6530-46b2-994f-53adc0ef074c.png)

FFが使用されていますね。

![SCR-20250426-pepi.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/59ada135-25c8-4405-a830-ce528d9f55c7.png)

入出力も意図した通りになっています。

### シミュレーション

```c++
#include <iostream>
#include <vector>
#include "../src/sequential_circuit.h"

void GoldenModel(bool a, bool *c1, bool *c2)
{
    static bool ff1 = false;
    static bool ff2 = false;

    ff2 = ff1;
    ff1 = a;

    *c1 = ff1;
    *c2 = ff2;
}

int main()
{
    std::vector<bool> a_values = {true, false};

    // テストベンチ
    for (bool a : a_values)
    {
        bool c1, c2;
        SequentialCircuit(a, &c1, &c2);

        bool golden_c1, golden_c2;
        GoldenModel(a, &golden_c1, &golden_c2);

        std::cout << "Input: " << a << ", Output: (" << c1 << ", " << c2 << "), Golden: (" << golden_c1 << ", " << golden_c2 << ")" << std::endl;

        if (c1 != golden_c1 || c2 != golden_c2)
        {
            std::cout << "Mismatch!" << std::endl;
            return 1;
        }
    }
    std::cout << "All tests passed!" << std::endl;

    return 0;
}

```

![SCR-20250426-phys.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/dc495eb9-a000-4ffe-b7bb-0746f698f117.png)

これも問題ないですね。

# おまけ1

先ほどの順序回路で```ff2```から代入していましたよね。
なぜこの順番なのか、```ff1 = a```じゃダメなんですか？と思いますが、結論言うとダメです。

↓これじゃダメ？
```c++
ff1 = a;
ff2 = ff1;
```

では試しにこれで作ってみましょう。

![SCR-20250426-pmfj.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/824502b9-4636-430f-b75e-4871b2cec5c7.png)

合成結果は上記です。あれ？FFがなくなっている…
どうなってるんだ、という感じですよね。
とりあえず次にシミュレーションしてみましょうか。

![SCR-20250426-poek.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/de709d44-4df8-4347-8bf3-2c2d1781bc7d.png)

あれれ？これ組み合わせ回路になってない？？と思いましたよね。
そうなんです、これは組み合わせ回路ができあがります。

なぜあの書き方をすると組み合わせ回路になってしまうのかですが、理由は簡単です。
ツール側で最適化が働いているからです。

```c++
ff1 = a;
ff2 = ff1;
```

これだと```ff2```に関しては、```ff2 = a```とも読み取れますよね。
だからツール側が、「```ff1```いらなくね？てか```ff2```もいらなくね？じゃあ入力をそのまま出力でよくね？」となる。
そして組み合わせ回路ができあがります。

では実際に生成されたverilogを見てみましょう。

↓生成されたverilog
![SCR-20250426-prtq.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/7b24cd6d-2dd6-4825-a57e-1108dfbf2102.png)

やっぱり入力がそのまま出力として出ていますね。ということです。

# おまけ2(VitisHLSの使い方)
:::note info
最低限の説明だけします。
:::

### プロジェクト生成方法

1. Vitis HLSを起動する。コマンド```vitis_hls```を入力。
2. 「Create Project」を押下する。
![SCR-20250426-nlke-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/c08c1e74-0167-4a03-9c1c-9b12a315a643.png)
3. 「Project name」と「Location」を入力する。
![SCR-20250426-nmrp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/e5f1b0fd-d0bd-4e9f-8315-ba926bc3333b.png)
4. 「New Vitis HLS Project」画面は「Next」を押下する。(2回出てくるので両方とも)
5. 「...」で対象のデバイスを選択できるため設定する。他は必要に応じて設定する。
![SCR-20250426-nnqo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/acedd6e5-989a-4151-aec4-35252e199296.png)

### ソースコードの登録方法

1. ソースコード(テストベンチも)の用意をする。
2. 「Source」を右クリックし、「Add Source File...」を押下し、ソースコードを登録する。
![SCR-20250426-npdk.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/0c38acf1-4a07-4338-b569-6519431a76b4.png)
3. 「Test Bench」を右クリックし、「Add Source File...」を押下し、ソースコードを登録する。
![SCR-20250426-nqvc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/c5a14a4f-fd34-4d5f-b095-79be1e35935a.png)
4. プロジェクトを選択して、右クリックを押下し、「Project Settings...」を押下する。
![SCR-20250426-oldb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/73503d8f-d26b-447e-904e-ed7b738b21bc.png)
5. 「Synthesis」を選択し、Top Function欄にTop階層となる関数を設定する。
![SCR-20250426-omiu-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/99ff9d5a-f54e-478c-9bc4-c964953c124f.png)

### 合成/シミュレーションのやり方

1. 「緑の再生」的なボタンの右の下三角形を押下する。
![SCR-20250426-nrik.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/abefb571-9a24-4b88-bd7b-8904b5aec8ab.png)
2. 「C Simulation」を押下する。下の画面が表示されるので、「OK」を押下する。
「Options」は任意でチェックする。
デバッグをしたい場合は「Launch Debugger」をチェックする。
「Clean Build」は差分ビルドをせずに、全ビルドを行う。
![SCR-20250426-nykw.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/cab15b06-93f4-47ac-ba24-a26b0b4ad23a.png)
3. 「C Synthesis」を押下する。下の画面が表示されるので、「OK」を押下する。
![SCR-20250426-nzou.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/958ad493-12b8-4cad-927b-76bb2341029e.png)
4. 「CoSimulation」を押下する。下の画面が表示されるので、「OK」を押下する。
「Vivado XSIM」と書いてあるのは、Vivadoを使ってシミュレーションするという意味です。
横の三角形を押下すると他のシミュレーションが出てきます。
「Dump Trace」に「all」を入れることにより、シミュレーション結果をダンプできます。
![SCR-20250426-oacf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3290770/92263db0-017d-4651-97c7-c1c351b7f4d3.png)

これにてお終い。