NVDLA FPGA Mapping Workflow

Part II-Petalinux project

Posted by Max on September 17, 2018

本文采用知识共享 署名-非商业性使用-禁止演绎 4.0 国际协议授权(CC BY-NC-ND 4.0),转载请注明出处.

Preface

参考Part I,基本可以跑起FPGA工程,而对于使用Xilinx heterogeneous architecture FPGA chip的小伙伴,完成PL.hdf设计后,肯定要进一步配置ARM linux,下板跑跑github.com/nvdla/swSanity测试集,所以,本文将结合作者实践及官网issue中的信息,尽可能完整的描述Xilinx petalinux工程构建流程及对官方SW工程源码的定制化修改.

此外,无论是Part I还是本文,文章内容描述方式都以‘How to do’为第一目标,背后的技术点并未阐述,因为不想干扰操作过程。实际上,作者在三个月的摸索过程中,遇到很多值得留意的技术点,特别是在搭多时钟域工程的时候,后期希望与同道讨论后逐步添加到文章中.

Step 1: Install petalinux in host

Xilinx petalinux是一个定制版的Yocto工具,Xilinx已经把BSP准备的妥妥的了,我们要做的是定制自己的kernel. 安装过程在Xilinx UG1144写的比较清楚,这里做个概述.

1.linux环境下安装依赖库,按照UG1144 Table 2-1补全依赖工具包和库,串口调试工具作者使用的是puttygtkterm.

2.修改shell,UG1144提示要修改shellbash,可以echo $SHELL查看一下当前/bin/sh是否为bash,如果不是,sudo dpkg-reconfigure,在弹出界面中点或重建ln.

3.安装petalinux,到Xilinx Download Center下载petalinux [version number] installer,[version number]要和Vivado版本一致,其他的BSP, sstate无需下载,petalinux-build工程时,会自动download. 运行下述命令安装,

$ ./petalinux-[version NO.]-final-installer.run <path-to-target-install-dir>

和S家EDA工具安装.run文件一样,不要以sudo权限安装,permission denialchmod改下权限. 安装目录自选即可,之前有个Agilent工程师说必须安装在/opt/pkg/下,否则可能会crash,也许是centos系统原因,ubuntu并不存在此问题. 另外,安装进程到license时,有限几处会出现...[y/N]?,别急着回车,这里默认是N,需要手动y.

4.验证安装正确性,安装完成后,应该会出现Warning:No tftp server found..., 如果不是使用NFS文件系统做远程boot,则无需在意此Warning,强迫症请参考下述命令.

$ apt-get install tftpd tftp openbsd-inetd
$ echo tftp dgram udp wait nobody /usr/sbin/tcpd /usr/sbin/in.tftpd /tftproot >> /etc/inetd.conf
$ mkdir /tftproot
$ /etc/init.d/openbsd-inetd restart

注意,千万不要把重定向追加>>,写成重定向覆盖>.

运行下述命令验证安装正确性,

$ source <path-to-installed-PetaLinux>/settings.sh
$ echo $PETALINUX 

#if output path is <path-to-installed-PetaLinux>, then success.

Step 2: Create and configure petalinux project

1.创建petalinux工程,工具安装成功后,运行如下命令建立工程,

$ cd <specified-prj-dir>
$ source <path-to-installed-Petalinux>/settings.sh
...
$ petalinux-create -t project --template [zynq/zynqMP/microblaze] -n [project_name]

对于template,如果使用zynq-7000系列芯片,选择zynqzynq +UltraScale MPSoC,则选择zynqMP.

====Tips====

1). 创建Petalinux工程有两种方式:i)使用下载的.bsp文件创建,ii)建个空工程.对于nvdla工程,后续都要使用PL.hdf
    重新build,没有区别;但如果不使用PL逻辑,可以直接使用第一种的prebuilt bootloader文件下板,而不用重新build;
    
2). petalinux的相关命令,可参考UG1157.

2.配置petalinux工程,首先将Vivado export hardware输出的?.hdf文件拷贝到新建的petalinux工程目录下,之后运行如下命令,

$ cd <path-to-petalinux-prj>
$ petalinux-config --get-hw-description=./
  • 在配置界面中,选中DTG settings–>(template) MACHINE NAME, enter进入修改为开发板版本,如zcu102-rev1.0,详见UG1144 Chapter 3,p22
  • 进入Image Packaging Configuration–>Root Filesystem Type,选中SD card. 修改此处后,linux根目录系统rootfs将配置到SD中,而非默认的raminitfs,后者是将根目录系统镜像在boot阶段加载到内存中,一旦裁剪的kernel较大(大概超过120M),那么系统boot不起来;
  • 退出并保存配置.

3.配置kernel,由于rootfs配置到SD boot,那么就要取消掉kernel的RAM intial,否则在boot阶段,kernel在内存中找不到rootfs的符号镜像,便会出错,

$ petalinux-config -c kernel

弹出窗口中,选择General setup,取消掉Initial RAM filesystem and RAM disk support,如Fig-1,实际上,可以进一步配置kernel,为其”瘦身”,如在Device Drivers中取消掉不使用的外设驱动,最后,退出并保存配置.

Fig-1

Step 3: Customize the linux kernel for building UMD

nvdla/sw/prebuilt/linux/中包含了官方在kernel v4.13.3下预编译的nvdla_runtimeELF文件和依赖库libnvdla_runtime.o文件,但patelinux 2017.4的kernel是v4.9,两个版本的DMA API不同,导致依赖于DRM实现DMA数据搬移的KMD驱动无法工作,从而,UMD无法正常运行,因此,需要重新为4.9版本编译UMD.

本来最初打算写recipe通过petalinux的bitbake直接编译/nvdla/sw/umd/下的源码,将动态库.oelf添加到rootfs下,但研究发现这很难实现,petalinux工具只能添加prebuilt的.o文件,而不能新建以库文件为目标的子工程(只能创建appsmodulesinstall子工程),即使将.o的编译添加到nvdla_runtimeapps子工程makefile编译中,如何配置对应recipe保存中间生成的.o文件到rootfs,也是个难题. 所以,最后选择配置kernel,添加GNU toolchain,直接下板编译UMD的一切所需,nvdla/sw/umd/源码目录结构对应的makefile将编译依赖关系妥善处理,所以,直接下板make即可,比较方便.

1.定制rootfs,实际上,petalinux不仅提供了很多不同平台版本的可配置kernel镜像,还提供了各种系统工具包和库文件,像OpenCV,Xen等等,配置根目录

$ petalinux-config -c rootfs

弹出界面中,选择Filesystem Packages–>misc,选择packagegroup-core-buildessential,如Fig-2 ~ Fig-3,退出并保存. 如果要实现更复杂的功能,可以选择packagegroup-petalinux-self-hosted,但该包过大(~10G); 也可以选择添加其他如lddsudo等工具. misc下各种group包的功能描述,可参考Building a Custom Linux Distribution

Fig-2 Fig-3

2.编译petalinux工程,配置到这里可以先编译一次工程,否则,后续修改device tree时无法查看PL nvdla的节点信息,编译petalinux project

$ petalinux-build

Step 4: Create and add KMD module by petalinux tool

1.创建modules子工程,驱动模块kmd的编译只能通过petalinux工具编译添加,且编译方式不同于nvdla/sw/readme中介绍的generic kernel out-of-tree module build(可参考linux kernel官网Building External Modules),创建modules子工程

$ petalinux-create -t modules -n opendla --enable

modules子工程创建后,将nvdla/sw/kmd/下的所有.c,.h文件拷贝到<path-to-petalinux-prj>/project-spec/meta-user/recipes-modules/opendla/files/目录下,并将目录下的opendla.c文件删除.

2.修改驱动源文件,之前提到不同版本的kernel,对应的API有所不同,需要根据自己的内核版本修改驱动源码中的调用函数. 作者使用4.9内核,需做如下修改

  • nvdla/sw/kmd/port/linux/nvdla_gem.c,line 332drm_gem_object_put_unlocked()函数是在v4.12之后版本中才出现,其替代了之前版本中的drm_gem_object_unreference_unlocked,详情可参考Linux kernel v4.12 DRM TODO List
...
332: //drm_gem_object_put_unlocked(dobj);
333: drm_gem_object_unreference_unlocked(dobj);
...
  • line 439: dma_declare_coherent_memory(drm->dev, 0xC0000000, 0xC0000000,0x40000000, DMA_MEMORY_MAP | DMA_MEMORY_EXCLUSIVE);该函数表示需要在PS DDR中保留一块不被kernel支配,大小为0x40000000的地址空间,以便nvdla core使用DMA方式对CNN输入图像和处理结果在Cache与DDR间进行数据搬移,不知为何官方源码采用了硬编码方式处理物理地址,总线地址和存储块尺寸参数. 按照源码中的注释/* TODO Register separate driver for memory and use DT node to read memory range */, 表明官方不希望我们修改函数中的参数信息,而是根据参数值去修改device tree中对应的Node属性值.但在Vivado BD工程中,PS DDR的有效地址为0x00000000 ~ 0x7FFFFFFF, 显然,参数列表中的物理地址和总线地址不在PL的有效访问范围内,必须对此进行修改,至于具体改为什么值,可自行选择,只要是page size整数倍便可,如256 MiB,那么便可将0xC0000000修改为0x70000000(为何取高位地址,感兴趣的可参考Memory Mapping and DMA,老司机请略过), 同时,存储块尺寸不需要修改. 作者保留了1G空间,
...
439: //dma=dma_declare_coherent_memory(drm->dev, 0xC0000000, 0xC0000000,0x40000000, DMA_MEMORY_MAP | DMA_MEMORY_EXCLUSIVE);
440: dma=dma_declare_coherent_memory(drm->dev, 0x40000000, 0x40000000,0x40000000, DMA_MEMORY_MAP | DMA_MEMORY_EXCLUSIVE);
...

====Tips====

对于使用2018版Xilinx工具的小伙伴,除了上述修改,还要删除DMA_MEMORY_MAP flag,官方开发者在issue中提到该flag已经
在kernel v4.13后续版本中停用了.
  • 对于nv_small版本,还要修改nvdla/sw/kmd/firmware/include/opendla.h,添加DLA_SMALL_CONFIG宏,这样KMD驱动才能根据opendla_small.h寄存器声明来完成对nvdla core内部子内核的裁剪,使其符合nv_small/spec/defs/nv_small.spec定义.

3.配置makefilerecipe,打开<path-to-petalinux-prj>/project-spec/meta-user/recipes-modules/opendla/files/目录下的makefile文件,并将nvdla/sw/kmd/各级子目录下makefile中构成opendla.ko.o对象添加其中,

opendla-m := opendla.o

###append all of sources###
opendla-objs := nvdla_core_callbacks.o nvdla_gem.o scheduler.o engine.o bdma.o conv.o sdp.o cdp.o pdp.o rubik.o cache.o common.o engine_data.o engine_isr.o engine_debug.o
###########################
...

之后,打开<path-to-petalinux-prj>/project-spec/meta-user/recipes-modules/opendla/下的?.bb,为之前新增加的源文件添加声明,

...
SRC_URI = "file://makefile \

file://opendla.c

           file://cdp.c \
           ...
           file://opendla.h \
           ...
           file://COPYRIGHT \
"
...

Step 5: Modify the default device tree for NVDLA identification

Device treeARM处理器Bootloader必备之品,u-boot在系统boot阶段将device tree加载到内存,之后,kernel才能根据nodes inside device tree确定当前环境下有哪些外围设备可供kernel驱使,而这个”确定”过程主要是通过特定diverprobe函数搜索DTB中是否存在匹配的compatible属性来实现的. 所以,对于nvdla工程,需要查看KMD驱动中nvdla probe函数的compatible属性值是否与petalinux工程下device treePL nodecompatible属性值一致. 查看nvdla/sw/kmd/port/linux/nvdla_core_callbacks.c, line 338,显示nvdla probe函数指定的compatible属性值为.compatible = "nvidia,nvdla_2" (nvdla/sw/kmd/Documentation/devicetree/bindings/nvdla/nvdla.txt中的compatible属性值compatible = "nvidia,nvdla-1"是针对nvdla_full版本的),而<path-to-petalinux-prj>/component/plnx_workspace/device-tree/pl.dtsi中的节点信息描述了我们在Vivado中创建的nvdla工程,其中的compatible属性值显然与驱动函数中的不同,需要修改一致. 即在<path-to-petalinux-prj>/project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi覆盖掉compatible属性值,并添加之前提到的reserved memory(reserved memory的节点定义可参考Linux Reserved Memory)

/include/ "system-conf.dtsi"
/ {
    reserved-memory {
            #address-cells = <2>;
            #size-cells = <2>;
            ranges;

            nvdla_reserved: buffer@0 {
                      no-map;
                      reg = <0x0 0x40000000 0x0 0x40000000>;
            };
    };
};

&[your label-name appeared in pl.dtsi]{
    compatible = "nvidia,nvdla_2";
    memory-region = <&nvdla_reserved>;
};

====Tips====

1). 根据UG1144 APPX.B Table B-1所言,<path-to-petalinux-prj>/component/plnx_workspace/device-tree/目录
    下后缀为.dtsi和.dts的device tree定义文件是petalinux工具自动生成的,每次petalinux-build都会自动覆盖更新,
    所以对其修改无用.而system-user.dtsi不会被工具修改;

2). Device tree格式目前尚无统一标准,Linaro在牵头组织,已经发布了DeviceTree Specification Release v0.2 
    (www.devicetree.org)但内容尚不完整,作者参考了Linaro,Linux,Raspberry Pi,ARM,NXP,Toradex等多
    家的Device Tree文档,后期会在Blog里挂出survey and summary文档.

Step 6: Build petalinux project and package bootloader files

1.重新编译petalinux,在之前petalinux-build后,我们不仅创建了modules子工程,而且修改了device tree,所以需要重新编译整个工程,如果仅仅是添加了apps or modules,那么只要按照UG1144 Chapter 7做增量编译即可.

2.制作bootloaderpetalinux bootloader使用u-boot, 可按照下述命令打包bootloader相关文件,下板需要使用到BOOT.BIN, image.ubrootfs.ext4. 其中,BOOT.BIN包含fsbl, bitstream, pmu, u-bootimage.ub包含kernel image, DTB, rootfs image.

$ cd <path-to-petalinux-prj>/images/linux/
$ petalinux-package --boot --fsbl [zynq_fsbl/zynqmp_fsbl] --fpga [your *.bit file] --pmufw pmufw --u-boot 

Step 7: Partition and configure SD card

1.SD卡分区,根据UG1144 Chapter 6 “Configuring SD Card ext filesystem Boot” section对SD分区,作者将SD卡划分为BOOT(fat32), rootfs(etx4), Workbench(etx4)三个分区,分别放置boot文件,rootfs,UMD源文件和测试文件.

2.拷贝分区文件,将<path-to-petalinux-prj>/images/linux/BOOT.BIN, image.ub直接拷贝到SD卡的BOOT分区,并将nvdla/sw/umd和预编译测试集nvdla/sw/regression/flatbus/kmd拷贝到Workbench分区. 对于rootfs,作者通过dd进行的文件系统底层转换,

$ cd <path-to-petalinux-prj>/images/linux/
$ lsblk -a
# make sure your SD partition 2 label---sdX2, X may be 'b' or 'c' 
...
$ sudo umount /dev/sdX2
$ sudo dd if=rootfs.ext4 of=/dev/sdX2
...
$ sync

====Tips====

1). nvdla/sw/umd/include/ErrorMacros.h,line 60,根据rootfs目录结构更改此error log路径; 

2). 查看nvdla/sw/umd/port/linux/nvdla.c,line 53,说明umd需要使用kmd申请的D128 render实现DMA,
    所以,下板安装kmd驱动后,可以通过查看/dev/dri下是否存在renderD128, /proc/interrupts是否存在
    nvdla相关的中断项,来确认kmd驱动是否正确安装.

Step 8: Download and run tests

1.下板调试,SD卡插回开发板,调整开发板启动模式开关拨到SD启动,host打开串口调试工具配置port. 上电后输入用户名密码进入系统,安装驱动,查看driinterrupts

$ insmod /lib/modules/[kernel version]-xilinx-[petalinux version]/extra/opendla.ko
...
$ ls /dev/dri/
...
$ cat /proc/interrupts
...

2.跑测试集,首先将SD第三个分区挂载到目录系统,然后编译umd,跑测试脚本, 如Fig-4. 如果SD p3挂载在/media/card,则

$ cd /media/card/umd
$ export TOP=${PWD}
$ make TOOLCHAIN_PREFIX=/usr/bin/
...
$ dmesg -n 1
$ su root ./run_test.sh
Fig-4

====Tips====

1). 在run_test.sh脚本中,执行nvdla_runtime之前,要先为其声明依赖库路径,export LD_LIBRARY_PATH=<path-to-libnvdla_runtime.o>

2). 由于目前官方发布的compiler不支持nv_small nvdla core,所以作者只跑了Runtime test application. 如果有构建nv_full版本工程的同学,
    需要注意,compiler是运行在host环境下的,而非ARM,可以到nvdla/sw/prebuilt/linux/文件下file *,一看便知.