EdgeBoard的PYNQ移植

EdgeBoard的PYNQ移植

PYNQ是我很喜欢的一个FPGA开源工具。它将Zynq上的各种硬件资源用Python封装了起来,允许用户通过Jupytor Notebook远程调试FPGA。将PYNQ移植到Baidu EdgeBoard上是我去年在COVID疫情期间开的坑(具体请参看我的GitHub),但中间遇到的小问题有点多,便一直没有完全填上。我最近抽出了些时间重新拾起了这个事情,就顺便把整个过程和遇到的问题都记录下来,以飨后来者。

相关源代码已开源至Github,预编译PYNQ镜像文件我也已上传至阿里云盘。因为设备有限,我没有为这个镜像进行所有外设的上板测试。如果你发现了任何问题,欢迎和我联系。

PYNQ官方提供了SD卡镜像编译的文档,因此一些比较明确的步骤我可能不会详述,请搭配官方文档阅读。

为了避免混淆,这里先澄清一下很多朋友的一个小误解:本文中的PYNQ指的是PYNQ框架(本质上是一个Ubuntu),而非那两个粉色的开发板(PYNQ-Z1和PYNQ-Z2)。PYNQ可以部署在PYNQ-Z1和PYNQ-Z2上,也可以部署在其他Zynq系列的FPGA上。

开始之前

Xilinx提供了PYNQ-Z1、PYNQ-Z2、ZCU104等开发板的镜像,可以直接从PYNQ网站上下载到,具体列表可以在PYNQ的下载页面找到(也包括Ultra96等第三方开发板)。如果我们想要在此外的开发板(比如本文中的EdgeBoard Lite)上使用PYNQ,就需要自己编译PYNQ镜像。

本文的编译目标是PYNQ v2.7。本文写作时(2021年8月)PYNQ2.7的开发已经完成,但是还没有合入主线分支,文档也没有更新(但差别不大,反正各种奇奇怪怪的bug本来也不会写在文档里)。每次大版本(即2.X)更新后,随着时间推移,依赖软件包之间会出现五花八门的兼容性问题,这些问题很可能要到下一次PYNQ大版本更新才会一次性修复。这个问题主要得归咎于Xilinx官方,PYNQ的编译中依赖的很多软件都不指定特定版本。因此,如果你看到本文时已是很久之后,文章中遇到的问题和你遇到的可能不尽相同。

硬件上,除了Edgeboard本身,我准备了一台半淘汰的笔记本搭建编译环境。这个不是很重要(虚拟机也不是不可以),唯一需要确认的是剩余磁盘空间要够大(大约200GB)。

PYNQ v2.7需要Vivado/Vitis/PetaLinux的版本为2020.2。我的操作系统是Ubuntu 18.04.5 LTS。Vivado/Vitis 2020.2最高支持Ubuntu 18.04.4,安装时需要修改/etc/os-release骗过安装程序。这里操作系统的版本建议请严格按照Xilinx的安装指南,我不建议你和我一样操作。

编译过程中需要从互联网上下载大量依赖组件,请确保你能够自由访问互联网。

理想的编译过程

设置PYNQ环境

首先将PYNQ的GitHub Repo复制到本地,并切换到image_v2.7分支。

Terminal
1
2
3
❯ git clone https://github.com/Xilinx/PYNQ.git pynq
cd pynq
❯ git checkout image_v2.7

我们接下来的主要工作都在sdbuild目录下进行。先运行scripts/setup_host.sh,它会用apt安装各种需要的包,以及下载QEMU和CrossTool-NG。

Terminal
1
source ./scripts/setup_host.sh

这个脚本运行需要很久的时间(主要是因为下载QEMU和CrossTool-NG的安装包),但好在只需要运行一次。

后续编译过程还会依赖Ninja,然而该脚本中没有安装,因此我们手动安装下。

Terminal
1
sudo apt install ninja-build

到这里,Xilinx全家桶需要的各种依赖包也安装完成了,接下来就可以安装Xilinx全家桶了。

安装Xilinx全家桶

我们需要安装Vivado、Vitis、PetaLinux三个软件。按照PYNQ v2.7的版本要求,三者的版本都必须是2020.2。这里需要注意的是,不要使用管理员权限安装。

Vivado和Vitis是通过同一个安装程序安装的,安装时命令行运行./xsetup,勾选需要的组件即可。

PetaLinux安装的命令如下:

Terminal
1
❯ petalinux-v2020.2-final-installer.run --dir <xilinx_install_dir>/petalinux/2020.2 --platform "aarch64 arm"

具体安装选项可以参考UG1144。这里存在一个坑,PetaLinux的安装程序允许用户任意指定安装位置,但是PYNQ之前的版本默认却要求它的路径必须是.../2020.2/的形式(v2.7有无修复不确定,我没有去测试)。

安装完成后,我们需要将下面几行代码加入.bashrc(也可以每次打开命令行手动执行)。这样一来,就可以在命令行中运行这些软件了。

.bashrc
1
2
3
source <xilinx_install_dir>/petalinux/2020.2/settings.sh
source <xilinx_install_dir>/Vivado/2020.2/settings64.sh
source <xilinx_install_dir>/Vitis/2020.2/settings64.sh

添加自定义开发板

PYNQ在boards文件夹下预置了Pynq-Z1Pynq-Z2ZCU104三个与对应开发板同名的文件夹。它们的内部结构大同小异,主要分成以下五个部分:

  1. notebooks
  2. petalinux_bsp
  3. packages
  4. baselogictoolsOverlay文件夹;
  5. <board_name>.spec

下面简单介绍下这些文件和文件夹的功能,具体的细节(如果你需要定制一些复杂的东西)还请自行阅读PYNQ的编译脚本源代码。

notebooks文件夹会原样复制到最终的用户目录下,每次大家打开Jupyter Notebook后看到的就是它。这个文件夹里的内容不是很重要,一般都是放些教程。

petalinux_bsp文件夹用于PetaLinux生成BSP,它只在sdbuild/scripts/create_bsp.sh脚本中用到。该文件夹里边包括两个文件夹meta-userhardware_project。其中,meta-user文件夹会被复制到PetaLinux项目文件夹下的project-spec/meta-user,里面放设备树文件、各种用户配置等(如果你对PetaLinux项目的目录结构不了解的话可以参考UG1144)。hardware_project里需要放.xsa硬件描述文件(该文件由Vivado导出),或者也可以放一些脚本(至少包括一个Makefile)供PetaLinux实时地生成.xsa文件。如果用户在<board_name>.spec中指定了BSP,那么hardware_project不会被用到。这里一个文档中一个没有注明的是,meta-user总是会起作用,即使你指定了BSP,它也会覆盖掉里边的meta-user并重新打包。

packages文件夹的结构和sdbuild/packages的结构类似,这两个目录下的每个文件夹对应一个个的组件,在编译过程中会被安装到RootFS中。安装过程主要是在sdbuild/scripts/install_packages.sh中进行。如果你需要增加组件,建议阅读此脚本和sdbuild/packages/README.md了解更多细节。

其他文件夹中如果存在Makefile文件,就会被认为是Overlay文件夹。在编译pynq本身时,<pynq_repo_dir>/build.sh脚本会试图进入这些文件夹,并挨个检查是否存在.bit.hwh.xsa等文件。这些文件夹不是必须的,主要是为用户提供一些针对该开发板的预置Overlay

<board_name>.spec文件描述了针对该开发板的各种配置和文件路径。它的格式如下所示:

board_name.spec
1
2
3
4
5
6
ARCH_<board_name> := aarch64 # Zynq的CPU架构,可以是aarch64或arm
BSP_<board_name> := ... # 开发板的BSP文件(如果有的话)
BITSTREAM_<board_name> := ... # 默认的比特流文件
FPGA_MANAGER_<board_name> := 1

STAGE4_PACKAGES_<board_name> := pynq ethernet ...

注意这里的board_name要和文件夹的名字一致。

接下来我们可以依样画葫芦为自己的开发板配置这些文件了。
我在edgeboard的仓库中放置了针对EdgeBoard Lite的配置文件,如果需要的话你也可以参考。

一把梭编译,赌人品

按照官方的流程,理论上我们可以开始进行漫长的编译了。不过我强烈建议你先阅读下后续的几个章节再开始编译(可以避免很多无谓的时间浪费)。

sdbuild下运行:

Terminal
1
❯ make BOARDDIR=<edgeboard_repo_dir>/edgeboard/pynq BOARDS=edgeboard-fz3a

运气不错的话,我们能够在几个小时后获得最终的SD卡镜像edgeboard-fz3a-2.7.0.img,然后就可以将其烧写到SD卡上了。

Terminal
1
❯ sudo dd if=<pynq_repo_dir>/sdbuild/output/edgeboard-fz3a-2.7.0.img bs=1M of=/dev/mmcblk0 && sync

注意这里你的SD卡设备名可能不是/dev/mmcblk0,请务必再三确认,以免写入其他磁盘丢失数据。

各种常见和不常见的Bug

实践中,上一步十之八九会遇到各种奇奇怪怪的问题,然后报错退出。这里我没法给出一个万能方法,只能说“具体情况,具体分析”。记得多翻日志,多问谷歌。

我把各种我遇到的问题罗列于此,并提供了我的原因分析和解决方法。

NodeJS安装时报错base_files is not configured

在运行到sdbuild/packages/jupyter/qemu.sh时,它会用apt安装NodeJS(这是运行Jupyter Notebook必要的)。此时,apt安装无法完成,并出现以下报错:

Terminal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Setting up base-files (11ubuntu5) ...
/bin/rmdir: failed to remove '/var/run': Directory not empty
dpkg: error processing package base-files (--configure):
installed base-files package post-installation script subprocess returned error exit status 1
dpkg: dependency problems prevent configuration of bash:
bash depends on base-files (>= 2.1.12); however:
Package base-files is not configured yet.

dpkg: error processing package bash (--configure):
dependency problems - leaving unconfigured
Errors were encountered while processing:
base-files
bash
No apport report written because the error message indicates its a followup error from a previous failure.
E: Sub-process /usr/bin/dpkg returned an error code (1)

它提示NodeJS在安装时需要访问一个名为base-files的组件,但是该组件在此时还没有完成“configure”。我们沿着这个信息往上追溯的话,会一直找到RootFS的初始化,此时该组件应当完成安装。

PYNQ采用Multistrap作为RootFS的初始化工具,它利用apt下载所需要的包并进行安装。base-files正是其中一个此时应当被安装的包,完整的包列表可以见ubuntu/focal/aarch64/multistrap.config。观察安装日志,可以最终定位到真正的错误原因:base-files的安装会用到chmod,这要求另一个名为base-passwd的包必须比它先完成“configure”,否则base-files的安装就会失败。简而言之,就是base-files依赖base-passwd

那么问题来了,为什么RootFS初始化时部分包安装失败后不会报错呢?原因在sdbuild/scripts/create_rootfs.sh脚本中如下的两行代码抑制了postinst1.shpostinst2.sh两个脚本的报错:

create_rootfs.sh
1
2
3
$dry_run sudo -E chroot $target bash postinst1.sh
... # other stuff
$dry_run sudo -E chroot $target bash postinst2.sh

因此此处即使发生安装失败,程序也会继续执行下去。

更为本质的一个问题是为什么dpkg无法检测到上述两个软件包之间的依赖关系。这已经超出了PYNQ的范畴,根据Debian社区的意思大体上可以这么理解:base-filesbase-passwd都是属于Essential的包,理论上它们都是必装的,因此就没有设置依赖关系。如果我们为这些必装的组件之间相互设置依赖关系的话,会陷入循环依赖的地狱。那么在安装时它们之间发生依赖冲突怎么办呢?这个问题至少在2011年就有人提出过,社区的结论是这个问题并不常见(多数时候base-passwd总比base-files先完成“configure”),所以不妨依靠玄学。他们认为这个问题在新的Multistrap版本上不会出现,然而并不。

这里我的解决方法是手工指定dpkg --configure的顺序,将sdbuild/scripts/create_rootfs.shpostinst1.sh部分中原先的dpkg --configure -a修改成:

create_rootfs.sh
1
2
3
4
# 先完成base-passwd的configure
dpkg --configure gcc-10-base libcrypt1 libc6 libgcc-s1 libdebconfclient0 base-passwd
# 再完成其他组件的configure
dpkg --configure -a

python2.7-minimal安装失败

与上个问题类似,有时会出现python2.7-minimal这个包的configure失败。这个问题本身不是很严重,因为postinst1.shpostinst2.sh两个脚本会各执行一遍dpkg --configure -a,因此第一遍中极少量没成功安装的包会在第二遍中完成安装(比如cups-pk-helper这个包经常如此)。然而,因为有大量包依赖于python2.7-minimal,一旦它安装失败后,一连串的包会一同安装失败,然后安装程序就崩了。

这里可以从报错信息中观察到,安装失败的原因是它的postinst脚本中用到了awk,然而此时awk这个命令还未安装。解决方法也和上个问题类似,即手工指定dpkg --configure的顺序,确保提供awk命令的包比python2.7-minimal先完成configure。有很多包都提供了awk命令,我这里选择了mawk

create_rootfs.sh
1
2
dpkg --configure <... 其他需要提前`configure`的包> mawk
dpkg --configure -a

无法从SD卡启动,找不到RootFS

这个问题深究起来非常复杂,现象是烧写完SD卡上板卡后,无法完成开机,屏幕/串口会显示内核错误。其中括号里的数字我这边是179,2179,10两种情形之一(我没有彻底弄清楚这俩数字的含义)。

1
Kernel Panic - not syncing: VFS: Unable to mount root fs on unknown-block(179,10)

关于SD卡的一系列bug都会引起这个报错。首先请确保SD卡烧写成功,烧写完成后可以在Ubuntu中挂载并尝试打开检查一下,如果不能正常打开的话重新烧写下。然后请参考以下几点依次排查。因为该报错的原因很多,我这里可能列举不全,见谅。

使用dd烧写时块大小不合适

RootFS分区的文件系统是Ext4,它在开机时会检查分区大小,因此如果在dd时使用过大的块大小(block size,也就是bs=...参数),就会无法通过分区大小的检查。

比如用4M的块大小烧写镜像的话(即sudo dd bs=4M if=... of=...),开机就会保错。在我这使用1M的块大小是正常的,具体命令可以看前文。

SD卡的写保护

这个问题似乎是来自于很多人使用了淘宝购买EdgeBoard FZ3A开发板时店家提供的所谓“Vivado参考设计”。这个参考设计中关于SD卡的配置存在错误,它打开了SD卡槽的写保护引脚。但是据WhyCan Forum社区的文章指出,EdgeBoard的PCB设计中去掉了这一引脚。因此,我们稳妥起见,可以在设备树中禁用掉写保护功能。

如前文所述,PYNQ为我们提供了一个修改设备树的接口,就是boards/<board_name>/petalinux_bsp文件夹。我们先在相应目录下创建一个设备树文件:

Terminal
1
2
3
mkdir -p <pynq_repo_dir>/boards/<board_name>/petalinux_bsp/meta-user/recipes-bsp/device-tree/files
cd <pynq_repo_dir>/boards/<board_name>/petalinux_bsp/meta-user/recipes-bsp/device-tree/files
touch system-user.dtsi

然后对sdhci1节点(对应于PS_SD1,即我们的SD卡槽)进行修改。完成以后,你的system-user.dtsi文件应该长这个样子:

system-user.dtsi
1
2
3
4
5
6
7
8
9
10
/include/ "system-conf.dtsi"
/ { /*根节点,这里保持不变*/
};

&sdhci1 {
status = "okay";
max-frequency = <50000000>;
no-1-8-v; /*我其实不太理解这行的作用,但反正一出兼容性问题,大家就会写这个*/
disable-wp; /*关掉写保护功能*/
}

理论上如果使用我自己做的Board Files里的Zynq Preset,是不会遇到这个问题的(不过我没试,重新编译太费时了)。

Boot参数中设置了错误的root分区位置

PYNQ默认总是从/dev/mmcblk0(这个路径是PYNQ上的,不是你的宿主Ubuntu上的)启动系统,即它如果它有多个SD外设的话,SD卡要连在PS_SD0上。不幸的是,EdgeBoard还真的有两个SD设备,一个是我们的TF卡槽,另一个是一颗eMMC Flash芯片。默认情形下,PYNQ总是会尝试从后者启动,而我们的系统实际存放在SD卡上。

PetaLinux的Boot参数是通过设备树中的/chosen/bootargs条目进行配置的。默认情况下,最后镜像使用的设备树中该条目会是这样的(每个字段的先后顺序不重要):

system-user.dtsi
1
bootargs = "root=/dev/mmcblk0p2 rw earlyprintk rootfstype=ext4 rootwait devtmpfs.mount=1 uio_pdrv_genirq.of_id=\"generic-uio\" clk_ignore_unused";

但我们希望其中的字段root=/dev/mmcblk0p2变成root=/dev/mmcblk1p2。直觉上,首先想到的是很上文一样修改system-user.dtsi文件,从而影响最终生成的设备树。实验会告诉你完全不起作用,最后输出也就是实际使用的设备树里还是上面这行默认值。这里大家就会遇到PYNQ这个编译流程设计的很糟糕的一点:system-user.dtsi这个文件中的只有一部分会起作用,至于想弄清哪部分,要么做实验,要么看懂编译源代码。比如上面对&sdhci1节点的修改就能生效,对/chosen/bootargs对修改就不起作用。

生成设备树是制作BSP文件的中一部分,接下来我们来弄清楚PYNQ是如何生成最终的BSP文件的。我们先考虑用户没有提供预编译的BSP文件的情形,此时PYNQ内部会依次做这些事:

  1. 建立一个空的PetaLinux项目
  2. 拷贝用户的petalinux_bsp/meta-user到该项目下;
  3. 读入硬件配置即petalinux_bsp/hardware_project中的XSA文件,生成Config文件;
  4. 构建并打包成BSP文件;
  5. 利用上一步获得的BSP文件建立一个新的PetaLinux项目;
  6. 直接在脚本中修改Config文件,加入一些配置;
  7. 重新运行petalinux-config生成新的Config文件;
  8. 开始各种build,最终生成我们需要的BOOT.bin。

其中步骤1、2、3、4在sdbuild/scripts/create_bsp.sh脚本中进行,步骤5、6、7、8在sdbuild/Makefile中进行。如果用户指定了预编译的BSP文件,就把上文中的第1步换成“利用用户提供的BSP文件建立一个新的PetaLinux项目”。这里最令人困惑的地方是,为什么要进行两次“create-config-build”的流程,至少我没有看出它这么做的必要性。这样一通操作之后,用户在petalinux_bsp/meta-user的子目录下的system-user.dtsi文件中修改的一些设备树节点(对应上文步骤2),会在步骤6中被新引入的一些设备树文件冲刷掉。步骤6通过CONFIG_USER_LAYER_0这一设置混入了一些新的设备树文件,这些额外的设备树文件位于sdbuild/boot/meta-pynq/recipes-bsp/device-tree。就Boot参数而言,这里边的pynq_bootargs.dtsi文件提供了前述的默认/chosen/bootargs。因此无论我们在system-user.dtsi文件中如何修改/chosen/bootargs,最终都会被覆盖掉。因此,我们的解决方案很简单,修改下该文件,将其中的root=/dev/mmcblk0p2变成root=/dev/mmcblk1p2。因为这个文件只有几行,改动也很小,我就不把代码贴出来了。

除了修改pynq_bootargs.dtsi,我们还需要修改sdbuild/Makefile,将下面这行代码中的mmcblk0p2变成mmcblk1p2

Makefile
1
echo 'CONFIG_SUBSYSTEM_SDROOT_DEV="/dev/mmcblk0p2"' >> $$(PL_CONFIG_$1)

我这里提供另一个有趣的思路,sdhci0sdhci1在设备树文件中是俩alias,我们可以在设备树中交换它们的值(/amba/mmc@ff160000/amba/mmc@ff170000)。不过我没有做过实验。

UART串口不工作

EdgeBoard FZ3A有两个UART串口,一个是BT1120连接件的一部分,另一个转成了USB接口。我们用来连接电脑进行交互的串口,显然希望是后者。和前面SD卡的情况相似,PYNQ默认PS_UART0作为输出串口,而我们实际想要的是PS_UART1。解决这个问题的方法很简单,在上面修改CONFIG_SUBSYSTEM_SDROOT_DEV的那行后面加上以下几行:

Makefile
1
2
3
4
5
echo 'CONFIG_SUBSYSTEM_PMUFW_SERIAL_PSU_UART_1_SELECT=y' >> $$(PL_CONFIG_$1)
echo 'CONFIG_SUBSYSTEM_FSBL_SERIAL_PSU_UART_1_SELECT=y' >> $$(PL_CONFIG_$1)
echo 'CONFIG_SUBSYSTEM_ATF_SERIAL_PSU_UART_1_SELECT=y' >> $$(PL_CONFIG_$1)
echo 'CONFIG_SUBSYSTEM_SERIAL_PSU_UART_1_SELECT=y' >> $$(PL_CONFIG_$1)
echo 'CONFIG_SUBSYSTEM_PRIMARY_SD_PSU_SD_1_SELECT=y' >> $$(PL_CONFIG_$1)

dash.preinst找不到

RootFS的编译脚本(即create_rootfs.sh)会在前面提到的postinst1.shpostinst2.sh两个脚本执行中提示dash.preinst执行失败,原因是/var/lib/dpkg/info/dash.preinst找不到。该错误的原因仅仅是上游已经把dash.preinst这个脚本删除了(可参考此文)。因此这里执行该脚本的三行代码都是多余的,直接删除即可。即使不删除,它们也应当不会引起其他异常(除了在终端里输出一些错误信息)。

RootFS分区磁盘容量不足

PYNQ在编译的最后,会对最终的镜像的RootFS分区进行扩容。扩容主要是增加一些用户空间,以及给操作系统本身腾出一些地方放临时文件。扩容操作是在sdbuild/scripts/resize_umount.sh脚本中进行(别问我为什么文件名中unmount拼写错了,源代码如此)。PYNQ的开发者很可能是为了尽量使最终镜像小于8GB,所以只额外扩容了300MB。

很不幸的是,这300MB实在是捉襟见肘,经常开完机就用得七七八八了。极端情况下,可能不足以支撑Jupyter成功运行,现象是能够ssh访问,但浏览器完全打不开Jupyter。排查方法是,上电后通过ssh进入板上的操作系统,执行df,观察/分区的磁盘占用情况。

我的SD卡是64GB的,因此,我直接修改了resize_umount.sh脚本,将扩容空间从300MB改成了3000MB。你可以根据你的SD卡容量自由发挥,当然没必要太大,否则烧写SD卡会变得很慢。具体到代码上,在该脚本中找到下面一行代码,把其中的300改成任意你想要的数字。

Makefile
1
new_size=$(( $used_size + (300 * 1024) ))

各种文件下载失败

多数都是网络环境的问题,可以先阅读下一节。如果还是解决不了的话,请联系公司的IT工程师协助解决。

Speedup!编译提速

在我的破笔记本上,完整的一次PYNQ流程需要整一个下午,中间还需要多次输入管理员密码。因为我们很难一次成功,所以需要不断地重新进行编译流程,所以我们总希望整个编译能够进行地快一些。接下来我们开始着手加速编译流程。

跳过输入管理员密码

PYNQ编译中会频繁使用sudo命令,需要我们不断地输入密码,否则程序就一直卡在那等待。Ubuntu默认两次sudo的时间间隔超过15分钟左右就要重新输入密码,我们可以把这个时间延长一些(比如这里我延长到了2小时)。

首先在/etc/sudoers中找到如下一行。

1
Defaults    env_reset

将它改成:

1
Defaults    env_reset,timestamp_timeout=120

为Multistrap更换下载源

Multistrap利用apt下载各种需要的包来构建RootFS。默认情况下,apt会从官方ports源(http://ports.ubuntu.com/ubuntu-ports)下载文件,时间很长且经常失败。换源的方法非常方便,直接在编译开始前声明PYNQ_UBUNTU_REPO环境变量即可。例如换成清华源:

Terminal
1
export PYNQ_UBUNTU_REPO=http://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports

注意这里必须是http而不是https

为CrossTool-NG建立本地缓存

CrossTool-NG每次运行时会从云端下载很多包(主要是各种源代码)。根据其文档的指示,我们可以建立一个本地的缓存文件夹。当需要下载的包本地已经缓存时,它就会跳过下载,从而节省时间。CrossTool-NG的配置文件在sdbuild/packages/gcc-mb/samples/<compile_targets>/crosstool.config,在其最后加上两行:

crosstool.config
1
2
CT_SAVE_TARBALLS=y
CT_LOCAL_TARBALLS_DIR=<somewhere_to_put_downloaded_files>

注意请在运行前保证该路径是个文件夹,且具有读写权限。

为PetaLinux建立本地SSTATE缓存

首先去Xilinx官网下载sstate-cache文件。注意版本要和PetaLinux保持一致(本文中我们用的是2020.2)。

因为EdgeBoard FZ3A板载的Zynq芯片是ARM64架构,因此为了节约空间,就只下载“aarch64 sstate-cache”和“downloads”两个包(加起来也超过60GB了)。然后我们将它们解压缩,并把路径添加到petalinux_bsp/meta-user/conf/petalinuxbsp.conf

petalinuxbsp.conf
1
2
DL_DIR = "<sstate_extract_dir>/2020.2/downloads"
SSTATE_DIR = "<sstate_extract_dir>/2020.2/sstate-cache/aarch64"

删除boards目录下Pynq-Z2以外的开发板

因为我们的编译目标是EdgeBoard FZ3A,所以boards目录下的Pynq-Z1Pynq-Z2ZCU104三个目录对我们来说就是多余的。然而PYNQ在编译过程中会遍历boards目录下的各个开发板并试图生成比特流文件。其中Pynq-Z2的输出会在pynq自身编译时用到,另外两个开发板的相关文件的编译纯属是浪费时间(关键是这步还特别费时),我怀疑这是个bug。因此,我们可以将Pynq-Z1ZCU104两个文件夹删除。

Terminal
1
2
cd <pynq_repo_dir>
rm -rf boards/Pynq-Z1 boards/ZCU104

完成之后,务必要提交到本地的Git版本历史中,否则不起作用(这是因为作者用git clone代替了cp)。

跳过boards/Pynq-Z2中各个Overlay生成比特流

pynq作为一个Python的包,在编译过程中是要和其他packages一起安装到镜像中的。进行这一步时,它会将Pynq的本地repo先复制到sdbuild/build/PYNQ中,然后再运行其中的build.sh。观察这个脚本,我们可以看到PYNQ在编译Pynq-Z2/logictoolsPynq-Z2/base两个Overlay文件夹时,会先检查其中是否已存在同名的.bit.hwh.xsa文件,存在的话就跳过生成比特流的过程(即综合、布局布线等)。因此,我们可以先按照正常流程make BOARDDIR=... BOARDS=...,然后把生成的相关文件拷贝到原始的boards/Pynq-Z2目录中的对应文件夹下:

Terminal
1
2
3
# 复制.xsa文件,.bit/.hwh文件类似处理
cp <pynq_repo_dir>/sdbuild/build/PYNQ/boards/Pynq-Z2/logictools/logictools.xsa <pynq_repo_dir>/boards/Pynq-Z2/logictools/
cp <pynq_repo_dir>/sdbuild/build/PYNQ/boards/Pynq-Z2/base/base.xsa <pynq_repo_dir>/boards/Pynq-Z2/base/

注意这里的路径,是从sdbuild/build中的PYNQ复制到原始的PYNQ。和前面一样,要提交到Git版本历史中才会生效。

另外,经过实验证明,这里.xsa.bit文件需要是同一次编译中产生,否则会发生一些奇怪且难以定位的错误。

后记

搭建这个个人博客也有几年了,本想闲暇时写些技术文章,但没能持之以恒地保持输出。中间也曾断断续续写过一些论文阅读笔记,但都没有坚持下来。一方面是因为总担心自己粗浅的专业认识贻笑大方,另一方面是实验室的项目也确实不太合适作为写作素材,便一直没有动笔。EdgeBoard的事情我在去年COVID疫情期间动工后就一直搁置了,结果发现最近有不少人关心这个事情,就花了些时间算是给它画了个句号。我也以此为契机,提起了笔记录下整个过程。希望能够对大家有所帮助。

photoed by Birmingham Museums

Author

Haozhe Zhu

Posted on

2021-08-12

Updated on

2023-08-10

Licensed under

Comments