Vitis C言語で作成した関数を論理合成して動作させてみる

# はじめに

Vitis HLSで足し算の関数を作成して論理合成して、動作させてみました。
Vitis 2024.2/Vivado 2024.2を使用しています。
備忘録として残しておきます。

# VitisでHLSでIPを作る

VitisでCreate HLS Componentを選択して、ウイザードに従って値を入力
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532235/2667f53b-a7b8-0fec-3ee8-0b4619a0547e.png)


flow_targetは、Vivado IP Flow Targetを選択
package.output.formatは、Generate Vivado IP and .zip archiveを選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532235/e43a6193-e0e6-90dd-3e8e-dba52ecf9316.png)


```C:add.cpp
#include <ap_int.h>

ap_uint<32> add32(ap_uint<32> *in1, ap_uint<32> *in2) {
#pragma HLS TOP name=add32
#pragma HLS INTERFACE s_axilite port=in1
#pragma HLS INTERFACE s_axilite port=in2
#pragma HLS INTERFACE s_axilite port=return

    return *in1 + *in2;
}
```

テストするほどでも無いですが、テストベンチを作成してみます。
ChatGPTに作ってもらいました。

```C:add_tb.cpp
#include <iostream>
#include <ap_int.h>

ap_uint<32> add32(ap_uint<32> *in1, ap_uint<32> *in2);

int main() {
    ap_uint<32> in1, in2, out, expected_out;

    in1 = 10;
    in2 = 20;
    expected_out = in1 + in2;

    out = add32(&in1, &in2);

    if (out == expected_out) {
        std::cout << "Test Case 1 Passed: " << out << " == " << expected_out << std::endl;
    } else {
        std::cerr << "Test Case 1 Failed: " << out << " != " << expected_out << std::endl;
    }

    in1 = 0xFFFFFFFF;
    in2 = 1;
    expected_out = in1 + in2;

    out = add32(&in1, &in2);

    // 結果の確認
    if (out == expected_out) {
        std::cout << "Test Case 2 Passed: " << out << " == " << expected_out << std::endl;
    } else {
        std::cerr << "Test Case 2 Failed: " << out << " != " << expected_out << std::endl;
    }

    in1 = 0;
    in2 = 0;
    expected_out = in1 + in2;

    out = add32(&in1, &in2);

    if (out == expected_out) {
        std::cout << "Test Case 3 Passed: " << out << " == " << expected_out << std::endl;
    } else {
        std::cerr << "Test Case 3 Failed: " << out << " != " << expected_out << std::endl;
    }

    return 0;
}
```

VitisのフローのC SIMULATION SYNTHESIS C/RTL CO-SIMULATION PACKAGEを順に実行します。

次のファイルが作成されました。
```hls_add/hls_add/hls_add/hls/impl/ip/xilinx_com_hls_add32_1_0.zip```

# vivadoでハードウエアを作成する

Vivadoを起動して、新規プロジェクトを作成します。
プロジェクト名は、vivado_hls_addとします。

IP Repositoryに、先ほど作成したIPのプロジェクトフォルダを追加します。hls_add/hls_add

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532235/c143b151-cca4-f3ca-5c29-a233346e4e62.png)

Create Block Designを選択します。Design nameは、design_hls_addとします。

DiagramでZYNQ7 Processing Systemを追加します。

Diagramで、add ipを実行して、add32を追加します。
Run Connection Automationを実行して、接続します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532235/66128b43-1b3f-ec47-4a5b-b9165e211893.png)

Create HDL Wrapperを選択して、OKを押します。

Generate Bitstre を実行

Export Hardware Platformを実行
Incloud bitstreamをチェックする
ファイル名は、デフォルト値の、design_hls_add_wrapper
して、OKを押します。

# Standaloneアプリで動作させてみる

VitisでPlatformを作成します。
作成時に、先ほど作成したXSAファイルを指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532235/0b77e4d0-d564-b6b6-ba7e-997efb039e79.png)

Operating Systemは、Standaloneを選択します。

Platformをビルドしてみます。

ビルドすると、Output/Platform/hw/drivers/add32_v1_0/srcに、xadd32.c, xadd32.hが作成されます。
これを呼び出すことで、作成したIPを動作させることができます。


```C:xadd32.h
// ==============================================================
// Vitis HLS - High-Level Synthesis from C, C++ and OpenCL v2024.2 (64-bit)
// Tool Version Limit: 2024.11
// Copyright 1986-2022 Xilinx, Inc. All Rights Reserved.
// Copyright 2022-2024 Advanced Micro Devices, Inc. All Rights Reserved.
// 
// ==============================================================
#ifndef XADD32_H
#define XADD32_H

#ifdef __cplusplus
extern "C" {
#endif

/***************************** Include Files *********************************/
#ifndef __linux__
#include "xil_types.h"
#include "xil_assert.h"
#include "xstatus.h"
#include "xil_io.h"
#else
#include <stdint.h>
#include <assert.h>
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stddef.h>
#endif
#include "xadd32_hw.h"

/**************************** Type Definitions ******************************/
#ifdef __linux__
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
#else
typedef struct {
#ifdef SDT
    char *Name;
#else
    u16 DeviceId;
#endif
    u64 Control_BaseAddress;
} XAdd32_Config;
#endif

typedef struct {
    u64 Control_BaseAddress;
    u32 IsReady;
} XAdd32;

typedef u32 word_type;

/***************** Macros (Inline Functions) Definitions *********************/
#ifndef __linux__
#define XAdd32_WriteReg(BaseAddress, RegOffset, Data) \
    Xil_Out32((BaseAddress) + (RegOffset), (u32)(Data))
#define XAdd32_ReadReg(BaseAddress, RegOffset) \
    Xil_In32((BaseAddress) + (RegOffset))
#else
#define XAdd32_WriteReg(BaseAddress, RegOffset, Data) \
    *(volatile u32*)((BaseAddress) + (RegOffset)) = (u32)(Data)
#define XAdd32_ReadReg(BaseAddress, RegOffset) \
    *(volatile u32*)((BaseAddress) + (RegOffset))

#define Xil_AssertVoid(expr)    assert(expr)
#define Xil_AssertNonvoid(expr) assert(expr)

#define XST_SUCCESS             0
#define XST_DEVICE_NOT_FOUND    2
#define XST_OPEN_DEVICE_FAILED  3
#define XIL_COMPONENT_IS_READY  1
#endif

/************************** Function Prototypes *****************************/
#ifndef __linux__
#ifdef SDT
int XAdd32_Initialize(XAdd32 *InstancePtr, UINTPTR BaseAddress);
XAdd32_Config* XAdd32_LookupConfig(UINTPTR BaseAddress);
#else
int XAdd32_Initialize(XAdd32 *InstancePtr, u16 DeviceId);
XAdd32_Config* XAdd32_LookupConfig(u16 DeviceId);
#endif
int XAdd32_CfgInitialize(XAdd32 *InstancePtr, XAdd32_Config *ConfigPtr);
#else
int XAdd32_Initialize(XAdd32 *InstancePtr, const char* InstanceName);
int XAdd32_Release(XAdd32 *InstancePtr);
#endif

void XAdd32_Start(XAdd32 *InstancePtr);
u32 XAdd32_IsDone(XAdd32 *InstancePtr);
u32 XAdd32_IsIdle(XAdd32 *InstancePtr);
u32 XAdd32_IsReady(XAdd32 *InstancePtr);
void XAdd32_EnableAutoRestart(XAdd32 *InstancePtr);
void XAdd32_DisableAutoRestart(XAdd32 *InstancePtr);
u32 XAdd32_Get_return(XAdd32 *InstancePtr);

void XAdd32_Set_in1(XAdd32 *InstancePtr, u32 Data);
u32 XAdd32_Get_in1(XAdd32 *InstancePtr);
void XAdd32_Set_in2(XAdd32 *InstancePtr, u32 Data);
u32 XAdd32_Get_in2(XAdd32 *InstancePtr);

void XAdd32_InterruptGlobalEnable(XAdd32 *InstancePtr);
void XAdd32_InterruptGlobalDisable(XAdd32 *InstancePtr);
void XAdd32_InterruptEnable(XAdd32 *InstancePtr, u32 Mask);
void XAdd32_InterruptDisable(XAdd32 *InstancePtr, u32 Mask);
void XAdd32_InterruptClear(XAdd32 *InstancePtr, u32 Mask);
u32 XAdd32_InterruptGetEnabled(XAdd32 *InstancePtr);
u32 XAdd32_InterruptGetStatus(XAdd32 *InstancePtr);

#ifdef __cplusplus
}
#endif

#endif
```

applicationを作成します。Platformは、上で作成したものを指定します。

アプリケーションのSettingsのlaunch.jsonのBoard InitializationをFSBLに変更します。
これを行わないと、エラーが発生して動作しませんでした。
TCLでないと動かな場合もあったりして、よくわかっていません。

アプリケーションのSources/srcにmain.cを新規作成します。

関数の呼び出しは、次の手順にします。

- パラメータの設定
- XAdd32_Startを実行
- XAdd32_IsDoneが真になるまで待つ
- XAdd32_Get_returnで結果を取得する


```C:main.c
#include <stdio.h>
#include "xadd32.h"

int init_xadd32(XAdd32 *add32, XAdd32_Config *config) {
    return XAdd32_CfgInitialize(add32, config);
}

int main() {
    u32 ret;
    XAdd32 add32;
    XAdd32_Config config = {
        .Control_BaseAddress = 0x40000000  // VivadoのIPアドレスMAPに合わせる
    };

    if (init_xadd32(&add32, &config) != XST_SUCCESS) {
        printf("XAdd32 の初期化に失敗しました\n");
        return -1;
    }

    u32 a = 10;
    u32 b = 20;

    XAdd32_Set_in1(&add32, a);
    XAdd32_Set_in2(&add32, b);

    u32 result, tmp;

    tmp = XAdd32_Get_in1(&add32);
    tmp = XAdd32_Get_in2(&add32);
    XAdd32_Start(&add32);
    while (1) {
        ret = XAdd32_IsDone(&add32);
        if (ret) {
            break;
        }
    }
    result = XAdd32_Get_return(&add32);
    printf("結果: %u + %u = %u\n", a, b, result);

    return 0;
}
```

ビルドして、デバッカーで実行します。

ステップ実行してみると、```result```に1e＝（十進数で30）が入っていることが確認できました。

# まとめ

- VitisでHLSでIPを作成して、Vivadoでハードウエアを作成し、Standaloneアプリで動作させるまでの手順を紹介しました。
- 作成されたDriversの関数を呼び出す事で、ハードウエアを動作させ、演算結果を得る事ができました。
- 今回は、簡単な加算の例を紹介しましたが、他の演算も同様に作成できるとおもいます。

今回は、Standaloneアプリで作成しましたが、Linuxアプリで作成することもできます。
原理的には、NIFを使う事で、Elixirからも呼び出す事ができると思います。今後試して見たいと思います。