Ultra96V2向けVitis AI(2019.2)の組み立て方。

# Ultra96V2向け　Vitis AIの組み立て。
[Ultra96V2](https://www.avnet.com/shop/japan/products/avnet-engineering-services/aes-ultra96-v2-g-3074457345642817668)は、Avnet社から提供されている、FPGAボードです。安価なボードなのですが、AI活用にも注目されています。そこで、Vitis AIコアを実装して、実際にDeepLeaningを作ってみます。

##開発環境について
ザイリンクスの開発環境が必要になります。
大きく、分けて２つのツールが必要になります。ひとつは、FPGAの下回りを開発するためのツール
もう一つはAIを開発するためのツールです。Vitis AIでは、両方必要ですので、
####Vitis 　Petalinux
ソフトウェア技術者向けにターゲットした開発環境です。これで全体的なターゲットができます。
####Vitis AI
AIライブラリー、コンパイラー　フレームワークがセットになっています。Github公開になっています。

####開発環境のインストール
開発には、Linuxが必要です。今回は、Ubuntu18.04を使いました。
Vitis、Vitis AI、Petalinuxの開発環境のインストールは、下記ホームページにまとめています。
これに準じて、インストールをします。
https://qiita.com/basaro_k/items/86811ed78397d2a3b4b1


##Vitis AIプラットフォームの作成方法。
Vitis、及び、Petalinuxが必要となります。動作する環境で行ってください。
手順的には Vivado（Vitisインストール時に含まれています）でVitisAIを作って、Petalinuxで、Linuxを準備して、その後、DPU(AI)IPコアを組み込むという形になります。
本来なら、一つ一つ手順があったほうが、いいのですが、相当な量になりますので、AIコアを組み込むまでをスクリプト化しました。
次のスクリプトを実行すれば、Vivado、Petalinux　Vitisと自動実行して、Ultra92V2向けファイルが自動生成します。コピー後、
source ultra96x.sh 
で、実行できます。

なお、私の開発環境では、3時間ほどかかっていました。（CPUが多かったら、もっと速いと思います）

```sh:ultra96x.sh
#!/bin/bash
mkdir ultra96v2_oob
cd ultra96v2_oob

git clone -b 2019.2 https://github.com/Xilinx/Vitis_Embedded_Platform_Source.git
cp -rf ./Vitis_Embedded_Platform_Source/Xilinx_Official_Platforms/zcu102_base/* .
mkdir hdl
mkdir petalinux_oob
mkdir bdf
mkdir download
cd download
#download Git
git clone -b 2019.1 https://github.com/Avnet/petalinux.git
git clone -b 2019.1 https://github.com/Avnet/hdl.git
#make work directory
mkdir ../hdl/Boards
mkdir ../hdl/IP
mkdir ../hdl/Projects
mkdir ../hdl/Scripts
mkdir ../hdl/Scripts/ProjectScripts
mkdir ../petalinux_oob/scripts
mkdir ../petalinux_oob/configs
mkdir ../petalinux_oob/configs/device-tree
mkdir ../petalinux_oob/configs/kernel
mkdir ../petalinux_oob/configs/meta-user
mkdir ../petalinux_oob/configs/project
mkdir ../petalinux_oob/configs/rootfs
mkdir ../petalinux_oob/configs/u-boot

#copy files
cp -rf hdl/Boards/ULTRA96V2 ../hdl/Boards
cp -rf hdl/IP/PWM_w_Int ../hdl/IP
cp -rf hdl/Projects/ultra96v2_oob ../hdl/Projects
cp hdl/Scripts/make_ultra96v2_oob.tcl ../hdl/Scripts
cp hdl/Scripts/make.tcl ../hdl/Scripts
cp hdl/Scripts/bin_helper.tcl ../hdl/Scripts
cp hdl/Scripts/ProjectScripts/ultra96v2_oob.tcl ../hdl/Scripts/ProjectScripts
cp hdl/Scripts/tag.tcl ../hdl/Scripts

cp petalinux/scripts/make_ultra96v2_oob_bsp.sh ../petalinux_oob/scripts
cp petalinux/configs/device-tree/system-user.dtsi.ULTRA96V2 ../petalinux_oob/configs/device-tree
cp petalinux/configs/kernel/user.cfg.ULTRA96V2 ../petalinux_oob/configs/kernel
cp -rf petalinux/configs/meta-user/ultra96v2_oob ../petalinux_oob/configs/meta-user
cp petalinux/configs/project/config.ultra96v2_oob.patch ../petalinux_oob/configs/project
cp petalinux/configs/project/config.sd_ext4_boot.patch ../petalinux_oob/configs/project
cp petalinux/configs/rootfs/config.ultra96v2_oob ../petalinux_oob/configs/rootfs
cp petalinux/configs/u-boot/platform-top.h.ultra96v2_sd_boot ../petalinux_oob/configs/u-boot
cp petalinux/configs/u-boot/bsp.cfg ../petalinux_oob/configs/u-boot

cd ..

#change vivado 
cd vivado
mv zcu102_base_xsa.tcl ultra96v2_oob_xsa.tcl.bak

cat ultra96v2_oob_xsa.tcl.bak | sed -e 's/zcu102_base/ultra96v2_oob/g' -e 's/zcu102/ultra96/g' > ultra96v2_oob_xsa.tcl1.bak
cat ultra96v2_oob_xsa.tcl1.bak | sed -e '43i   source ../hdl/Boards/ULTRA96V2/ultra96v2_oob.tcl -notrace' | sed -e '44i avnet_create_project ultra96v2_oob ultra96v2_oob Project' | sed -e '45i set_property board_part em.avnet.com:ultra96v2:part0:1.0 [current_project]' | sed -e '47,51d' > ultra96v2_oob_xsa.tcl2.bak
cat ultra96v2_oob_xsa.tcl2.bak | sed -e '47i set_property ip_repo_paths  ../hdl/IP [current_fileset]' | sed -e '48i update_ip_catalog' | sed -e '388,1890d' | sed -e '388i  avnet_add_ps_preset ultra96v2_oob ultra96v2_oob ultra96v2_oob' |  sed -e '389i set_property name ps_e [get_bd_cells zynq_ultra_ps_e_0]' |  sed -e '431i avnet_add_user_io_preset ultra96v2_oob ultra96v2_oob ultra96v2_oob' > ultra96v2_oob_xsa.tcl3.bak
cat ultra96v2_oob_xsa.tcl3.bak | sed -e '439s/set i 1/set i 10/g' | sed -e '469i add_files -fileset constrs_1 -norecurse ../hdl/Projects/ultra96v2_oob/ultra96v2_oob.xdc'| sed -e '470i import_files -fileset constrs_1 ../hdl/Projects/ultra96v2_oob/ultra96v2_oob.xdc' > ultra96v2_oob_xsa.tcl4.bak
cat ultra96v2_oob_xsa.tcl4.bak | sed -e 's/-jobs 16/-jobs 6/g' > ultra96v2_oob_xsa.tcl
rm *.bak

#Chang directory for script
cd ..
cd hdl/Boards/ULTRA96V2
mv ultra96v2_oob.tcl ultra96v2_oob.tcl.bak
cat ultra96v2_oob.tcl.bak | sed -e '68,71d' | sed -e '442,444d' | sed -e '479i startgroup' | sed  -e '480i set_property -dict [list CONFIG.PSU__USE__M_AXI_GP1 {0}] [get_bd_cells zynq_ultra_ps_e_0]' | sed   -e '481i set_property -dict [list CONFIG.PSU__USE__M_AXI_GP2 {1}] [get_bd_cells zynq_ultra_ps_e_0]' | sed -e '482i set_property -dict [list CONFIG.PSU__USE__S_AXI_GP5 {1}] [get_bd_cells zynq_ultra_ps_e_0]' | sed -e  '483i endgroup' > ultra96v2_oob.tcl1.bak
cat ultra96v2_oob.tcl1.bak | sed -e  '462,464d' | sed -e '61,77d' | sed -e '61i delete_bd_objs [get_bd_nets axi_intc_0_irq]' | sed -e '62i startgroup' | sed -e '63i create_bd_cell -type ip -vlnv xilinx.com:ip:xlconcat:2.1 xlconcat_0' | sed -e '64i endgroup' | sed -e '65i set_property -dict [list CONFIG.NUM_PORTS {1}] [get_bd_cells xlconcat_0]' | sed -e '66i connect_bd_net [get_bd_pins xlconcat_0/dout] [get_bd_pins ps_e/pl_ps_irq0]' | sed -e '67i connect_bd_net [get_bd_pins axi_intc_0/irq] [get_bd_pins xlconcat_0/In0]' > ultra96v2_oob.tcl2.bak
cat ultra96v2_oob.tcl2.bak | sed -e  's/zynq_ultra_ps_e_0\/pl_clk0 (100 MHz)/clk_wiz_0\/clk_out3 (75 MHz)/g' -e  's/zynq_ultra_ps_e_0\/M_AXI_HPM0_FPD/ps_e\/M_AXI_HPM0_LPD/g' -e 's/ps8_0_axi_periph/interconnect_axilite/g' > ultra96v2_oob.tcl3.bak
cat ultra96v2_oob.tcl3.bak | sed -e '243,249d' | sed -e '244d' | sed -e 's/CONFIG.NUM_PORTS {5}/CONFIG.NUM_PORTS {6}/g' -e 's/xlconcat_0\/In4/xlconcat_0\/In5/g' -e 's/xlconcat_0\/In3/xlconcat_0\/In4/g' -e 's/xlconcat_0\/In2/xlconcat_0\/In3/g' -e 's/xlconcat_0\/In1/xlconcat_0\/In2/g' -e '240,250s/xlconcat_0\/In0/xlconcat_0\/In1/g' | sed -e '243,424s/zynq_ultra_ps_e_0/ps_e/g' > ultra96v2_oob.tcl4.bak
mv ultra96v2_oob.tcl4.bak ultra96v2_oob.tcl
rm *.bak

cd ../../../..

#cp -rf vivado ultra96v2_oob
cd ultra96v2_oob/vivado
make PLATFORM=ultra96v2_oob
#cp ../../ultra96v2_oob.xsa .
cd ../../

## copy vivado 
cp ultra96v2_oob/vivado/ultra96v2_oob.xsa ultra96v2_oob/petalinux

cd ultra96v2_oob/petalinux
make refresh_hw XSA_DIR=../vivado

cd project-spec/configs
mv config config.bak
mv rootfs_config rootfs_config.bak

cat config.bak | sed -e 's/PSU_UART_0_/PSU_UART_1_/g' -e 's/# CONFIG_SUBSYSTEM_SERIAL_PSU_UART_1_SELECT is not set/# CONFIG_SUBSYSTEM_SERIAL_PSU_UART_0_SELECT is not set/g' -e 's/psu_uart_0/psu_uart_1/g' -e 's/cadence/cadence1/g' > config1.bak
cat config1.bak | sed -e 's/zcu102-rev1.0/avnet-ultra96-rev1/g' -e 's/xilinx_zynqmp_zcu102_rev1_0_defconfig/avnet_ultra96_rev1_defconfig/g' -e 's/xilinx-zcu102/ultra96v2-oob/g' -e 's/zcu102-zynqmp/ultra96-zynqmp/g' -e 's/CONFIG_SUBSYSTEM_PRIMARY_SD_PSU_SD_1_SELECT=y/CONFIG_SUBSYSTEM_PRIMARY_SD_PSU_SD_0_SELECT=y/g' -e 's/# CONFIG_SUBSYSTEM_PRIMARY_SD_PSU_SD_0_SELECT is not set/# CONFIG_SUBSYSTEM_PRIMARY_SD_PSU_SD_1_SELECT is not set/g' > config2.bak
cat config2.bak | sed -e 's/CONFIG_SUBSYSTEM_ROOTFS_INITRAMFS=y/# CONFIG_SUBSYSTEM_ROOTFS_INITRAMFS is not set/g' -e 's/# CONFIG_SUBSYSTEM_ROOTFS_EXT is not set/CONFIG_SUBSYSTEM_ROOTFS_EXT=y/g' -e 's/115200 clk_ignore_unused/115200 clk_ignore_unused root=\/dev\/mmcblk0p2 rw rootwait/g' | sed -e '182i CONFIG_SUBSYSTEM_SDROOT_DEV="/dev/mmcblk0p2"' > config3.bak
cat rootfs_config.bak | sed -e 's/# CONFIG_bc is not set/CONFIG_bc=y/g' -e 's/# CONFIG_i2c-tools is not set/CONFIG_i2c-tools=y/g' -e 's/# CONFIG_usbutils is not set/CONFIG_usbutils=y/g' -e 's/# CONFIG_ethtool is not set/CONFIG_ethtool=y/g' -e  's/# CONFIG_git is not set/CONFIG_git=y/g' > rootfs_config1.bak
cat rootfs_config1.bak | sed -e 's/# CONFIG_coreutils is not set/CONFIG_coreutils=y/g' -e 's/# CONFIG_openamp-fw-echo-testd is not set/CONFIG_openamp-fw-echo-testd=y/g' -e 's/# CONFIG_openamp-fw-mat-muld is not set/CONFIG_openamp-fw-mat-muld=y/g' -e 's/# CONFIG_openamp-fw-rpc-demo is not set/CONFIG_openamp-fw-rpc-demo=y/g' > rootfs_config2.bak
cat rootfs_config2.bak | sed -e 's/# CONFIG_packagegroup-petalinux is not set/CONFIG_packagegroup-petalinux=y/g' -e 's/# CONFIG_packagegroup-petalinux-benchmarks is not set/CONFIG_packagegroup-petalinux-benchmarks=y/g' -e 's/# CONFIG_packagegroup-petalinux-matchbox is not set/CONFIG_packagegroup-petalinux-matchbox=y/g' > rootfs_config3.bak
cat rootfs_config3.bak | sed -e 's/# CONFIG_packagegroup-petalinux-openamp is not set/CONFIG_packagegroup-petalinux-openamp=y/g' -e 's/# CONFIG_packagegroup-petalinux-self-hosted is not set/CONFIG_packagegroup-petalinux-self-hosted=y/g' -e 's/# CONFIG_packagegroup-petalinux-utils is not set/CONFIG_packagegroup-petalinux-utils=y/g' -e 's/# CONFIG_packagegroup-petalinux-v4lutils is not set/CONFIG_packagegroup-petalinux-v4lutils=y/g' -e 's/# CONFIG_packagegroup-petalinux-x11 is not set/CONFIG_packagegroup-petalinux-x11=y/g' -e 's/# CONFIG_imagefeature-package-management is not set/CONFIG_imagefeature-package-management=y/g' > rootfs_config4.bak
cat rootfs_config4.bak | sed -e '$a CONFIG_wilc=y' -e '$a CONFIG_libftdi=y' -e '$a CONFIG_bonniePLUSPLUS=y' -e '$a CONFIG_cmake=y' -e '$a CONFIG_iperf3=y' -e '$a CONFIG_iw=y' -e '$a CONFIG_lmsensors-sensorsdetect=y' -e '$a CONFIG_nano=y' -e '$a CONFIG_packagegroup-base-extended=y' -e '$a CONFIG_packagegroup-petalinux-96boards-sensors=y' -e '$a CONFIG_packagegroup-petalinux-ultra96-webapp=y' -e '$a CONFIG_python-pyserial=y' -e '$a CONFIG_python3-pip=y' -e '$a CONFIG_ultra96-ap-setup=y' -e '$a CONFIG_ultra96-misc=y' -e '$a CONFIG_wilc-firmware-wilc3000=y' -e '$a CONFIG_ultra96-radio-leds=y' -e '$a CONFIG_ultra96-wpa=y' -e '$a CONFIG_sds-lib=y' -e '$a CONFIG_wilc3000-fw=y'  -e '$a CONFIG_ultra96-startup-pages=y'  > rootfs_config5.bak
cat rootfs_config5.bak | sed -e 's/# CONFIG_packagegroup-petalinux-weston is not set/CONFIG_packagegroup-petalinux-weston=y/g' > rootfs_config6.bak
mv config3.bak config
mv rootfs_config6.bak rootfs_config
rm *.bak

cd ..
#meta-user/recipes-kernel/linux/linux-xlnx/user_2019-10-31-20-33-00.cfg

cd meta-user/recipes-bsp/device-tree/
cp ../../../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-bsp/device-tree/device-tree.bbappend .
cd files
cp -rf ../../../../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-bsp/device-tree/files/multi-arch .
cat system-user.dtsi | sed -e '1,25d' > system-user.dtsi.bak
cat ../../../../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-bsp/device-tree/files/system-user.dtsi system-user.dtsi.bak > system-user.dtsi
cat openamp.dtsi | sed -e '1,42d' > openamp.dtsi.bak
cat ../../../../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-bsp/device-tree/files/openamp.dtsi | sed -e '56,57d' > openamp.dtsi1.bak
cat openamp.dtsi1.bak openamp.dtsi.bak > openamp.dtsi
cp ../../../../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-bsp/device-tree/files/xen.dtsi .
cp ../../../../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-bsp/device-tree/files/zynqmp-qemu-arm.dts .
rm *.bak

cd ../../..

#meta-user copy
cp -rf ../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-core .
cp -rf ../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-graphics .
cp -rf ../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-modules .
mkdir recipes-utils 
cd recipes-utils 
cp -rf ../../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-utils/ultra96-radio-leds .
cp -rf ../../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-utils/ultra96-wpa .
cp -rf ../../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-utils/ultra96-ap-setup .
cd ..
cp -rf ../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-apps/sds-lib recipes-apps
cp -rf ../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-bsp/u-boot recipes-bsp
cp -rf ../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-bsp/pmu-firmware recipes-bsp
cp -rf ../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-bsp/ultra96-misc recipes-bsp
cp -rf ../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-bsp/wilc3000-fw recipes-bsp
cp -rf ../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-kernel .

cd conf
mv petalinuxbsp.conf petalinuxbsp.conf.bak
mv user-rootfsconfig user-rootfsconfig.bak 
mv ../../../../petalinux_oob/configs/meta-user/ultra96v2_oob/recipes-core/images/petalinux-image-full.bbappend petalinux-image-full.bbappend.bak
cat petalinuxbsp.conf.bak | sed -e '$a MACHINE_FEATURES_remove_ultra96-zynqmp = "mipi"' -e '$a DISTRO_FEATURES_append = " bluez5 dbus"' -e '$a PREFERRED_VERSION_wilc-firmware = "15.2" ' > petalinuxbsp.conf
cat petalinux-image-full.bbappend.bak | sed -e '3,4d' | sed -e '/#/d' | sed -e "/^[<space><tab>]*$/d" | sed -e 's/IMAGE_INSTALL_append = " /CONFIG_/g' -e 's/"//g' > user-rootfsconfig1.bak
cat user-rootfsconfig.bak user-rootfsconfig1.bak | sed  -e '$a CONFIG_ultra96-startup-pages'  > user-rootfsconfig
rm *.bak
cd ..

cd recipes-kernel/linux

mv linux-xlnx_%.bbappend linux-xlnx_%.bbappend.bak
cat  linux-xlnx_%.bbappend.bak | sed -e '3iSRC_URI += "file://user.cfg"' > linux-xlnx_%.bbappend
rm *.bak

cd ../..

cd recipes-kernel/linux/linux-xlnx
mv user_2019-10-31-20-33-00.cfg user_2019-10-31-20-33-00.cfg.bak
cat user_2019-10-31-20-33-00.cfg.bak | sed -e 's/CONFIG_CMA_SIZE_MBYTES=1024/CONFIG_CMA_SIZE_MBYTES=512/g'  > user_2019-10-31-20-33-00.cfg
cd ../../../../..

make all XSA_DIR=../vivado PLATFORM=ultra96v2_oob
make sysroot
cd ..

mv Makefile Makefile.bak
cat Makefile.bak | sed -e 's/zcu102_base/ultra96v2_oob/g' > Makefile
rm *.bak
mv scripts/zcu102_base_pfm.tcl scripts/ultra96v2_oob_pfm.tcl
make pfm

cd ..

if [ ! -d sd_card ]; then
	mkdir sd_card
	mkdir sd_card/boot 
	mkdir sd_card/rootfs
fi
cp ultra96v2_oob/petalinux/images/linux/image.ub ultra96v2_oob/petalinux/images/linux/BOOT.BIN ultra96v2_oob/petalinux/images/linux/system.dtb sd_card/boot
cp ultra96v2_oob/petalinux/images/linux/rootfs.tar.gz sd_card/rootfs 

if [ ! -d Vitis-ai ]; then
	git clone https://github.com/Xilinx/Vitis-AI.git
fi
export TRD_HOME=$PWD/Vitis-AI/DPU-TRD
export SDX_PLATFORM=$PWD/ultra96v2_oob/platform_repo/ultra96v2_oob/export/ultra96v2_oob/ultra96v2_oob.xpfm
cd Vitis-AI/DPU-TRD/prj/Vitis
mv dpu_conf.vh dpu_conf.vh.bak
cat dpu_conf.vh.bak | sed -e 's/ine B4096/ine B1152/g' > dpu_conf.vh
cd config_file
mv prj_config prj_config.bak
cat prj_config.bak | sed -e 's/freqHz=300000000:dpu_xrt_top_2.aclk/#freqHz=300000000:dpu_xrt_top_2.aclk/g' -e 's/freqHz=600000000:dpu_xrt_top_2.ap_clk_2/#freqHz=600000000:dpu_xrt_top_2.aclk/g' | sed -e 's/300000000/200000000/g' -e 's/600000000/400000000/g' -e 's/sp=dpu_xrt_top_2/#sp=dpu_xrt_top_2/g' -e 's/nk=dpu_xrt_top:2/nk=dpu_xrt_top:1/g' > prj_config
cd ..
make KERNEL=DPU DEVICE=ultra96v2
cd ../../../../
cp Vitis-AI/DPU-TRD/prj/Vitis/binary_container_1/dpu.xo .
cp -r Vitis-AI/DPU-TRD/prj/Vitis/binary_container_1/sd_card/* sd_card/boot

```

最終的にSD_CARDフォルダーが作成され、必要なファイルが、生成されます。

##SDカードの生成
SDカードはバーテーションを区切って使用します。
fdisk,parted,gparted等を使って、パーティーションを作成してください。
第一パーティーションがFAT32　第２パーティションがEXT4になります。
![gparted.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299213/a19cabd5-268a-a7e5-2458-fac1f32f022c.png)

作成されたSD_CARDフォルダーの内容をSDカードにコピーします。

```sh:sdcard_copy.sh
cp ./sd_card/boot/* /media/$USER/boot
sudo tar -C /media/$USER/root -xzvf ./sd_card/rootfs/rootfs.tar.gz
sync
```

#学習済みデータからの動作確認

せっかくなので、AIを試してみたいと思います。
幸い、ザイリンクス社から、ModelZooという、学習済みデータがありますので、これを元に、Ultra96V2向けに訂正して、動作させたいと思います。
手順としては、ここのものを参考にします。必要に応じて、開発環境を整えてください。
https://github.com/Xilinx/Vitis-AI/blob/master/mpsoc/tool_docker.md
https://github.com/Xilinx/Vitis-AI/blob/master/mpsoc/runtime_docker.md

今回は、CAFFEで、resnet50でのテストをしてみます。
必要なものは、開発環境の他にデータが必要です。
resnet50のネットワークモデルと、テスト用画像が必要になります。

##学習済みデータから、FPGA用データを作成する。
機械学習については、かなり情報が出ていますので、それらを参照していただければと思います。
ザイリンクスでも学習済みデータが用意されていますので、そこから、Ultra96で動くデータを作成します。

####動作環境
ザイリンクスから、Dockerイメージで、提供されています。GPU付きもありますので、ご自分の環境で、
お使いください。
https://github.com/Xilinx/Vitis-AI/blob/master/doc/install_docker/load_run_docker.md
ここで使うのはtoolsの方になります。
`./docker_run.sh xilinx/vitis-ai:tools-1.0.0-cpu` または、
`./docker_run.sh xilinx/vitis-ai:tools-1.0.0-gpu` で起動します。

####作業ファイルの設定。
https://github.com/Xilinx/Vitis-AI/tree/master/mpsoc/vitis-ai-tool-example
を参照してください。作業領域を仮に`workspace` とします。ここに、必要なデータを集めます。
また`cf_resnet50` と `tf_resnet50`のディレクトリーを作成します。

```sh:make.sh
cd workspace
cp -rf <Vitis-AI>/mpsoc/vitis-ai-tool-example/* .
mkdir cf_resnet50
mkdir tf_resnet50
```

####学習済みネットワークモデル。
ネットワークモデルは、ザイリンクス社から、Githubで公開されています。学習済みデータもありますので、それを使います。
https://github.com/Xilinx/Vitis-AI/tree/master/AI-Model-Zoo
その中の get_model.sh をスクリプト実行すれば、モデルはダウンロードできます。

resnet50のデータをそのままコピーします。
CAFFE側は　`./AI-Model-Zoo/models/cf_resnet50_imagenet_224_224_7.7G/* `を `cf_resnet50`にコピーします。
また、Tensorflow側は`./AI-Model-Zoo/models/tf_resnetv1_50_imagenet_224_224_6.97G/*` を`tf_resnet50`にコピーします。

```sh
cp -rf <Vitis-AI>/AI-Model-Zoo/models/cf_resnet50_imagenet_224_224_7.7G/*   cf_resnet50
cp -rf <Vitis-AI>/AI-Model-Zoo/models/tf_resnetv1_50_imagenet_224_224_6.97G/* tf_resnet50
```

####画像用ファイル
テスト用に画像データセットが必要となります。テストデータは Imagenet のILSVRC2012_val_00000001.JPEG から ILSVRC2012_val_00001000.JPEG　が必要です。http://www.image-net.org/
登録後、ダウンロードするか、画像を集める方法を公開されている方がいらっしゃいますので、そちらを参照してください。なお、後で紹介するSDカードイメージにも、画像を入れておきます。

####ハードウェア情報ファイル。
Ultra96で使われているハードウェア情報ファイルを使います。
SDカードを作ったスクリプトのときに作成した、hwhファイルを使います。

```sh:dlet.sh
dlet -f <sd_card>/boot/ultra96v2_oob.hwh
```
dcfファイルができますので、それを保存しておいてください。後ほど使います。

##FPGA用データの作成
ここからは、workspace内にある、スクリプトを順番に実行していきます。

#####環境設定
ここは素直に環境設定のファイルを実行します。

```sh:0_set_env.sh
 souce 0_set_env.sh
```

#####CAFFEの設定
環境の中から、CAFFEの設定を行います。

```sh:caffe
conda activate vitis-ai-caffe
```

#####量子化データの作成
量子化のスクリプトを実行する前に一部変更する必要があります。
まず、スクリプト内に書いてあるcaffemodelのファイル名が異なっていますので、そこを変更します。変更したファイルは次のところに示します。

```sh:1_caffe_quantize.sh
#!/bin/sh

vai_q_caffe quantize -model ${CF_NETWORK_PATH}/float/trainval.prototxt \
                     -weights ${CF_NETWORK_PATH}/float/trainval.caffemodel \
                     -output_dir ${CF_NETWORK_PATH}/vai_q_output \
                     -calib_iter 100 \
                     -test_iter 100 \
                     -auto_test \
```

次に、ネットワークモデルを変更します。内部にテスト用画像ファイルとそのラベルファイルの位置を訂正します。
`workspace/cf_resnet50/float/trainval.prototxt`を変更します。
18行目19行目、39行目、40行目を変更します。

```prototxt:trainval.prototxt
    source: "/workspace/images/list.txt"
    root_folder: "/workspace/images/"
```

ここで、スクリプトを実行します。

```sh:1_caffe_quantize.sh
source 1_caffe_quantize.sh
```
#####FPGAデータの作成
先ほど作りました、dcfファイルを使用します。またこの指定をするためにjsonファイルを作成します。
ultra96v2_oob.jsonを次のように作成します。（dcfファイルは適宜変更してください）

```json:ultra96v2_oob.json
{
    "target"   : "dpuv2", 
    "dcf"      : "/workspace/sd_card/dpu-11-18-2019-18-45.dcf",
    "cpu_arch" : "arm64"
}
```

次にスクリプトファイルを編集します。必要に応じて適宜変更してください。

```sh:2_caffe_compile.sh

#!/bin/sh

TARGET=ultra96v2_oob
NET_NAME=resnet50
DEPLOY_MODEL_PATH=vai_q_output
ARCH=/workspace/ultra96v2_oob.json

vai_c_caffe --prototxt ${CF_NETWORK_PATH}/${DEPLOY_MODEL_PATH}/deploy.prototxt \
            --caffemodel ${CF_NETWORK_PATH}/${DEPLOY_MODEL_PATH}/deploy.caffemodel \
    	    --arch ${ARCH} \
    	    --output_dir ${CF_NETWORK_PATH}/vai_c_output_${TARGET}/ \
    	    --net_name ${NET_NAME} \
```

その後実行します。`cf_resnet50/vai_c_out_ultra96v2_oob`に`dpu_resnet50.elf`が作成されます。

```sh:
source 2_caffe_compile.sh 
``` 

今回はCaffeだけで、試します。
ここで、toolsのdocker は不要になります。

#アプリケーションを作成する。
最終的に動かすためのアプリケーションを作成します。
ザイリンクスが用意したものでもいいのですが、変更する部分が多いので、こちらに必要なファイルを書きます。
なお、ベースにしたアプリケーションは次のページのものです。
https://github.com/Xilinx/Vitis-AI/tree/master/mpsoc/dnndk_samples_zcu104/resnet50

#####開発環境
開発環境はザイリンクスから提供のVitis AI runtime を使います。　Dockerが必要です。
次のようにして、起動します。

```sh:runtime.sh
./docker_run.sh xilinx/vitis-ai:runtime-1.0.0-cpu
```

#####ライブラリーのSDカードへのコピー
Vitis AIをUltra96上で動かすには、一部ライブラリーが必要です。
runtime パッケージに入っていますので、それをSDカードにコピーしてください。

```
sudo cp -r /opt/vitis_ai/xilinx_vai_board_package /media/$USER/root/home/root
```

#####フォルダー構成
仮にフォルダーは、resnet50といたします。
フォルダー構成は次のようにします。

resnet50
　+Makefile
　+src
　　+main.cc
　+model
　　+dpu_resnet.elf
common
　+dputils.cpp
　+dputils.h

#####FPGA用ファイルのコピー
前に作成した、`dpu_resnet50.elf`を`model`の下にコピーします。これは、今回のUltra96向けに専用で作っています。

```
cp <workspace>/cf_resnet50/cf_resnet50/vai_c_out_ultra96v2_oob/dpu_resnet50.elf ./resnet50/model
```

`Makefile`はザイリンクス提供のものをそのまま使えます。
ザイリンクス提供のものと変わりません。

```make:Makefile
## Copyright 2019 Xilinx Inc.
##
## Licensed under the Apache License, Version 2.0 (the "License");
## you may not use this file except in compliance with the License.
## You may obtain a copy of the License at
##
##     http://www.apache.org/licenses/LICENSE-2.0
##
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.

PROJECT   =  resnet50   

override CROSS_COMPILE := /opt/vitis_ai/petalinux_sdk/sysroots/x86_64-petalinux-linux/usr/bin/aarch64-xilinx-linux/aarch64-xilinx-linux-
# If need to compile the model on the board directly, just comment out SYSROOT
override SYSROOT := /opt/vitis_ai/petalinux_sdk/sysroots/aarch64-xilinx-linux

ifeq ($(SYSROOT), )
CXX       :=   g++
CC        :=   gcc

LDFLAGS   =   $(shell pkg-config --libs opencv)
LDFLAGS   +=  -lhineon -ln2cube -lpthread -lopencv_videoio
CFLAGS    :=  -O2 -Wall -Wpointer-arith -std=c++11 -ffast-math -I../common/

else
CXX       :=   $(CROSS_COMPILE)g++
CC        :=   $(CROSS_COMPILE)gcc

CFLAGS    :=   --sysroot=$(SYSROOT) -O2 -Wall -Wpointer-arith -std=c++11 -ffast-math
CFLAGS    +=   -I$(SYSROOT)/usr/include -I$(SYSROOT)/usr/local/include -I./include -I../common/

LDFLAGS   :=  --sysroot=$(SYSROOT) -L$(SYSROOT)/usr/local/lib/ -L${SYSROOT}/lib -L${SYSROOT}/usr/lib 
LDFLAGS   +=  -ln2cube -lhineon -lopencv_videoio  -lopencv_imgcodecs -lopencv_highgui -lopencv_imgproc -lopencv_core -lpthread 

endif

CUR_DIR =   $(shell pwd)

MODDIR  =   $(CUR_DIR)/model
BUILD   =   $(CUR_DIR)/build
VPATH   =   $(SRC):$(SRC_DPUTILS)
C_DIR  :=   $(shell find $(SRC) -name *.c)
OBJ     =   $(patsubst %.c, %.o, $(notdir $(C_DIR)))
CC_DIR :=   $(shell find $(SRC) -name *.cc)
OBJ    +=   $(patsubst %.cc, %.o, $(notdir $(CC_DIR)))
CPP_DIR :=   $(shell find $(SRC) -name *.cpp)
OBJ    +=   $(patsubst %.cpp, %.o, $(notdir $(CPP_DIR)))
OBJ    +=   dputils.o

CFLAGS +=  -mcpu=cortex-a53

MODEL = $(CUR_DIR)/model/dpu_resnet50.elf
SRC     =   $(CUR_DIR)/src
SRC_DPUTILS = $(shell cd ../common/; pwd)

.PHONY: all clean 

all: $(BUILD) $(PROJECT) 
 
$(PROJECT) : $(OBJ) 
	$(CXX) $(CFLAGS) $(addprefix $(BUILD)/, $^) $(MODEL) -o $@ $(LDFLAGS)
 
%.o : %.cc
	$(CXX) -c $(CFLAGS) $< -o $(BUILD)/$@
%.o : %.cpp
	$(CXX) -c $(CFLAGS) $< -o $(BUILD)/$@

clean:
	$(RM) -rf $(BUILD)
	$(RM) $(PROJECT) 

$(BUILD) : 
	-mkdir -p $@ 
```

`main.cc`は、`src`フォルダーの下に入れます。ザイリンクス提供のものを使いたいのですが、Ultra96に使うDPU（AI）IPを小さいのを選んだため、一部変更があります。（Softmaxをアプリでやる）そのために、ソースコードは、次のようになります。

```cpp:main.cc
/*
 * Copyright 2019 Xilinx Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <assert.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <queue>
#include <string>
#include <vector>

/* header file OpenCV for image processing */
#include <opencv2/opencv.hpp>

/* header file for Vitis AI advanced API */
#include <dnndk/dnndk.h>

/* header file for Caffe input images APIs */
#include "dputils.h"

using namespace std;
using namespace cv;

/* 7.71 GOP MAdds for ResNet50 */
#define RESNET50_WORKLOAD (7.71f)
/* DPU Kernel name for ResNet50 */
#define KRENEL_RESNET50 "resnet50"
/* Input Node for Kernel ResNet50 */
#define INPUT_NODE      "conv1"
/* Output Node for Kernel ResNet50 */
#define OUTPUT_NODE     "fc1000"

const string baseImagePath = "../dataset/image500_640_480/";

/**
 * @brief put image names to a vector
 *
 * @param path - path of the image direcotry
 * @param images - the vector of image name
 *
 * @return none
 */
void ListImages(string const &path, vector<string> &images) {
    images.clear();
    struct dirent *entry;

    /*Check if path is a valid directory path. */
    struct stat s;
    lstat(path.c_str(), &s);
    if (!S_ISDIR(s.st_mode)) {
        fprintf(stderr, "Error: %s is not a valid directory!\n", path.c_str());
        exit(1);
    }

    DIR *dir = opendir(path.c_str());
    if (dir == nullptr) {
        fprintf(stderr, "Error: Open %s path failed.\n", path.c_str());
        exit(1);
    }

    while ((entry = readdir(dir)) != nullptr) {
        if (entry->d_type == DT_REG || entry->d_type == DT_UNKNOWN) {
            string name = entry->d_name;
            string ext = name.substr(name.find_last_of(".") + 1);
            if ((ext == "JPEG") || (ext == "jpeg") || (ext == "JPG") ||
                (ext == "jpg") || (ext == "PNG") || (ext == "png")) {
                images.push_back(name);
            }
        }
    }

    closedir(dir);
    sort(images.begin(), images.end());
}

/**
 * @brief load kinds from file to a vector
 *
 * @param path - path of the kinds file
 * @param kinds - the vector of kinds string
 *
 * @return none
 */
void LoadWords(string const &path, vector<string> &kinds) {
    kinds.clear();
    fstream fkinds(path);
    if (fkinds.fail()) {
        fprintf(stderr, "Error : Open %s failed.\n", path.c_str());
        exit(1);
    }
    string kind;
    while (getline(fkinds, kind)) {
        kinds.push_back(kind);
    }

    fkinds.close();
}

/**
 * @brief calculate softmax
 *
 * @param data - pointer to input buffer
 * @param size - size of input buffer
 * @param result - calculation result
 *
 * @return none
 */
void CPUCalcSoftmax(const float *data, size_t size, float *result) {
    assert(data && result);
    double sum = 0.0f;

    for (size_t i = 0; i < size; i++) {
        result[i] = exp(data[i]);
        sum += result[i];
    }

    for (size_t i = 0; i < size; i++) {
        result[i] /= sum;
    }
}

/**
 * @brief Get top k results according to its probability
 *
 * @param d - pointer to input data
 * @param size - size of input data
 * @param k - calculation result
 * @param vkinds - vector of kinds
 *
 * @return none
 */
void TopK(const float *d, int size, int k, vector<string> &vkinds) {
    assert(d && size > 0 && k > 0);
    priority_queue<pair<float, int>> q;

    for (auto i = 0; i < size; ++i) {
        q.push(pair<float, int>(d[i], i));
    }

    for (auto i = 0; i < k; ++i) {
        pair<float, int> ki = q.top();
        printf("top[%d] prob = %-8f  name = %s\n", i, d[ki.second],
        vkinds[ki.second].c_str());
        q.pop();
    }
}

/**
 * @brief Run DPU Task for ResNet50
 *
 * @param taskResnet50 - pointer to ResNet50 Task
 *
 * @return none
 */
void runResnet50(DPUTask *taskResnet50) {
    assert(taskResnet50);

    /* Mean value for ResNet50 specified in Caffe prototxt */
    vector<string> kinds, images;

    /* Load all image names.*/
    ListImages(baseImagePath, images);
    if (images.size() == 0) {
        cerr << "\nError: No images existing under " << baseImagePath << endl;
        return;
    }

    /* Load all kinds words.*/
    LoadWords(baseImagePath + "words.txt", kinds);
    if (kinds.size() == 0) {
        cerr << "\nError: No words exist in file words.txt." << endl;
        return;
    }

    /* Get the output Tensor for Resnet50 Task  */
//    int8_t *outAddr = (int8_t *)dpuGetOutputTensorAddress(taskResnet50, OUTPUT_NODE);
    /* Get size of the output Tensor for Resnet50 Task  */
//    int size = dpuGetOutputTensorSize(taskResnet50, OUTPUT_NODE);
    /* Get channel count of the output Tensor for ResNet50 Task  */
    int channel = dpuGetOutputTensorChannel(taskResnet50, OUTPUT_NODE);
    /* Get scale of the output Tensor for Resnet50 Task  */
//    float out_scale = dpuGetOutputTensorScale(taskResnet50, OUTPUT_NODE);
//    float *softmax = new float[size];
    float *softmax = new float[channel];
    float *FCResult = new float[channel];

    for (auto &imageName : images) {
        cout << "\nLoad image : " << imageName << endl;
        /* Load image and Set image into DPU Task for ResNet50 */
        Mat image = imread(baseImagePath + imageName);
        dpuSetInputImage2(taskResnet50, INPUT_NODE, image);

        /* Launch RetNet50 Task */
        cout << "\nRun DPU Task for ResNet50 ..." << endl;
        dpuRunTask(taskResnet50);

        /* Get DPU execution time (in us) of DPU Task */
        long long timeProf = dpuGetTaskProfile(taskResnet50);
        cout << "  DPU Task Execution time: " << (timeProf * 1.0f) << "us\n";
        float prof = (RESNET50_WORKLOAD / timeProf) * 1000000.0f;
        cout << "  DPU Task Performance: " << prof << "GOPS\n";

        /* Calculate softmax on DPU and display TOP-5 classification results */
//        dpuRunSoftmax(outAddr, softmax, channel, size/channel, out_scale);
        /* Get FC result and convert from INT8 to FP32 format */
        dpuGetOutputTensorInHWCFP32(taskResnet50, OUTPUT_NODE, FCResult, channel);

        /* Calculate softmax on CPU and display TOP-5 classification results */
        CPUCalcSoftmax(FCResult, channel, softmax);

        TopK(softmax, channel, 5, kinds);

        /* Display the impage */
        cv::imshow("Classification of ResNet50", image);
        cv::waitKey(1);
    }

    delete[] softmax;
    delete[] FCResult;
}

/**
 * @brief Entry for runing ResNet50 neural network
 *
 * @note Vitis AI advanced APIs prefixed with "dpu" are used to easily program &
 *       deploy ResNet50 on DPU platform.
 *
 */
int main(void) {
    /* DPU Kernel/Task for running ResNet50 */
    DPUKernel *kernelResnet50;
    DPUTask *taskResnet50;

    /* Attach to DPU driver and prepare for running */
    dpuOpen();

    /* Load DPU Kernel for ResNet50 */
    kernelResnet50 = dpuLoadKernel(KRENEL_RESNET50);

    /* Create DPU Task for ResNet50 */
    taskResnet50 = dpuCreateTask(kernelResnet50, 0);

    /* Run ResNet50 Task */
    runResnet50(taskResnet50);

    /* Destroy DPU Task & free resources */
    dpuDestroyTask(taskResnet50);

    /* Destroy DPU Kernel & free resources */
    dpuDestroyKernel(kernelResnet50);

    /* Dettach from DPU driver & free resources */
    dpuClose();

    return 0;
}
```

ここまで出来たら、`make`を実行します。

```make:make
make
```

`resnet50` が実行ファイルとして、できますので、それをSDカードにコピーします。

```cmd:cp
sudo cp -rf resnet50 /media/$USER/root/home/root
```

#Ultra96での実行
SDカードを作成の上、Ultra96v2に挿入の上、動作させます。
ssh
テスト用画像ファイルを用意してください。それを、入れます。
デフォルトでは　~/dataset/image500_640_480 に入れます。

WifiをUltra96V2_<MAC ADDRESS>で接続します。
192.168.2.1で接続できますので、ホスト側から、SSHで接続します。

```sh:ssh
ssh -X root@192.168.2.1
```
パスワードはroot です。

これからは、Ultra96側の操作です。最初の一回だけは、ライブラリーのインストールを行います。

```sh:initall.sh
cd ~/xilinx_vai_board_package/
./initall.sh
```
その後、resnet50を動作させます。

```sh:resnet50
cd ~/resnet50/
./resnet50
```

うまく行けば、次のように表示されます。
![Screenshot from 2020-02-18 01-04-17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299213/85959ade-7de5-1112-7844-19c24d0fd089.png)

##参考文献
https://www.xilinx.com/support/documentation/sw_manuals/vitis_ai/1_0/ug1414-vitis-ai.pdf　Vitis AI User Guide　
https://www.xilinx.com/support/documentation/ip_documentation/dpu/v3_1/pg338-dpu.pdf　Zynq DPU V3.1 Product Guide

https://github.com/Xilinx/Vitis-AI   XILINX Vitis-AI Github

https://qiita.com/basaro_k/items/86811ed78397d2a3b4b1   Qiita   Ultra96の開発環境(Vitis2019.2版)

##おまけ
すぐに試したい方、画像が手に入りにくい方向けに、SDカードイメージを用意しました。
AI-EDGEコンテスト向けと共通になっています。
http://www.fpga.co.jp/AI-EDGE/ultra96v2_oob.img.zip
書き込みツールには次のものが便利です。
https://www.balena.io/etcher/