はじめに
iwkzm氏のudmabufを試してみたので、自分用備忘録です。
なお、udmabuf のドライバ名は udmabuf → u-dma-buf と名称変更された経緯があり、古い環境を利用する方は読み替えて頂ければと思います。
udmabuf を試してみる (ZYBO-Z7編)とよく似た内容となっていますが、Device Tree Overlay のやり方などで Zynq-7000 と ZynqMP とで細かい部分で差異があるあるのでご注意ください。
事前準備
環境
今回の環境は下記の通り。
- Ultra96V2
- iwkzm氏の Debianブートイメージ 2019.2版
- Vivado 2019.2
Debianイメージは一度起動SDを作ってしまえば Vivado だけでもいろいろできるのが素敵です。
Ultra96v2側の準備
bootgen を使うのでインストールしておきます。
git clone https://github.com/Xilinx/bootgen
cd bootgen/
make
sudo cp bootgen /usr/local/bin/
他にも make や dtc など使うので、不足があれば随時 sudo apt install してください。
ソースコードの所在
今回は拙作の Jelly の1プロジェクトとして追加しています。
git clone https://github.com/ryuz/jelly -b v2.0.2
で取得でき、/projects/ultra96v2/ultra96v2_udmabuf_sample/ 以下が今回のプロジェクトです。
PL用bitstreamの作成
PS用のbitstreamは PC(WindowsやLinuxなど)で Vivado を使って行います。
Vivado のプロジェクトは
/projects/ultra96v2_udmabuf_sample/syn/vivado2019.2
のにあります。
ultra96v2_udmabuf_sample.xpr が vivado2019.2 のプロジェクトファイルで、 Block Design は 同じディレクトリにある design_1.tcl に export しております。
(design_1.tcl を実行すれば Block Design が作れるはずですが、一度今あるものをプロジェクトから消してから tcl 実行が必要かもしれません。)
合成すると、ultra96v2_udmabuf_sample.bit が出来上がります。
このファイルを projects/ultra96v2_udmabuf_sample/app にコピーしておいてください。
なお、回路の方は本記事の本質ではないので飛ばしますが、重要な点としてメモリマップのみ。
PS部からは LPDDR4-SDRAM にアクセスするための 128bit幅 のAXI4のバススレーブと、0x00_A000_0000 番地からアサインした、ペリフェラルアクセス用の64bit幅のAXI4Liteのバスマスターを出しております。
あとは ultra96v2_udmabuf_sample.v の方で WISHBONE バスに変換した後に下記のようになるようにアドレスを割り当てています。
- テスト用簡易DMA0 0x00_A000_0000 - 0x00_A000_07FF
- テスト用簡易DMA1 0x00_A000_0800 - 0x00_A000_0FFF
- LED制御レジスタ 0x00_A000_8000 - 0x00_A000_87FF
ソフト側の作成と実行
Ultra96V2側のソフトの開発です。
projects/ultra96v2_udmabuf_sample/app を Ultra96 のどこか適当な箇所にコピーします。
Ultra96V2側の作業は Debian のブートイメージで起動したあと、常に起動したまま行うことが可能で、運用したままPLとソフトをアップデートすることも可能なのがこのブートイメージの素晴らしいところです。
Ultra96V2 の debian でも git は動きますので、こちらでも clone する手があります。
(なお、この app ディレクトリ以下は VS code Remote Development を使ってセルフコンパイル開発してそのままpushしています。)
とりあえず動かしてみる
sudoできるユーザーで app ディレクトリに移動してください。
make run
とすればひとまず動くように作っております。
途中、sudo コマンドを使っているのでパスワードを聞かれると思いますが入力ください。
Device Tree overlay
ここから本記事のメインの部分です。
今回は Device Tree overlay によって
- PS部がPLに供給する fabric clock の設定
- PS部とPL部を繋ぐAXIバスのバス幅などの設定
- bitfile のダウンロード
- レジスタアクセスの為の uio の割り当て
- メモリ領域割り当ての為の udmabuf の割り当て
のなどの機能を担っています。
dts ファイル
ここで ultra96v2_udmabuf_sample.dtsファイルの作成が重要となります。
順にみていきたいと思います。
なお、dtsファイルのコンパイルは、実行環境で行うことが必要なようです(内部で既存のDevice Treeのシンボルを参照する為)。
bitstream 指定
fragment@0 {
target = <&fpga_full>;
__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
と実行することによって得られます。
クロックと AXIのバス幅
fragment@1 {
target-path = "/amba_pl@0";
#address-cells = <2>;
#size-cells = <2>;
__overlay__ {
#address-cells = <2>;
#size-cells = <2>;
afi0 {
compatible = "xlnx,afi-fpga";
config-afi = <0 0>,
<1 0>,
<2 0>,
<3 0>,
<14 0x0500>;
/* 0: S_AXI_HPC0_FPD(read) : 0:128bit, 1:64bit, 2:32bit */
/* 1: S_AXI_HPC0_FPD(write) : 0:128bit, 1:64bit, 2:32bit */
/* 2: S_AXI_HPC1_FPD(read) : 0:128bit, 1:64bit, 2:32bit */
/* 3: S_AXI_HPC1_FPD(write) : 0:128bit, 1:64bit, 2:32bit */
/* 14: M_AXI_HPM0_FPD[9:8], M_AXI_HPM0_FPD[11:10]
: 0:32bit, 1:64bit, 2:128bit */
};
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";
};
};
の config-afi の部分が AXI バスのバス幅の設定です。
これはこちらの記事を参考にさせて頂きました。
また、clocking0 の部分がクロックで、pclk0 を 100MHz に設定しています。
これはこちらの記事を参考にさせて頂きました。
uioとudmabuf
続いて uio と udmabuf です。
fragment@2 {
target-path = "/amba";
__overlay__ {
#address-cells = <0x1>;
#size-cells = <0x1>;
uio_pl_peri {
compatible = "generic-uio";
reg = <0xa0000000 0x08000000>;
};
udmabuf4 {
compatible = "ikwzm,udmabuf-0.10.a";
minor-number = <4>;
size = <0x00400000>;
};
};
};
今回はペリフェラル領域をまとめて一個の uio に割り当てています。
開始アドレス 0xa0000000番地から サイズ 0x08000000 バイトの領域が uio_pl_peri という名前の uio として生成されます。
また udmabuf4 という名前で、0x00400000 バイトの CMA(Continuous Memory Allocator) を確保してもらうように指定しています。udmabuf を用いることで、連続した物理メモリアドレスを割り当ててもらうことが可能になります。
dtcでのコンパイル
dtc -I dts -O dtb -o ultra96v2_udmabuf_sample.dtbo ultra96v2_udmabuf_sample.dts
とすることで ultra96v2_udmabuf_sample.dtbo を得ることができます。
Overlay
いよいよ overlay です
configfs の mount
初めに 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
次に 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 もソフトウェアから点滅させています。
Device Tree Overlay の解除
すこし自信のないところなのですが
sudo rmdir /configfs/device-tree/overlays/full
と削除すると、解除できるようです。
おしらせ
2020/10/30 にて Qrunch がサービス終了したので移転してきた記事となります。
一時退避場所:https://github.com/ryuz/qrunch_blog