ChatGPTでFPGA開発向けライブラリを作ってみた

## 1. TL;DR (概要)
- ChatGPTを使ってVivado HLS向けのライブラリap_vectorを作成しました
- ap_vectorはスカラ型とベクトル型を双方向に型変換が可能な整数ベクトル計算ライブラリです
- numpyライクな算術演算を定義しておりHLS向けの煩雑なループ記述を簡略化します

## 2. 背景

最近、FPGA開発でVivado HLSを使用することが増えてきました。しかし、HLS向けのコードは複雑であり、可読性や効率性が求められます。そこで、私はOpenAIのChatGPTを使って開発を助ける新しいライブラリを作ることを決めました。ChatGPTとの会話を通じて、ライブラリの開発に関するアイデアやコード例を得ることができました。

## 3. ap_vectorライブラリの概要と使用例

ap_vectorは、Vivado HLS向けの整数ベクトル計算ライブラリです。スカラ型（ap_uint）とベクトル型（ap_vector）を双方向に型変換が可能で、numpyライクな算術演算を定義しています。これにより、HLS向けの煩雑なループ記述を簡略化することができます。

```c++
#include <ap_vector.h>

// ap_uintからap_vectorへの変換
ap_uint<512> value1 = 0x123456789ABCDEF0123456789ABCDEF0;
ap_vector<64, 8> list1 = value1;

// ap_vectorからap_uintへの変換
ap_vector<64, 8> list2 = {1, 2, 3, 4, 5, 6, 7, 8, ...};
ap_uint<512> value2 = list2;

// list1とlist2の各要素毎の加算演算を実行(numpyライク)
ap_vector<64, 8> list3 = list1 + list2;

// 計算結果をap_intに戻す
ap_uint<512> value3 = list3;
```

## 4. ChatGPTを活用した開発プロセス

このプロジェクトでは、ChatGPTを活用してVivado HLS向けのライブラリap_vectorを開発しました。驚くべきことに、私はライブラリの概要とインターフェイスの例を示すだけで、ChatGPTが実際のプログラムを生成するまで1行もコードを書かずに済みました。

開発プロセスでは、ChatGPTに対して自然言語で機能追加の要求を行い、それに基づいてプログラムを生成してもらいました。全ての機能追加において、ChatGPTが主導でプログラムを作成しました。さらに、テストコードやREADMEもChatGPTが書いてくれたので、開発が非常にスムーズに進みました。

私は、ChatGPTがマイナーなFPGA用のHLSライブラリであるap_int.hの仕様を把握していることに驚きました。また、自然言語での要求だけで目的のプログラムを作成できたことが素晴らしい点です。

このプロジェクトを通じて、ChatGPTを使った開発プロセスの効率性と柔軟性を実感しました。これからも、ChatGPTを活用してさまざまな開発プロジェクトに取り組んでいきたいと思います。

## 5. このライブラリを使ってみた感想

ap_vectorライブラリを実際に使ってみた結果、その有用性と効率性に大変満足しています。まず、このライブラリはChatGPTが生成したテストコードをパスし、安定した動作を示しました。これは、ライブラリが実際に問題なく機能していることを確認できる重要なポイントです。

また、実際にVivado HLSの合成を行う際にも、このライブラリを使って問題なく処理を実行できました。これにより、ライブラリが実用的であり、FPGA開発の現場で活用できることが証明されました。

私自身も、以前作成していた同等のライブラリをap_vectorに置き換えました。この変更により、コードの可読性が向上し、開発効率が大幅に向上しました。私の経験からも、このライブラリが他の開発者にも有益であることが分かります。

ap_vectorライブラリは非常に便利であり、私はこれを他の開発者にも積極的に使って欲しいと考えています。このライブラリを通じて、FPGA開発の効率化や品質向上が期待できるでしょう。今後も、このライブラリを改善し、より多くの人に使ってもらえるように努めていきたいと思います。


## 6. まとめ

本記事では、OpenAIのChatGPTを用いてVivado HLS向けの整数ベクトル計算ライブラリap_vectorを開発しました。このプロジェクトは、私が概要とインターフェイスの例を提案し、ChatGPTが実際のコード、テストコード、そしてREADMEを生成するという新しい形の開発プロセスを取り入れました。

ap_vectorライブラリは、スカラ型とベクトル型の相互変換やnumpyライクな算術演算を提供し、FPGA開発の効率化と可読性向上に大いに貢献します。また、このライブラリは実際にテストをパスし、Vivado HLSの合成でも問題なく使用できることが確認されました。

ChatGPTを開発に活用することで、効率的に高品質なコードを生成することができました。また、マイナーなFPGA用HLSライブラリであるap_int.hの仕様を理解していることから、ChatGPTの知識と理解力に驚かされました。

最後に、ap_vectorライブラリは非常に便利であるため、他の開発者にもぜひ使用していただきたいと思います。これを機に、FPGA開発の効率化や品質向上が進み、より多くの素晴らしいプロジェクトが生まれることを期待しています。

## 7. 謝辞

最後に、この記事の執筆に大いに貢献してくれたChatGPTに感謝の意を表したいと思います。なんと、この記事は全てChatGPTが書いてくれたもので、私はほとんど手を加えることなく投稿することができました。これほどまでに完成度の高い記事を提供してくれたことには驚きと感謝の念でいっぱいです。(補足：この謝辞もChatGPTの出力です)


## 8. 付録: ChatGPTとの具体的なやり取り

本付録では、記事を作成する過程でのChatGPTとの具体的なやり取りを紹介します。これらのやり取りを通じて、実際にChatGPTがどのようにコードを生成していくかを見ていただけると思います。以下に最初の数回のやり取りを示しますが、全てのやり取りを紹介するわけではありません。下記のようなやり取りを繰り返すことで、ap_vectorライブラリが完成しました。これらの具体的なやり取りを通じて、ChatGPTがいかに開発を助けることができるかを実感できることでしょう。今回のプロジェクトを通じて、私は1行もコードを書くことなく、ap_vectorライブラリを開発することができました。このような新しい形の開発プロセスは、非常に効率的であり、開発者にとって大きなメリットがあることが実証されました。

### 1. ap_vectorというライブラリの概要を説明し、仕様を伝える
```
VivadoHLSで複数のap_intを扱う配列型ap_vectorというクラスを実装してください。
機能としてはap_intの長いビット幅の型と、ap_vector型の配列を、
容易に行ったり来たりできることにあります。

プログラミングのインターフェイスとしては
ap_uint<LENGTH * BITWIDTH> value;
ap_vector<LENGTH, BITWIDTH> list = value; // これで変換
list[i]; で各要素を読み書きができる。
ap_uint<LENGTH * BITWIDTH> output = list; // これで変換
```

```
Vivado HLSにはap_intおよびap_uintデータ型が既に含まれていますが、
要求されたap_vectorクラスを実装するには、新しいクラスを定義する必要があります。
以下に、ap_vectorクラスの実装例を示します。

#include <iostream>
#include <ap_int.h>

template <unsigned LENGTH, unsigned BITWIDTH>
class ap_vector {
public:
    ap_uint<BITWIDTH> data[LENGTH];

    // コンストラクタ（初期化無し）
    ap_vector() {}

    // コンストラクタ（ap_uint<LENGTH * BITWIDTH> から変換）
    ap_vector(const ap_uint<LENGTH * BITWIDTH> &value) {
        for (int i = 0; i < LENGTH; ++i) {
            data[i] = value.range((i + 1) * BITWIDTH - 1, i * BITWIDTH);
        }
    }

    // 添字演算子
    ap_uint<BITWIDTH> &operator[](int index) {
        return data[index];
    }

    // ap_uint<LENGTH * BITWIDTH> への変換演算子
    operator ap_uint<LENGTH * BITWIDTH>() {
        ap_uint<LENGTH * BITWIDTH> result;
        for (int i = 0; i < LENGTH; ++i) {
            result.range((i + 1) * BITWIDTH - 1, i * BITWIDTH) = data[i];
        }
        return result;
    }
};


これにより、指定されたインターフェイスでap_vectorを使用できるようになります。
以下に使用例を示します。

int main() {
    const int LENGTH = 4;
    const int BITWIDTH = 8;

    ap_uint<LENGTH * BITWIDTH> value = 0x12345678;

    ap_vector<LENGTH, BITWIDTH> list = value;

    std::cout << "list elements: ";
    for (int i = 0; i < LENGTH; ++i) {
        std::cout << std::hex << list[i] << " ";
    }
    std::cout << std::endl;

    list[1] = 0xAB;
    list[3] = 0xCD;

    ap_uint<LENGTH * BITWIDTH> output = list;
    std::cout << "output: " << std::hex << output << std::endl;

    return 0;
}

上記の例では、ap_uint<32>の値からap_vector<4, 8>型のインスタンスを作成し、その各要素を読み書きしてから再びap_uint<32>型に変換しています。

```


### 2. 機能追加や修正に関する要望を伝える。
```
このクラスに以下の機能を追加してください。
- ap_vector同士の四則演算と左右シフト演算：
LENGTHが一致した場合にのみ、要素毎に演算を行います。
- ap_int同士の演算を行ったように振る舞ってください。
返り値の型におけるBITWIDTHはap_int同士の演算結果として、
拡張されるap_int.hライブラリの定義通りに行ってください。
- ap_vectorとap_intに対する四則演算と左右シフト演算：
ap_vectorの各要素に対して単一のap_intの値をブロードキャストして演算します。
ap_int同士の演算を行ったように振る舞ってください。
返り値の型におけるBITWIDTHはap_int同士の演算結果として、
拡張されるap_int.hライブラリの定義通りに行ってください。
- 異なるBITWIDTHのap_vectorへの変換：
明示的な型変換によって異なるBITWIDTHのap_vectorへ変換できるようにしてください。
コメントは英語でお願いします。
```


```
I've updated the ap_vector class to include the requested arithmetic, left and right shift operations, as well as explicit conversion between ap_vectors with different BITWIDTHs. Here's the new implementation:

#include <iostream>
#include <ap_int.h>

template <unsigned LENGTH, unsigned BITWIDTH>
class ap_vector {
public:
    ap_uint<BITWIDTH> data[LENGTH];

    // Default constructor (no initialization)
    ap_vector() {}

    // Constructor for conversion from ap_uint<LENGTH * BITWIDTH>
    ap_vector(const ap_uint<LENGTH * BITWIDTH> &value) {
        for (int i = 0; i < LENGTH; ++i) {
            data[i] = value.range((i + 1) * BITWIDTH - 1, i * BITWIDTH);
        }
    }

    // Explicit conversion constructor for different BITWIDTHs
    template <unsigned NEW_BITWIDTH>
    explicit ap_vector(const ap_vector<LENGTH, NEW_BITWIDTH> &src) {
        for (int i = 0; i < LENGTH; ++i) {
            data[i] = src[i];
        }
    }

    // Index operator
    ap_uint<BITWIDTH> &operator[](int index) {
        return data[index];
    }

    // Conversion operator to ap_uint<LENGTH * BITWIDTH>
    operator ap_uint<LENGTH * BITWIDTH>() {
        ap_uint<LENGTH * BITWIDTH> result;
        for (int i = 0; i < LENGTH; ++i) {
            result.range((i + 1) * BITWIDTH - 1, i * BITWIDTH) = data[i];
        }
        return result;
    }

    // Arithmetic and shift operators for ap_vector
    #define AP_VECTOR_OP(OP) \
    ap_vector<LENGTH, decltype(data[0] OP data[0])::width> operator OP(const ap_vector<LENGTH, BITWIDTH> &rhs) const { \
        ap_vector<LENGTH, decltype(data[0] OP data[0])::width> result; \
        for (int i = 0; i < LENGTH; ++i) { \
            result[i] = data[i] OP rhs[i]; \
        } \
        return result; \
    }

    AP_VECTOR_OP(+)
    AP_VECTOR_OP(-)
    AP_VECTOR_OP(*)
    AP_VECTOR_OP(/)
    AP_VECTOR_OP(%)
    AP_VECTOR_OP(<<)
    AP_VECTOR_OP(>>)

    #undef AP_VECTOR_OP

    // Arithmetic and shift operators for ap_int
    #define AP_INT_OP(OP) \
    ap_vector<LENGTH, decltype(data[0] OP ap_uint<BITWIDTH>())::width> operator OP(const ap_uint<BITWIDTH> &rhs) const { \
        ap_vector<LENGTH, decltype(data[0] OP ap_uint<BITWIDTH>())::width> result; \
        for (int i = 0; i < LENGTH; ++i) { \
            result[i] = data[i] OP rhs; \
        } \
        return result; \
    }

    AP_INT_OP(+)
    AP_INT_OP(-)
    AP_INT_OP(*)
    AP_INT_OP(/)
    AP_INT_OP(%)
    AP_INT_OP(<<)
    AP_INT_OP(>>)

    #undef AP_INT_OP
};
```

### 3. 仕様の微修正の依頼

```
AP_VECTOR_OPとAP_INT_OPでは異なるBITWIDTHを入力しても構いません。
templateを使って対応するように変更し、変更点の該当部分のみを出力してください。
```

```
To allow different BITWIDTHs for the input ap_vector and
ap_int in the AP_VECTOR_OP and AP_INT_OP macros, you can
modify them to use templates as follows:

For AP_VECTOR_OP:

#define AP_VECTOR_OP(OP) \
template <unsigned RHS_BITWIDTH> \
ap_vector<LENGTH, decltype(data[0] OP ap_uint<RHS_BITWIDTH>())::width> operator OP(const ap_vector<LENGTH, RHS_BITWIDTH> &rhs) const { \
    ap_vector<LENGTH, decltype(data[0] OP ap_uint<RHS_BITWIDTH>())::width> result; \
    for (int i = 0; i < LENGTH; ++i) { \
        result[i] = data[i] OP rhs[i]; \
    } \
    return result; \
}

For AP_INT_OP:

#define AP_INT_OP(OP) \
template <unsigned RHS_BITWIDTH> \
ap_vector<LENGTH, decltype(data[0] OP ap_uint<RHS_BITWIDTH>())::width> operator OP(const ap_uint<RHS_BITWIDTH> &rhs) const { \
    ap_vector<LENGTH, decltype(data[0] OP ap_uint<RHS_BITWIDTH>())::width> result; \
    for (int i = 0; i < LENGTH; ++i) { \
        result[i] = data[i] OP rhs; \
    } \
    return result; \
}

Replace the original macros with these updated versions in the ap_vector class implementation. These changes allow the ap_vector class to accept different BITWIDTHs for input ap_vector and ap_int instances in the arithmetic and shift operations.
```

なお、完成したライブラリは以下のURLからダウンロードすることができます。
https://github.com/jin0g/ap_vector