KV260などの PL制御を gRPC 経由で行う
はじめに
Zynq などで FPGA 使う場合、root 権限でないと操作できないケースと言うのが多々あります。
DeviceTree Overlay であったり、Bitstream のダウンロードであったり、それらの為の /lib/firmware へのファイルコピーであったり、/dev/mem へのアクセスであったり、様々なものが思い付きます。
デバイスファイルなどに細かくアクセス権など与えていけばある程度緩和できる部分もありますが、特にテスト中や、プロトタイピングなどでは sudo で実行してしまう事も多いと思います。
一方でコードを丸ごと sudo で実行するのもやや乱暴で、seteuid() などしなければ、アプリの出力したファイルも root 権限になるなど面倒もあります。
そこで、その手の処理を別プロセスに切り出して、gRPC サーバーにしてみる実験をしております。
gRPC サーバー化である程度 root 権限が必須な操作をアプリから追い出すことが出来る可能性があります。また、gRPC の利点として
- 様々な言語から呼び出せる
- 別のPCからも呼び出せる
という点があります。
メモリマップドI/Oの操作など、どうしても C/C++ や Rust など、生ポインタの扱える言語に頼ることになりますが、gRPC を挟むことで、ポインタを備えない言語からも使えるようになりますし、KV260 に別PCから制御が可能になります。
これは Vivado などが PC でしか動かないのに対して、合成+実機テストなどを自動で流す場合などに便利になりそうな気がしています。
なお、現時点ではまだ実験的な実装で、インストールも GitHub 経由で実験している状態です。こなれてきたら crates.io や PyPI への登録も考えたいと思います。
サーバー側インストール&起動
もし Rust が未インストールならインストールしてください。
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
次に拙作の jelly-fpga-server をインストールします。
cargo install --git https://github.com/ryuz/jelly-fpga-server.git --tag v0.0.2
次に root権限でサーバーを立ち上げます。
sudo $HOME/.cargo/bin/jelly-fpga-server --external
クライアント側
今回は下記で作った KV260 で PS から LEDチカチカを行う回路を PC から動かしてみます。
プロジェクトはこちらにあります。
解説記事は下記です。
以降は、Vivado で合成した kv260_blinking_led_ps.bit がカレントディレクトリに置かれているものとします。
Python の場合
下記のようにインストール
pip install git+https://github.com/ryuz/jelly-fpga-client-python.git
以降 python コードとして、下記のように KV260 の IP アドレスを指定して接続します。
Jupyter など使うと便利かと思います。
from jelly_fpga_client import jelly_fpga_control
ftga_ctl = jelly_fpga_control.JellyFpgaControl("XX.XX.XX.XX:50051")
ftga_ctl.reset() # 念のためリセット
次に DeviceTree Overlay 用の DTS ファイルを DTB に変換してファームウェアとして書き込みます。
# DTS のソースを埋める
dts = """\
/dts-v1/; /plugin/;
/ {
fragment@0 {
target = <&fpga_full>;
overlay0: __overlay__ {
#address-cells = <2>;
#size-cells = <2>;
firmware-name = "kv260_blinking_led_ps.bit.bin";
};
};
fragment@1 {
target = <&amba>;
overlay1: __overlay__ {
clocking0: clocking0 {
#clock-cells = <0>;
assigned-clock-rates = <100000000>;
assigned-clocks = <&zynqmp_clk 71>;
clock-output-names = "fabric_clk";
clocks = <&zynqmp_clk 71>;
compatible = "xlnx,fclk";
};
};
};
};
"""
# DTBに変換して firmware としてアップロード
dtb = ftga_ctl.dts_to_dtb(dts)
ftga_ctl.upload_firmware("kv260_blinking_led_ps.dtbo", dtb)
次に bitstream ファイルをアップロードして、bin に変換します。
# bitstreamファイルをアップロード
ftga_ctl.upload_firmware_file("kv260_blinking_led_ps.bit", "./kv260_blinking_led_ps.bit")
# アップロードした bitstream ファイルを bin ファイルに変換
ftga_ctl.bitstream_to_bin("kv260_blinking_led_ps.bit", "kv260_blinking_led_ps.bit.bin", "zynqmp")
最後に DeviceTree Overaly を実施します。
この段階で PL が書き変わります。
# 既存の PL をアンロード
ftga_ctl.unload()
# DeviceTree Overaly を実施
ftga_ctl.load_dtbo("kv260_blinking_led_ps.dtbo")
/dev/mem を開いて、メモリマップドI/O に書き込みして LED を点滅させてみます。
# /dev/mem を mmap して LED0 を点滅させる
import time
accessor = ftga_ctl.open_mmap("/dev/mem", 0xa0000000, 0x1000)
for _ in range(3):
ftga_ctl.write_mem_u64(accessor, 0, 1) # LED0 ON
time.sleep(0.5)
ftga_ctl.write_mem_u64(accessor, 0, 0) # LED0 OFF
time.sleep(0.5)
# close
ftga_ctl.close(accessor)
PC から操作する事が出来ました。
最後にアップロードしたファイルを削除しておきます。
# 後始末
ftga_ctl.unload()
ftga_ctl.remove_firmware("kv260_blinking_led_ps.dtbo")
ftga_ctl.remove_firmware("kv260_blinking_led_ps.bit")
ftga_ctl.remove_firmware("kv260_blinking_led_ps.bit.bin")
Elixir の場合
例えば Livebook から操作してみます。
Mix.install(
[
{:grpc, "~> 0.9.0"},
{:protobuf, "~> 0.13.0"},
{:jelly_fpga_client, github: "ryuz/jelly-fpga-client-elixir"}
],
force: true
)
下記のように接続します。
{:ok, channel} = GRPC.Stub.connect("XX.XX.XX.XX:50051")
channel |> JellyFpgaControl.reset() # 念のためリセット
としておいて、まず DTS を DTB にしてアップロードします。
# DTS のソース
dts = """
/dts-v1/; /plugin/;
/ {
fragment@0 {
target = <&fpga_full>;
overlay0: __overlay__ {
#address-cells = <2>;
#size-cells = <2>;
firmware-name = "kv260_blinking_led_ps.bit.bin";
};
};
fragment@1 {
target = <&amba>;
overlay1: __overlay__ {
clocking0: clocking0 {
#clock-cells = <0>;
assigned-clock-rates = <100000000>;
assigned-clocks = <&zynqmp_clk 71>;
clock-output-names = "fabric_clk";
clocks = <&zynqmp_clk 71>;
compatible = "xlnx,fclk";
};
};
};
};
"""
# DTBに変換して firmware としてアップロード
{:ok, dtb} = channel |> JellyFpgaControl.dts_to_dtb(dts)
channel |> JellyFpgaControl.upload_firmware("kv260_blinking_led_ps.dtbo", dtb)
次に bitstream をアップロードして bin に変換します。
# bitstreamファイルをアップロード
channel |> JellyFpgaControl.upload_firmware_file("kv260_blinking_led_ps.bit", "./kv260_blinking_led_ps.bit")
# アップロードした bitstream ファイルを bin ファイルに変換
channel |> JellyFpgaControl.bitstream_to_bin("kv260_blinking_led_ps.bit", "kv260_blinking_led_ps.bit.bin", "zynqmp")
DeviceTree Overlay を実施して PL にロードします。
# 既存の PL をアンロード
channel |> JellyFpgaControl.unload()
# DeviceTree Overaly を実施
channel |> JellyFpgaControl.load_dtbo("kv260_blinking_led_ps.dtbo")
/dev/mem を mmap して LED を点滅させます。
# /dev/mem を mmap して LED0 を点滅させる
{:ok, accessor} = channel |> JellyFpgaControl.open_mmap("/dev/mem", 0xa0000000, 0x1000)
Enum.each(1..3, fn _ ->
# LED0 ON
channel |> JellyFpgaControl.write_mem_u64(accessor, 0, 1) # オフセット0番地に1を書き込む
Process.sleep(500)
# LED0 OFF
channel |> JellyFpgaControl.write_mem_u64(accessor, 0, 0) # オフセット0番地に0を書き込む
Process.sleep(500)
end)
一応、後始末しておきます。
# 後始末
channel |> JellyFpgaControl.unload()
channel |> JellyFpgaControl.remove_firmware("kv260_blinking_led_ps.dtbo")
channel |> JellyFpgaControl.remove_firmware("kv260_blinking_led_ps.bit")
channel |> JellyFpgaControl.remove_firmware("kv260_blinking_led_ps.bit.bin")
おわりに
今のところまだ実験段階ですが、どのようなものを作ろうとしているか伝われば幸いです。
なお、まだ本当に必要な箇所しか実装しておらず、README なども碌に記載できていないので、おいおい充実させていければと思います。
まずは、こんなもの作ってますという雰囲気が伝わればと思います。
なお、あくまでローカル環境で、テストやプロトタイピングを行う事を目的としていますので、セキュリティー的なところは一切考慮していません。
そもそも /dev/mem などで存在しないアドレスをアクセスしてシステムごとフリーズさせるような事が簡単に出来てしまう領域での利便性追求なので、悪しからずご了承ください。
おまけ
このツールの開発途中での副産物はこちらです。
sudo などの uid の切り替え便利ツールと、それをつかった FPGA 制御ユーティリティーです。
Discussion