iwkzm氏のu-dma-bufを試してみた際のプロジェクト一式です。 Zynqを活用するうえで非常に有用なソフトウェアですので同じことを試そうという方のご参考になれば幸いです。
環境は下記の通りです。
Debian GNU/Linux (v2021.1版) ブートイメージ 環境にて試しております。
image : https://github.com/ikwzm/ZynqMP-FPGA-Linux/tree/v2021.1.1
Description : Debian GNU/Linux 11
kernel : 5.10.0-xlnx-v2021.1-zynqmp-fpga
PC側の合成環境には Vivado 2021.2 を利用しております。
bootgen を使うのでインストールしておきます。
git clone https://github.com/Xilinx/bootgen
cd bootgen/
make
sudo cp bootgen /usr/local/bin/
他にも make や dtc など使うので、不足があれば随時 sudu apt install してください。
git clone https://github.com/ryuz/jelly
で取得できます。
/projects/ultra96v2/ultra96v2_udmabuf_sample/
以下が今回のプロジェクトです。
PS用のbitstreamは PC(WindowsやLinuxなど)で Vivado を使って行います。
Vivado のプロジェクトは
/projects/ultra96v2/ultra96v2_udmabuf_sample/syn/vivado2021.2/ultra96v2_udmabuf_sample.xpr
にありますので Vivado で開いてください。
最初に BlockDesign を tcl から再構成する必要がります。
Vivado メニューの「Tools」→「Run Tcl Script」で、プロジェクトと同じディレクトリにある update_design.tcl を実行すると再構築を行うようにしています。
うまくいかない場合は、既に登録されている i_design_1 を手動で削除してから、design_1.tcl を実行しても同じことができるはずです。
design_1 が生成されたら「Flow」→「Run Implementation」で合成を行います。正常に合成できれば ultra96v2_udmabuf_sample.bit が出来上がります。
このファイルを projects/ultra96v2/ultra96v2_udmabuf_sample/app にコピーしておいてください。
なお、本PLは用の bitstream は
- テスト用簡易DMA0 0x00_A000_0000 - 0x00_A000_07FF
- テスト用簡易DMA1 0x00_A000_0800 - 0x00_A000_0FFF
- LED制御レジスタ 0x00_A000_8000 - 0x00_A000_87FF
というメモリマップになるようにサンプル回路を作っております。
Ultra96V2側でのPSソフトのビルドです。 projects/ultra96v2/ultra96v2_udmabuf_sample/app を Ultra96 のどこか適当な箇所にコピーします。 Ultra96V2側の作業は Debian のブートイメージで起動したあと、常に起動したまま行うことが可能で、運用したままPLとソフトをアップデートすることも可能なのがこのブートイメージの素晴らしいところです。
Ultra96V2 の debian でも git は動きますので、こちらでも clone する手があります。 (なお、この app ディレクトリ以下は VS code Remote Development を使ってセルフコンパイル開発してそのままpushしています。)
sudoできるユーザーで app ディレクトリに移動してください。
make run
とすればひとまず動くように作っております。 途中、sudo コマンドを使っているのでパスワードを聞かれると思いますが入力ください。 DeviceTree overlay や uio へのアクセスの為にルート権限が必要なためです。
今回は Device Tree overlay によって
- PS部がPLに供給する fabric clock の設定
- PS部とPL部を繋ぐAXIバスのバス幅などの設定
- bitfile のダウンロード
- レジスタアクセスの為の uio の割り当て
- メモリ領域割り当ての為の udmabuf の割り当て
のなどの機能を担っています。
ultra96v2_udmabuf_sample.dts が Device Tree overlay のソースファイルとなります。
順にみていきたいと思います。 なお、dtsファイルのコンパイルは、実行環境で行うことが必要なようです(内部で既存のDevice Treeのシンボルを参照する為)。
fragment@0 {
target = <&fpga_full>;
overlay0: __overlay__ {
#address-cells = <2>;
#size-cells = <2>;
firmware-name = "ultra96v2_udmabuf_sample.bit.bin";
};
};
上のように指定します。この時、ultra96v2_udmabuf_sample.bit.bin は bitstream から bootgen で生成されたファイルであり、/lib/firmware に置かれている必要があります。
bootgen の使い方としては、下記のような ultra96v2_udmabuf_sample.bif に対して
all:
{
ultra96v2_udmabuf_sample.bit
}
bootgenを用いて
bootgen -image ultra96v2_udmabuf_sample.bif -arch zynqmp -process_bitstream bin
と実行することによって得られます。 上書きを許可する場合にはさらに -w を付けます。
fragment@0 {
target = <&fpga_full>;
overlay0: __overlay__ {
#address-cells = <2>;
#size-cells = <2>;
firmware-name = "ultra96v2_udmabuf_sample.bit.bin";
};
};
fragment@1 {
target = <&amba>;
overlay1: __overlay__ {
afi0: afi0 {
compatible = "xlnx,afi-fpga";
config-afi = <0 0>, // S_AXI_HPC0_FPD(read) : 0:128bit, 1:64bit, 2:32bit
<1 0>, // S_AXI_HPC0_FPD(write) : 0:128bit, 1:64bit, 2:32bit
<2 0>, // S_AXI_HPC1_FPD(read) : 0:128bit, 1:64bit, 2:32bit
<3 0>, // S_AXI_HPC1_FPD(write) : 0:128bit, 1:64bit, 2:32bit
<4 0>, // S_AXI_HP0_FPD(read) : 0:128bit, 1:64bit, 2:32bit
<5 0>, // S_AXI_HP0_FPD(write) : 0:128bit, 1:64bit, 2:32bit
<6 0>, // S_AXI_HP1_FPD(read) : 0:128bit, 1:64bit, 2:32bit
<7 0>, // S_AXI_HP1_FPD(write) : 0:128bit, 1:64bit, 2:32bit
<8 0>, // S_AXI_HP2_FPD(read) : 0:128bit, 1:64bit, 2:32bit
<9 0>, // S_AXI_HP2_FPD(write) : 0:128bit, 1:64bit, 2:32bit
<10 0>, // S_AXI_HP3_FPD(read) : 0:128bit, 1:64bit, 2:32bit
<11 0>, // S_AXI_HP3_FPD(write) : 0:128bit, 1:64bit, 2:32bit
<12 0>, // S_AXI_LPD(read) : 0:128bit, 1:64bit, 2:32bit
<13 0>, // S_AXI_LPD(write) : 0:128bit, 1:64bit, 2:32bit
<14 0x0500>,// M_AXI_HPM0_FPD[9:8], M_AXI_HPM0_FPD[11:10] : 0:32bit, 1:64bit, 2:128bit
<15 0x100>; // M_AXI_HPM0_LPD : 0x000:32bit, 0x100:64bit, 0x200:128bit
};
fclk0 {
compatible = "ikwzm,fclkcfg-0.10.a";
clocks = <&zynqmp_clk 71 &zynqmp_clk 0>;
insert-rate = "100000000";
insert-enable = <1>;
remove-rate = "1000000";
remove-enable = <0>;
};
};
の config-afi の部分が AXI バスのバス幅の設定です。 これはこちらの記事を参考にさせて頂きました。
また、clocking0 の部分がクロックで、pclk0 を 100MHz に設定しています。 これはこちらの記事を参考にさせて頂きました。
続いて uio と u-dma-buf です。
fragment@2 {
target = <&amba>;
overlay2: __overlay__ {
#address-cells = <0x2>;
#size-cells = <0x2>;
uio_pl_peri@a0000000 {
compatible = "generic-uio";
reg = <0x0 0xa0000000 0x0 0x08000000>;
interrupt-parent = <&gic>;
interrupts = <0 89 4>;
};
};
};
fragment@3 {
target = <&amba>;
overlay3: __overlay__ {
#size-cells = <0x2>;
udmabuf0 {
compatible = "ikwzm,u-dma-buf";
device-name = "udmabuf-jelly-sample";
size = <0x0 0x00400000>;
};
};
};
今回はペリフェラル領域をまとめて一個の uio に割り当てています。 開始アドレス 0xa0000000番地から サイズ 0x08000000 バイトの領域が uio_pl_peri という名前の uio として生成されます。
また udmabuf4 という名前で、0x00400000 バイトの CMA(Continuous Memory Allocator) を確保してもらうように指定しています。u-dma-buf を用いることで、連続した物理メモリアドレスを割り当ててもらうことが可能になります。
dtc -I dts -O dtb -o ultra96v2_udmabuf_sample.dtbo ultra96v2_udmabuf_sample.dts
とすることで ultra96v2_udmabuf_sample.dtbo を得ることができます。
いよいよ overlay です
初めに configfs をマウントします。
sudo mkdir -p /configfs
sudo mount -t configfs configfs /configfs
必要なものを /lib/firmware にコピーします。
sudo mkdir -p /lib/firmware
sudo cp ultra96v2_udmabuf_sample.bit.bin /lib/firmware
sudo cp ultra96v2_udmabuf_sample.dtbo /lib/firmware
次に overlay を行います。
sudo sh -c "echo 0 > /sys/class/fpga_manager/fpga0/flags"
sudo mkdir /configfs/device-tree/overlays/full
sudo sh -c "echo -n ultra96v2_udmabuf_sample.dtbo > /configfs/device-tree/overlays/full/path"
この段階で bitstream は書き込まれ、動作を開始しています。
状態を確認するには
cat /configfs/device-tree/overlays/full/status
でできるようで applied と表示されればよいようです。
役目を終えたファイルは削除してよいようです。
sudo rm /lib/firmware/ultra96v2_udmabuf_sample.dtbo
sudo rm /lib/firmware/ultra96v2_udmabuf_sample.bit.bin
ここでアプロケーションを実行します。 /dev 以下に uio や dmabuf に対応するデバイスがが追加されているはずなのでそれらを開いてアクセスすることができます。
このやり方は別の記事で紹介しております。
詳しくはmain.cppをお読みください。
うまく動けば、udmabuf領域にPLのコアからと、Cortex-A53 の双方からアクセスして、データがやり取りできることが確認できます。 また、uio にマップした RADIO_LED もソフトウェアから点滅させています。
sudo rmdir /configfs/device-tree/overlays/full
と削除すると、解除できるようです。
Rust がインストールされた環境にて
make run_rust
と実行すると Rust 版のデモが動きます。
python3 が動く環境にて
pip3 install flask
しておけば
make run_server
でサーバーが起動し、PCなどから Webブラウザで接続することで、LEDを ON/OFF できます。