はじめに
Vitis HLSで足し算の関数を作成して論理合成して、動作させてみました。
Vitis 2024.2/Vivado 2024.2を使用しています。
備忘録として残しておきます。
VitisでHLSでIPを作る
VitisでCreate HLS Componentを選択して、ウイザードに従って値を入力
flow_targetは、Vivado IP Flow Targetを選択
package.output.formatは、Generate Vivado IP and .zip archiveを選択
#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に作ってもらいました。
#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
Create Block Designを選択します。Design nameは、design_hls_addとします。
DiagramでZYNQ7 Processing Systemを追加します。
Diagramで、add ipを実行して、add32を追加します。
Run Connection Automationを実行して、接続します。
Create HDL Wrapperを選択して、OKを押します。
Generate Bitstre を実行
Export Hardware Platformを実行
Incloud bitstreamをチェックする
ファイル名は、デフォルト値の、design_hls_add_wrapper
して、OKを押します。
Standaloneアプリで動作させてみる
VitisでPlatformを作成します。
作成時に、先ほど作成したXSAファイルを指定します。
Operating Systemは、Standaloneを選択します。
Platformをビルドしてみます。
ビルドすると、Output/Platform/hw/drivers/add32_v1_0/srcに、xadd32.c, xadd32.hが作成されます。
これを呼び出すことで、作成したIPを動作させることができます。
// ==============================================================
// 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で結果を取得する
#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からも呼び出す事ができると思います。今後試して見たいと思います。