はじめに
本記事ではXilinx Zynq FPGA SoC(以下、FPGA)にできるだけ環境構築をせずに独自Bitstreamを使用してPLとPSで通信を行う方法について解説します。本記事ではKria SOM KV260をターゲットにしますが、同様の方法で、KD240、KR260、ZCU102、ZCU104、ZCU106、VCK190で動作が可能です。
FPGAを使う方法は大きく3つあります。
- Vivadoで作成したBitstreamを生のMMIOで扱う方法。FPGA側の環境構築がほとんど不要ですが、生のMMIOを扱うホストプログラムを記述する必要があり、原則として動作にはroot権限が必要となります。本記事ではこの方法を説明します。
- Vivadoで作成したBitstreamをPYNQライブラリから扱う方法。メモリマップをライブラリが解決し、Pythonで簡単にホストプログラムを書くことができますが、同様にroot権限が必要なことと、Pythonのオーバーヘッドがあります。こちらの記事で解説しています。
- Vitis(v++)で作成したxclbinをXRTから扱う方法。PLとPSの接続を簡潔に記述でき、XRTで簡単にホストプログラムを書くことができ、設定によりroot権限が不要で動作できますが、環境構築が少し複雑で、VitisとXRTのバージョンの互換性がシビアです。
本記事では1番のBitstreamを生のMMIOで扱う方法を紹介します。この方法はFPGAの初期設定をほとんど行うことなくFPGAを扱うことができます。
検証環境の紹介
PC
- IntelまたはAMDのCPU搭載PC
- メモリ32GB以上推奨
- Ubuntu Server 24.04
- Vivado 2024.2
- Vitis HLS 2024.2
FPGA
- Kria SOM KV260
- Ubuntu Server 24.04
FPGAの環境構築
Ubuntu公式サイトからUbuntu Serverのイメージをダウンロードし、Balena Etcherなどを使ってSDカードにイメージを焼きます。SDカードをFPGAに挿入し、LANケーブルと、お好みに応じてシリアルケーブルを接続してから、電源を接続します。
DHCPからIPアドレスを探してSSHすれば、シリアルケーブルの接続は省略できます。デフォルトのユーザー名とパスワードはubuntu / ubuntuです。初回にパスワードの変更を求められます。FPGA側の環境構築は以上です。
HLSカーネルの合成
まずVitis HLSでadd関数を合成し、Vivado IPパッケージを生成する必要があります。HLSのトップ関数はint add(int a, int b)とします。
add.cpp
int add(int a, int b) {
#pragma HLS INTERFACE mode=s_axilite port=return
#pragma HLS INTERFACE mode=s_axilite port=a
#pragma HLS INTERFACE mode=s_axilite port=b
return a + b;
}
hls.tcl
open_component -reset -flow_target vivado add
add_files add.cpp
set_top add
set_part xck26-sfvc784-2LV-c
create_clock -period 10
csynth_design
export_design -rtl verilog -format ip_catalog
exit
HLS合成の実行
vitis-run --mode hls --tcl hls.tcl
合成後に./add/hls/impl/ip/component.xmlを開き、AXI-Liteレジスタの配置を確認します。本記事の合成結果は以下のような配置になりました。HLS関数の引数を変更した場合は、component.xmlの配置を確認してホストプログラム側も合わせて編集してください。
-
0x00:ap_ctrl -
0x10:ap_return -
0x18:a -
0x20:b
VivadoでBitstreamを作成する
Vivadoを用いてFPGAのAXIマスタとHLS IPのAXI-Lite制御ポートを接続します。本記事ではオートメーション機能による設定と接続を多用します。詳しい仕様はAMD UG835を参照してください。
bit.tcl
# PSとHLS IPを自動接続してFPGA用Bitstreamを作成する
create_project -force add build/vivado -part xck26-sfvc784-2LV-c
set_property board_part xilinx.com:kv260_som:part0:1.3 [current_project]
# ブロックデザインを作成する
create_bd_design bd
# PS領域を設定する
create_bd_cell -type ip -vlnv xilinx.com:ip:zynq_ultra_ps_e:3.5 ps
apply_bd_automation -rule xilinx.com:bd_rule:zynq_ultra_ps_e -config {apply_board_preset 1} [get_bd_cells ps]
set_property -dict [list CONFIG.PSU__USE__M_AXI_GP1 {0}] [get_bd_cells ps]
# 先ほど作成したIPパッケージを登録する
set_property ip_repo_paths add/hls/impl/ip [current_project]
update_ip_catalog
# HLS合成で作成したIPのインスタンスを作成する
create_bd_cell -type ip -vlnv xilinx.com:hls:add:1.0 add_0
# AXI-Lite接続、SmartConnect、クロック、リセットを自動生成・接続する
apply_bd_automation -rule xilinx.com:bd_rule:axi4 -config {Master "/ps/M_AXI_HPM0_FPD" Clk "Auto"} [get_bd_intf_pins add_0/s_axi_control]
# アドレス割り当ては自動化しつつ、hostのMMIOアドレスは固定する
assign_bd_address -offset 0xA0000000 -range 0x1000 [get_bd_addr_segs add_0/s_axi_control/Reg]
# ブロックデザインを保存しVivado合成を行いBitstreamを作成する
save_bd_design
validate_bd_design
generate_target all [get_files *.bd]
make_wrapper -files [get_files *.bd] -top -import
update_compile_order -fileset sources_1
launch_runs synth_1
wait_on_run synth_1
launch_runs impl_1 -to_step write_bitstream
wait_on_run impl_1
open_run impl_1
write_bitstream -force add.bit
exit
Bitstream合成の実行
vivado -mode batch -source bit.tcl
ホストプログラムのクロスコンパイル
ホストプログラムはBitstreamをロードし、MMIOを直接操作してAXI-Liteを用いて通信を行い、FPGA上でBitstreamを実行します。
host.cpp
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fcntl.h>
#include <fstream>
#include <string>
#include <sys/mman.h>
#include <unistd.h>
int main(int argc, char **argv) {
if (argc != 4) return 1;
// Bitstreamを一時ファイルへコピーする
const char *tmp = "/lib/firmware/add.bit";
std::filesystem::copy_file(argv[1], tmp, std::filesystem::copy_options::overwrite_existing);
std::ofstream("/sys/class/fpga_manager/fpga0/flags") << "0";
// FPGA Managerに一時ファイル名を渡す
std::ofstream("/sys/class/fpga_manager/fpga0/firmware") << "add.bit";
std::string s;
// FPGA Managerが動作状態になるまで無限に待つ
while (s != "operating") {
std::ifstream state("/sys/class/fpga_manager/fpga0/state");
std::getline(state, s);
usleep(50000);
}
unlink(tmp);
// HLSのAXI-Liteレジスタを直接操作する
const int mem = open("/dev/mem", O_RDWR | O_SYNC);
auto *r = static_cast<volatile std::uint32_t *>(mmap(nullptr, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED, mem, 0xA0000000));
const int a = std::atoi(argv[2]), b = std::atoi(argv[3]);
r[0x18 / 4] = a;
__sync_synchronize();
r[0x20 / 4] = b;
__sync_synchronize();
r[0x00 / 4] = 1;
__sync_synchronize();
// HLSカーネルのap_doneが立つまで無限に待つ
while ((r[0x00 / 4] & 2U) == 0) usleep(10);
const int result = static_cast<std::int32_t>(r[0x10 / 4]);
munmap(const_cast<std::uint32_t *>(r), 0x1000);
close(mem);
std::printf("%d\n", result);
return 0;
}
コンパイルの実行
Vitis付属のaarch64クロスコンパイラを使います。バージョンやインストール先が異なる場合はパスを読み替えてください。この手順でhostバイナリを作成します。
CXX=/opt/amd/Vitis/2024.2/gnu/aarch64/lin/aarch64-linux/bin/aarch64-linux-gnu-g++
SYSROOT=/opt/amd/Vitis/2024.2/gnu/aarch64/lin/aarch64-xilinx-linux
"$CXX" --sysroot="$SYSROOT" -std=c++17 -O2 -Wall -Wextra -o host host.cpp
FPGAへ転送して実行
FPGAにscpコマンドなどでadd.bitとhostを転送して実際に実行します。
sudo ./host add.bit 12 30
実行結果はresultだけが1行で出力されます。今回の入力では次のようになります。
42
まとめ
本記事では、Vivadoで生成した生のBitstreamをFPGA Managerへロードし、/dev/memからAXI-Liteを操作する方法を説明しました。構成が単純である一方、FPGA Managerと/dev/memを扱うため、原則としてroot権限が必要です。より簡単にPythonから扱いたい場合はPYNQ、root権限を避けてアクセラレータを扱いたい場合はVitis + XRTを検討してください。