目录
1. 简介
2. 通过脚本构建
2.1 准备工作
2.2 通过 Makefile 构建
2.3 Makefile 源码及解析
2.3.1 源码-中文注释
2.3.2 主要功能分析
2.3.3 vivado batch 模式
2.3.4 package_xo 命令
2.3.5 vitis v++ 命令
2.4 DPU 参数
2.4.1 Arch 选项卡
2.4.2 Advanced 选项卡
2.4.3 参数汇总
2.5 工程参数
2.5.1 prj_config 文件
2.5.2 clock 选项
2.5.3 connectivity 选项
2.5.4 advanced 选项
2.5.5 vivado 选项
2.6 在 PYNQ 中使用
3. 创建 vivado platform
3.1 创建 Vivado 工程
3.1.1 platform 属性
3.1.2 创建 Block Design
3.1.3 配置时钟与复位
3.1.4 添加中断控制器
3.1.5 连接模块
3.1.6 platform 属性设置
3.1.7 Verification
3.1.8 导出 Platform
3.2 执行自定义 Makefile
3.2.1 拷贝 XSA
3.2.2 修改 Makefile
3.2.3 修改 prj_config
3.2.4 执行脚本
3.2.5 在 PYNQ 中使用
3.3 关键语句
4. 重要命令和概念解释
4.1 XO 文件
4.1.1 通过 HLS 导出 xo
4.2 xclbin 文件
4.3 xclbinutil
5. 总结
1. 简介
应网友邀请,分享在 PYNQ 框架下,搭建自定义包含 DPU 的 overlay 的过程。
- 使用 kv260 官方代码构建 DPU。
- 使用 vivado 创建自定义平台,修改官方代码构建 DPU。
- 构建 DPU 的 Makefie 解析。
- 一些重要概念的解释。
2. 通过脚本构建
2.1 准备工作
1)下载 DPU-PYNQ:
Releases · Xilinx/DPU-PYNQ · GitHubDPU on PYNQ. Contribute to Xilinx/DPU-PYNQ development by creating an account on GitHub.https://github.com/Xilinx/DPU-PYNQ/releases
目录结构:
├── boards
├── host
├── LICENSE
├── MANIFEST.in
├── pynq_dpu
├── pyproject.toml
├── README.md
└── setup.py
2)首先确保 Vitis 和 XRT 已安装,XRT安装方法见如下博文:
Vitis Accelerated Libraries 学习笔记--OpenCV 安装指南(附 XRT 安装)_vitis vision library-CSDN博客文章浏览阅读1.4k次,点赞37次,收藏9次。本文分享 ubuntu 环境下 Xilinx XRT 与 OpenCV 的安装指南。_vitis vision libraryhttps://blog.csdn.net/DongDong314/article/details/139803440
3)配置环境变量:
source /opt/xilinx/xrt/setup.sh
source /opt/Xilinx/Vitis/2022.1/settings64.sh
2.2 通过 Makefile 构建
以 KV260 为例。
通过 Makefile 构建需要输出以下三个文件:
- dpu.bit
- dpu.hwh
- dpu.xclbin
执行以下代码即可。
cd <DPU-PYNQ-2.5.0>/boards/
make BOARD=kv260_som
执行完毕,可以在 <DPU-PYNQ-2.5.0>/boards/kv260_som 目录下,找到上述文件。
2.3 Makefile 源码及解析
2.3.1 源码-中文注释
DIR_PRJ = $(shell pwd)/${BOARD}
DIR_TRD = $(shell pwd)/DPUCZDX8G
VIVADO_ROOT := $(XILINX_VIVADO)
RM = rm -f
RMDIR = rm -rf
VIVADO := ${VIVADO_ROOT}/bin/vivado
TARGET := hw
KERNEL := DPU
# 声明伪目标
.PHONY: all check_env
# 默认规则
all : check_env DPU_TRD dpu.xclbin
# 如果用户没有设置VITIS_PLATFORM,则默认为板子目录中的platform.xsa文件
VITIS_PLATFORM ?= ${DIR_PRJ}/platform.xsa
# 如果没有提供平台,则使用gen_platform.tcl脚本生成一个平台
${VITIS_PLATFORM}:
cd ${DIR_PRJ} && \
vivado -mode batch -source gen_platform.tcl
# 环境检查
check_env :
@echo "BOARD: ${BOARD}"
@echo "VITIS_PLATFORM: ${VITIS_PLATFORM}"
bash check_env.sh
# DPU IP的HDL源文件在DPU TRD流程中,更多信息请参阅Xilinx文档
DPU_TRD:
wget -O DPUCZDX8G.tar.gz https://www.xilinx.com/bin/public/openDownload?filename=DPUCZDX8G.tar.gz && \
tar xf DPUCZDX8G.tar.gz && \
rm DPUCZDX8G.tar.gz
# 从TRD流程中获取的可选参数
XOCC_OPTS = -t ${TARGET} --platform ${VITIS_PLATFORM} \
--save-temps --config ${DIR_PRJ}/prj_config \
--xp param:compiler.userPostSysLinkOverlayTcl=${DIR_TRD}/prj/Vitis/syslink/strip_interconnects.tcl
# DPU HDL源文件列表
DPU_HDLSRCS=\
${DIR_PRJ}/kernel_xml/dpu/kernel.xml\
${DIR_PRJ}/scripts/package_dpu_kernel.tcl\
${DIR_PRJ}/scripts/gen_dpu_xo.tcl\
${DIR_PRJ}/scripts/bip_proc.tcl\
${DIR_PRJ}/dpu_conf.vh\
${DIR_TRD}/dpu_ip/Vitis/dpu/hdl/DPUCZDX8G.v\
${DIR_TRD}/dpu_ip/Vitis/dpu/inc/arch_def.vh\
${DIR_TRD}/dpu_ip/Vitis/dpu/xdc/*.xdc\
${DIR_TRD}/dpu_ip/DPUCZDX8G_*/hdl/DPUCZDX8G_*_dpu.sv\
${DIR_TRD}/dpu_ip/DPUCZDX8G_*/inc/function.vh\
${DIR_TRD}/dpu_ip/DPUCZDX8G_*/inc/arch_para.vh
# Softmax HDL源文件列表
SOFTMAX_HDLSRCS=\
${DIR_PRJ}/kernel_xml/sfm/kernel.xml\
${DIR_PRJ}/scripts/package_sfm_kernel.tcl\
${DIR_PRJ}/scripts/gen_sfm_xo.tcl\
${DIR_TRD}/dpu_ip/Vitis/sfm/hdl/*.v\
${DIR_TRD}/dpu_ip/DPUCZDX8G_*/hdl/DPUCZDX8G_*_sfm.sv\
${DIR_TRD}/dpu_ip/DPUCZDX8G_*/xci/sfm/fp_*/*.xci
# 复制构建DPU设计所需的脚本
# 使用sed替换路径以反映我们的板子目录结构
${DIR_PRJ}/kernel_xml/dpu/kernel.xml:
@mkdir -p $(@D)
cp -rf ${DIR_TRD}/prj/Vitis/kernel_xml/dpu/kernel.xml $@
${DIR_PRJ}/kernel_xml/sfm/kernel.xml:
@mkdir -p $(@D)
cp -rf ${DIR_TRD}/prj/Vitis/kernel_xml/sfm/kernel.xml $@
${DIR_PRJ}/scripts:
@mkdir -p $@
${DIR_PRJ}/scripts/gen_dpu_xo.tcl: $(DIR_PRJ)/scripts
cp -f ${DIR_TRD}/prj/Vitis/scripts/gen_dpu_xo.tcl $@
${DIR_PRJ}/scripts/gen_sfm_xo.tcl: $(DIR_PRJ)/scripts
cp -f ${DIR_TRD}/prj/Vitis/scripts/gen_sfm_xo.tcl $@
${DIR_PRJ}/scripts/bip_proc.tcl : $(DIR_PRJ)/scripts
cp -f ${DIR_TRD}/prj/Vitis/scripts/bip_proc.tcl $@
${DIR_PRJ}/scripts/package_dpu_kernel.tcl: $(DIR_PRJ)/scripts
cp -f ${DIR_TRD}/prj/Vitis/scripts/package_dpu_kernel.tcl $@
sed -i 's/set path_to_hdl "..\/..\/dpu_ip"/set path_to_hdl "..\/DPUCZDX8G\/dpu_ip"/' $@
${DIR_PRJ}/scripts/package_sfm_kernel.tcl: $(DIR_PRJ)/scripts
cp -f ${DIR_TRD}/prj/Vitis/scripts/package_sfm_kernel.tcl $@
sed -i 's/set path_to_hdl "..\/..\/dpu_ip"/set path_to_hdl "..\/DPUCZDX8G\/dpu_ip"/' $@
# 内核名称必须与kernel.xml中的内核名称匹配
DPU_KERN_NAME = DPUCZDX8G
SFM_KERN_NAME = sfm_xrt_top
# 如果内核为DPU_SM,则添加softmax.xo
ifeq ($(KERNEL),DPU_SM)
kernel_xo += binary_container_1/dpu.xo
kernel_xo += binary_container_1/softmax.xo
else
kernel_xo += binary_container_1/dpu.xo
endif
# 添加此规则以便从解压的TRD中成功复制源文件
# 否则makefile将失败
${DPU_HDLSRCS}: ${DPU_TRD}
# 规则:构建Vitis DPU内核
binary_container_1/dpu.xo: ${DPU_HDLSRCS}
@mkdir -p ${DIR_PRJ}/binary_container_1
-@$(RM) ${DIR_PRJ}/$@
cd ${DIR_PRJ} ;\
$(VIVADO) -mode batch -source scripts/gen_dpu_xo.tcl -notrace -tclargs $@ $(DPU_KERN_NAME) ${TARGET} ${BOARD}
binary_container_1/softmax.xo: $(SOFTMAX_HDLSRCS)
@mkdir -p ${DIR_PRJ}/binary_container_1
-@$(RM) ${DIR_PRJ}/$@
cd ${DIR_PRJ} ;\
$(VIVADO) -mode batch -source scripts/gen_sfm_xo.tcl \
-tclargs $@ $(SFM_KERN_NAME) ${TARGET} ${BOARD}
# 规则:生成PYNQ覆盖二进制文件,我们只使用硬件平台
# 软件组件由PYNQ镜像处理,因此使用--package.no_image选项
dpu.xclbin: $(kernel_xo) $(VITIS_PLATFORM)
cd ${DIR_PRJ} ;\
v++ $(XOCC_OPTS) -l --temp_dir binary_container_1 \
--log_dir binary_container_1/logs --package.no_image \
--remote_ip_cache binary_container_1/ip_cache -o ${DIR_PRJ}/binary_container_1/$@ $<
cp -f ${DIR_PRJ}/binary_container_1/link/vivado/vpl/prj/prj.gen/sources_1/bd/*/hw_handoff/*.hwh \
${DIR_PRJ}/dpu.hwh
cp -f ${DIR_PRJ}/binary_container_1/link/vivado/vpl/prj/prj.runs/impl_1/*.bit \
${DIR_PRJ}/dpu.bit
cp -f ${DIR_PRJ}/binary_container_1/$@ \
${DIR_PRJ}/dpu.xclbin
2.3.2 主要功能分析
1)变量定义:
DIR_PRJ = $(shell pwd)/${BOARD} # 定义项目目录路径为当前路径加上板子名称
DIR_TRD = $(shell pwd)/DPUCZDX8G # 定义DPUCZDX8G目录路径为当前路径加上DPUCZDX8G
VIVADO_ROOT := $(XILINX_VIVADO) # 定义Vivado根目录路径
RM = rm -f # 定义删除文件命令
RMDIR = rm -rf # 定义删除目录命令
VIVADO := ${VIVADO_ROOT}/bin/vivado # 定义Vivado命令路径
TARGET := hw # 定义目标为硬件
KERNEL := DPU # 定义内核名称为DPU
- DIR_PRJ 和 DIR_TRD:定义项目和 DPU TRD(Target Reference Design)的目录。
- VIVADO_ROOT 和 VIVADO:定义 Vivado 工具的路径。
- TARGET 和 KERNEL:定义目标硬件和内核类型。
- VITIS_PLATFORM:定义 Vitis 平台文件路径,如果未设置,则默认生成。
2)伪目标:
- all:默认目标,依赖于 check_env、DPU_TRD 和 dpu.xclbin。
- check_env:检查 vitis 和 XRT 的版本是否正确。
3)生成平台文件:
- 如果 VITIS_PLATFORM 未设置,则运行 gen_platform.tcl 脚本生成平台文件。
4)环境检查:
- 输出 BOARD 和 VITIS_PLATFORM 变量,并运行 check_env.sh 脚本。
5)下载和解压 DPU TRD:
- 下载 DPU TRD 压缩包并解压。
6)DPU 和 Softmax 内核的 HDL 源文件:
- 定义 DPU 和 Softmax 内核的 HDL 源文件路径。
7)复制必要的脚本:
- 从 TRD 目录复制生成 DPU 和 Softmax 内核的 TCL 脚本,并进行路径替换。
8)生成 DPU 和 Softmax 内核:
- 使用 Vivado 工具生成 DPU 和 Softmax 内核的 XO 文件。
9)生成 xclbin 文件:
- 使用 Vitis 工具生成 xclbin 文件(dpu.xclbin),并复制相关文件(HWH和BIT文件)。
2.3.3 vivado batch 模式
binary_container_1/dpu.xo: ${DPU_HDLSRCS}
@mkdir -p ${DIR_PRJ}/binary_container_1
-@$(RM) ${DIR_PRJ}/$@
cd ${DIR_PRJ} ;\
$(VIVADO) -mode batch -source scripts/gen_dpu_xo.tcl -notrace -tclargs $@ $(DPU_KERN_NAME) ${TARGET} ${BOARD}
1)目标文件和依赖
binary_container_1/dpu.xo: ${DPU_HDLSRCS}
- binary_container_1/dpu.xo,定义了一个规则的目标文件,这是想要生成的文件。
- ${DPU_HDLSRCS},是这个目标的依赖文件,它是一个变量,代表 DPU 源文件的集合。只有当这些依赖文件发生变化时,目标文件才会被重新构建。
2)binary_container_1
@mkdir -p ${DIR_PRJ}/binary_container_1
- 创建一个目录(如果它不存在的话)。mkdir -p 命令用于创建目录,-p 参数确保即使目录已经存在,也不会报错。${DIR_PRJ} 是一个变量,代表项目的目录。
3)删除已存在的目标文件
-@$(RM) ${DIR_PRJ}/$@
- 删除已存在的目标文件以准备新的构建。
- $(RM) 变量表示 rm -f,-@ 前缀的意思是即使命令失败也不会停止执行后续命令,并且 @ 会使得命令在执行时不显示在控制台。
- ${DIR_PRJ}/$@ 中的 $@ 是自动变量,代表规则的目标文件名。
4)执行 vivado 命令
cd ${DIR_PRJ} ;\
$(VIVADO) -mode batch -source scripts/gen_dpu_xo.tcl -notrace -tclargs $@ $(DPU_KERN_NAME) ${TARGET} ${BOARD}
- 首先切换到项目目录 ${DIR_PRJ}
- 执行 $(VIVADO) 命令,以批处理模式运行(-mode batch),并指定一个Tcl脚本(scripts/gen_dpu_xo.tcl)来生成目标文件。
- -notrace 参数表示在执行过程中不显示详细的调试信息。
- -tclargs 后面跟着的参数会传递给 Tcl 脚本,这里传递以下四个参数:
- $@,即目标文件名。
- ${DPU_KERN_NAME},DPU IP 核的名称。
- ${TARGET},即目标平台名称。
- ${BOARD},即板卡信息。
2.3.4 package_xo 命令
《Vitis 统一软件平台文档:应用加速开发(UG1393)》
1)gen_dpu_xo.tcl
if { $::argc != 4 } {
puts "ERROR: Program \"$::argv0\" requires 4 arguments!\n"
puts "Usage: $::argv0 <xoname> <krnl_name> <target> <device>\n"
exit
}
set xoname [lindex $::argv 0]
set krnl_name [lindex $::argv 1]
set target [lindex $::argv 2]
set device [lindex $::argv 3]
puts $xoname
set suffix "${krnl_name}_${target}_${device}"
if { [info exists ::env(DIR_PATH)] } {
source -notrace $env(DIR_PRJ)/scripts/package_dpu_kernel.tcl
} else {
source -notrace ./scripts/package_dpu_kernel.tcl
}
if {[file exists "${xoname}"]} {
file delete -force "${xoname}"
}
if { [info exists ::env(DIR_PATH)] } {
package_xo -xo_path ${xoname} -kernel_name ${krnl_name} -ip_directory ./packaged_kernel_${suffix} -kernel_xml $env(DIR_PRJ)/kernel_xml/dpu/kernel.xml
} else {
package_xo -xo_path ${xoname} -kernel_name ${krnl_name} -ip_directory ./packaged_kernel_${suffix} -kernel_xml ./kernel_xml/dpu/kernel.xml
}
- suffix 字符串,该字符串是 krnl_name、target 和 device 的组合。仅仅用于命名文件。
2)package_xo:
Package IP/Package XO Flow — Vitis™ Tutorials 2022.1 documentation
package_xo 命令来创建 vitis object ( .xo ) 文件。 package_xo 命令还将 IP 文件和 kernel.xml 文件打包到生成的 .xo 文件中。
package_xo \
-force \
-xo_path <path>/.../Vadd_A_B.xo \
-kernel_name Vadd_A_B \
-ip_directory <path>/.../IP
- package_xo:由 Vivado IP 创建 vitis object 文件 (.xo)。
- -force:覆盖现有的内核文件(如果存在)。
- -xo_path:xo 文件的路径和名称。
- -kernel_name:要创建的内核的名称,应与 RTL 模块名称匹配。
- -ip_directory:Vivado IP 的路径。
2.3.5 vitis v++ 命令
《Vitis 统一软件平台文档:应用加速开发(UG1393)》- Vitis 编译器命令
dpu.xclbin: $(kernel_xo) $(VITIS_PLATFORM)
cd ${DIR_PRJ} ;\
v++ $(XOCC_OPTS) -l --temp_dir binary_container_1 \
--log_dir binary_container_1/logs --package.no_image \
--remote_ip_cache binary_container_1/ip_cache -o ${DIR_PRJ}/binary_container_1/$@ $<
cp -f ${DIR_PRJ}/binary_container_1/link/vivado/vpl/prj/prj.gen/sources_1/bd/*/hw_handoff/*.hwh ${DIR_PRJ}/dpu.hwh
cp -f ${DIR_PRJ}/binary_container_1/link/vivado/vpl/prj/prj.runs/impl_1/*.bit ${DIR_PRJ}/dpu.bit
cp -f ${DIR_PRJ}/binary_container_1/$@ ${DIR_PRJ}/dpu.xclbin
1)主要功能
- 定义了一个目标 dpu.xclbin
- 依赖于两个文件:$(kernel_xo) 和 $(VITIS_PLATFORM),即 platform.xsa 文件
2)v++ 介绍
v++ $(XOCC_OPTS) \
-l \
--temp_dir binary_container_1 \
--log_dir binary_container_1/logs \
--package.no_image \
--remote_ip_cache binary_container_1/ip_cache \
-o ${DIR_PRJ}/binary_container_1/$@ $<
- --link (-l):用于将多个内核和单个目标硬件平台链接到赛灵思器件二进制文件 (xclbin) 中。
- --package.no_imag:绕过 SD 卡镜像创建操作。
2.4 DPU 参数
2.4.1 Arch 选项卡
- RAM Usage:在片上存储器中对权重、偏差和中间特征映射进行缓冲。高 RAM 用量表示片上存储器块将更大,使 DPUCZDX8G 能更灵活地 处理中间数据。
DPUCZDX8G 架构 | 低 RAM 用量 | 高 RAM 用量 |
B512 | 72 | 88 |
B1024 | 104 | 136 |
B4096 | 255 | 315 |
- Channel augmentation:一种在DPU中提高计算效率的技术,它可以将多个小通道的特征图合并成一个大通道的特征图,从而减少内存访问次数和数据传输量。
DPUCZDX8G 架构 | 搭配通道增广使用的额外 LUT |
B512 | 3121 |
B1024 | 3133 |
B4096 | 1701 |
- Alu:DepthwiseConv(逐通道卷积),它是深度可分离卷积(Depthwise separable convolution)的第一步。它的特点是每个输入通道只用一个卷积核进行卷积,不会混合不同的输入通道。
ALU并行 | LUT | FF | Block RAM | DSP |
1 | 44212 | 88250 | 255 | 662 |
2 | 46599 | 92380 | 255 | 678 |
4(推荐) | 51388 | 98525 | 255 | 710 |
8 | 60751 | 111329 | 255 | 774 |
2.4.2 Advanced 选项卡
- dpu_2x Clock Gating:dpu_2x 时钟门控,添加 2x_clk 的使能信号,用于降低 DPU 功耗。
- DSP Cascade:DSP 级联,用于设置 DSP48E slice(切片)级联链的最大长度。通常级联长度越长,使用的逻辑资源越少,但可能时序更糟。较短的级联长度可能不适合小型器件,因为小型器件需要更多硬件资源。建议在首次迭代中选择中间值(即 4),如果不满足时序,则调整该值。
- DSP Usage:是否使用 DSP48E slice 来执行累加。如果所选 DSP 用量较低,那么在卷积模块中,DPUCZDX8G IP 会将 DSP slice 仅用于乘法。在高 DSP 用量模式下,DSP slice 将同时用于乘法和累加。因此,DSP 用量越高,耗用的 DSP slice 数量越多,耗用的 LUT 越少。
- 时间戳:启用该选项时,DPUCZDX8G 会记录 DPUCZDX8G 工程执行综合的时间。禁用该选项时,该时间戳会保持上次 IP 更新时的值不变。
- UltraRAM:每个Block RAM 都包含 2 个 18K slice,此 slice 可配置为 9b*4096、18b*2048 或36b*1024。UltraRAM 采用固定配置 72b*4096。DPUCZDX8G 中的存储器单元位宽为 ICP*8 位,深度为 2048。对于 B1024 架构,ICP 为 8,存储器单元位宽为 8*8 位。随后,每个存储器单元均可利用一个 UltraRAM 块来加以例化。当 ICP 大于 8 时,DPUCZDX8G 中的每个存储器单元都需要至少 2 个 UltraRAM 块。默认情况下,DPUCZDX8G 使用Block RAM 作为存储器单元。对于同时包含块 RAM 和 UltraRAM 的目标器件,请配置 UltraRAM 数量,以判定用于替换部分块 RAM 的 UltraRAM 数量。UltraRAM 数量应设置为 DPUCZDX8G 中每个存储器单元所需的 UltraRAM 数量的倍数。
2.4.3 参数汇总
You can modify the dpu_conf.vh file to change the DPU IP settings in the board folder. The adjustable settings of the DPU IP include:
- DPU model number
- URAM_ENABLE
- RAM_USAGE_LOW
- CHANNEL_AUGMENTATION_ENABLE
- DWCV_ENABLE
- POOL_AVG_ENABLE
- RELU_LEAKYRELU_RELU6
- DSP48_USAGE_HIGH
- LOWPOWER_ENABLE
- DEVICE Configuration
2.5 工程参数
2.5.1 prj_config 文件
1)prj_config 文件汇总路径:
<DPU-PYNQ-2.5.0>/boards/DPUCZDX8G/prj/Vitis/config_file
---
prj_config
prj_config_102_3dpu
prj_config_102_3dpu_LPD
prj_config_104_2dpu
prj_config_1dpu
prj_config_1dpu_l1s1
prj_config_1dpu_sfm
2)KV260_SOM 的 prj_config
[clock]
freqHz=200000000:DPUCZDX8G_1.aclk
freqHz=400000000:DPUCZDX8G_1.ap_clk_2
[connectivity]
sp=DPUCZDX8G_1.M_AXI_GP0:HPC0
sp=DPUCZDX8G_1.M_AXI_HP0:HP0
sp=DPUCZDX8G_1.M_AXI_HP2:HP1
nk=DPUCZDX8G:1
[advanced]
misc=:solution_name=link
#param=compiler.addOutputTypes=sd_card
#param=compiler.skipTimingCheckAndFrequencyScaling=1
[vivado]
prop=run.impl_1.strategy=Performance_Explore
#param=place.runPartPlacer=0
2.5.2 clock 选项
freqHz=200000000:DPUCZDX8G_1.aclk
freqHz=400000000:DPUCZDX8G_1.ap_clk_2
1)对于 ap_clk_2 和 aclk 这两个接口:
<dpu_ip>/Vitis/dpu/xdc/timing_clocks.xdc
---
[line 21]:set clk_1x [get_clocks -of_objects [get_ports aclk]]
[line 22]:set clk_2x [get_clocks -of_objects [get_ports ap_clk_2]]
<dpu_ip>/DPUCZDX8G_v4_0_0/ttcl/timing_clocks_xdc.ttcl
---
[line 31]:set clk_2x [get_clocks -of_objects [get_ports dpu_2x_clk ] ]
...
[line 37]:set clk_1x [get_clocks -of_objects [get_ports m_axi_dpu_aclk ] ]
2)v++ 命令中的 --clock 选项
《Vitis 统一软件平台文档:应用加速开发(UG1393)》- Vitis 编译器命令 - [--clock 选项]
如果不指定 --clock.XXX 选项,则会对每个 kernel 默认时钟。对于含 2 个时钟的内核,来自平台的时钟 ID 0 将分配给 ap_clk,时钟 ID 1 则分配给 ap_clk_2。
总结:在此配置中,s_axi_clk 并未被指定,那么将会使用平台默认时钟,即 200MHz,m_axi_dpu_aclk 被指定为 200MHz 这个时钟,所以:s_axi_clk 与 m_axi_dpu_aclk(DPUCZDX8G_1.aclk -> clk_1x) 将会共用 200MHz 这个时钟。
3)等价的 clock 连接方式
id=1:DPUCZDX8G_1.aclk
id=2:DPUCZDX8G_1.ap_clk_2
2.5.3 connectivity 选项
sp=DPUCZDX8G_1.M_AXI_GP0:HPC0
sp=DPUCZDX8G_1.M_AXI_HP0:HP0
sp=DPUCZDX8G_1.M_AXI_HP2:HP1nk=DPUCZDX8G:1
1)DPUCZDX8G 的 GP 接口
prj/Vivado/scripts/base/trd_bd.tcl:
---
[2633] M_AXI_GP [dict create "M_AXI_INSTR" {M_AXI_GP0}]
M_AXI_INSTR 是用于 DPUCZDX8G 指令提取的 32 位 AXI 存储器映射接口。
2)DPUCZDX8G 的 HP 接口
DPUCZDX8G 的两个 M_AXI 接口:
- M_AXI_HP0 -> DPUx_M_AXI_DATA0
- M_AXI_HP2 -> DPUx_M_AXI_DATA1
中间没有 M_AXI_HP1,不知为什么,从以下文件中能观察到 M_AXI 对应 DPU 内存访问空间。
prj/Vitis/kernel_xml/dpu/kernel.xml
---
[16]: <arg name="dpu_base0_addr" ... id="5" port="M_AXI_HP0" size="0x8" offset="0x60" ...>
[17]: <arg name="dpu_base1_addr" ... id="6" port="M_AXI_HP0" size="0x8" offset="0x68" ...>
[18]: <arg name="dpu_base2_addr" ... id="7" port="M_AXI_HP0" size="0x8" offset="0x70" ...>
[19]: <arg name="dpu_base3_addr" ... id="8" port="M_AXI_HP0" size="0x8" offset="0x78" ...>
[20]: <arg name="dpu_base4_addr" ... id="9" port="M_AXI_HP2" size="0x8" offset="0x80" ...>
[21]: <arg name="dpu_base5_addr" ... id="10" port="M_AXI_HP2" size="0x8" offset="0x88" ...>
[22]: <arg name="dpu_base6_addr" ... id="11" port="M_AXI_HP2" size="0x8" offset="0x90" ...>
[23]: <arg name="dpu_base7_addr" ... id="12" port="M_AXI_HP2" size="0x8" offset="0x98" ...>
3)Number of Kernel
nk,即 Number of Kernel,该配置包含一个 DPUCZDX8G。
4)--connectivity 选项
《Vitis 统一软件平台文档:应用加速开发(UG1393)》- Vitis 编译器命令 - [--connectivity 选项]
2.5.4 advanced 选项
misc=:solution_name=link
advanced 选项包含三个子项,本配置只包含一个 misc 用于指定 solution_name。
- misc
- param
- prop
2.5.5 vivado 选项
用于配置 Vivado 工具,以对器件二进制文件 (.xclbin) 执行综合与实现。例如:
- 指定要生成的作业数量
- impl run 的 LSF 命令
- 要使用的特定实现策略
- 还可配置最优化、布局、时序
- 指定要输出的报告
2.6 在 PYNQ 中使用
执行 make BOARD=kv260_som 得到如下三个文件:
- dpu.bit
- dpu.hwh
- dpu.xclbin
拷贝至 jupyter 中,即可使用。
3. 创建 vivado platform
3.1 创建 Vivado 工程
3.1.1 platform 属性
勾选 Project is an extensible Vitis platform
或者在工程创建后,通过 Settings 修改:
3.1.2 创建 Block Design
- All Automation
- Zynq_ultra_ps_e_0
- Apply Board Presets
添加一个风扇控制,kv260 的专用风扇控制 IO:
I/O Configuration -> Low Speed -> Processing Unit -> TTC -> TTC0 -> Waveout: EMIO
该引脚绑定为:
set_property PACKAGE_PIN A12 [get_ports {fan_en_b[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {fan_en_b[0]}]
Slice 配置:
最后效果:
3.1.3 配置时钟与复位
- V++ 链接器可以自动链接内核与平台之间的时钟信号。平台中可用的时钟信号通过 PFM.CLK 属性导出。
- 对于简单的设计,中断信号可以由处理器的 pl_clk 来源。限制是处理器最多有 4 个 pl_clk,且它们的相位没有对齐。
- 为了提供更多的中断信号,或提供相位对齐的时钟,可以使用 Clocking Wizard。
- clk_out1 设为 75 MHz
- clk_out2 设为 150 MHz
- clk_out3 设为 300 MHz
- clk_out4 设为 600 MHz
- 复位类型为低电平有效
使用 pl_clk 作为输入时钟,并生成整个逻辑设计所需的时钟。
使用 75 MHz 的时钟作为 axi_lite 控制总线时钟。
300 MHz 和 600 MHz 的时钟被保留用于数据处理单元(DPU)AXI 接口时钟和 DPU 核心时钟,在设计链接阶段使用。
需要为每个时钟创建复位信号,因为在设置时钟导出时需要这些信号。
添加 4 个 proc_sys_reset 模块,分别命名为:
- proc_rst_1
- proc_rst_2
- proc_rst_3
- proc_rst_4
3.1.4 添加中断控制器
V++ 链接器可以自动链接内核与平台之间的中断信号。
1)添加一个 AXI LPD Master 接口
- 使用 AXI HPM0 LPD 进行寄存器控制,它的数据位宽是 32 位。
- 如果 Master 接口超过 32 位,比如 AXI HPM0 FPD,则 AXI 互连或 SmartConnect 将使用 PL 逻辑进行 AXI 总线宽度转换。这将消耗逻辑资源并引入不必要的延迟。
- 在 Block Design 中禁用 AXI HPM0 FPD 和 AXI HPM1 FPD,可以防止它们被自动连接。
- 可以在平台设置中导出未使用的 AXI 接口,无论它是否在 Block Design 中可见。
2)添加一个 AXI Interrupt Controller
对稳定性来说,中断控制器和别的内核 IRQ 信号同步到一个时钟是最好的。但如果内核在不同的时钟下运行,不用担心异步 IRQ。中断控制器也可以很好地管理带有电平中断信号的异步 IRQ。
3.1.5 连接模块
- 使用 75 MHz 连接 M_AXI_HPM0_LPD 相关模块。
- M_AXI_HPM0_LPD 复位选择 interconnect_aresetn 信号。
3.1.6 platform 属性设置
Windows -> Platform Setup
1)时钟
注意:Platform 应该有且只有一个默认时钟。在 v++ 链接过程中,如果没有指定的链接配置,链接器将使用默认时钟来连接 IP 块。
2)中断
3)AXI Port
- Memport: Memory Port
- M_AXI_GP: General Purpose AXI Master
- SP Tag: Slave Port Tag
4)Platform Name
- Name: kv260_hardware_platform
- Board: kv260
- Vendor: xilinx
- Version: 0.0
3.1.7 Verification
注意:在验证过程中,Vivado 会报告 /axi_intc_0/intr 未连接的严重警告。请忽略此警告,因为 v++ 链接器会将内核中断信号链接到此浮动 intr 信号。
[PFM-48] Interrupt pin /xlconcat_0/In0 does not have an assigned id.
It is being assigned ID 1.
3.1.8 导出 Platform
1)Generate HDL Wrapper
2)Generate Output Product
3)Export Platform
注意:确保Utility Sources - utils_1 下没有dcp 文件,否则会导致脚本运行期间出错。
<...>/kv260_platform.srcs/utils_1/imports/synth_1/design_1_wrapper.dcp' does not exist
...
ERROR: [v++ 60-661] v++ link run 'run_link' failed
ERROR: [v++ 60-626] Kernel link failed to complete
ERROR: [v++ 60-703] Failed to finish linking
INFO: [v++ 60-1653] Closing dispatch client.
make: *** [Makefile:116: dpu.xclbin] Error 1
3.2 执行自定义 Makefile
3.2.1 拷贝 XSA
将上一步导出的 XSA 文件放到自定义的板卡目录下,如 kv260_my
3.2.2 修改 Makefile
DPU_TRD:
wget -O DPUCZDX8G.tar.gz https://www.xilinx.com/bin/public/openDownload?filename=DPUCZDX8G.tar.gz && \
tar xf DPUCZDX8G.tar.gz && \
rm DPUCZDX8G.tar.gz
修改为:
DPU_TRD:
tar xf DPUCZDX8G.tar.gz
目的:避免每次下载 DPUCZDX8G.tar.gz,经常会无法建立连接。
3.2.3 修改 prj_config
[clock]
freqHz=300000000:DPUCZDX8G_1.aclk
freqHz=600000000:DPUCZDX8G_1.ap_clk_2
[connectivity]
sp=DPUCZDX8G_1.M_AXI_GP0:HPC0
sp=DPUCZDX8G_1.M_AXI_HP0:HP0
sp=DPUCZDX8G_1.M_AXI_HP2:HP1
nk=DPUCZDX8G:1
[advanced]
misc=:solution_name=link
[vivado]
prop=run.impl_1.strategy=Performance_Explore
impl.jobs=16
synth.jobs=16
新增了:
impl.jobs=16
synth.jobs=16
用于加快执行速度,需根据 CPU 和内存容量设定,否则会导致脚本执行失败。
3.2.4 执行脚本
make BOARD=kv260_my
执行完毕:
INFO: [v++ 60-1653] Closing dispatch client.
cp -f /home/fl/Documents/DPU-PYNQ-2.5.0/boards/kv260_my/binary_container_1/link/vivado/vpl/prj/prj.gen/sources_1/bd/*/hw_handoff/*.hwh \
/home/fl/Documents/DPU-PYNQ-2.5.0/boards/kv260_my/dpu.hwh
cp -f /home/fl/Documents/DPU-PYNQ-2.5.0/boards/kv260_my/binary_container_1/link/vivado/vpl/prj/prj.runs/impl_1/*.bit \
/home/fl/Documents/DPU-PYNQ-2.5.0/boards/kv260_my/dpu.bit
cp -f /home/fl/Documents/DPU-PYNQ-2.5.0/boards/kv260_my/binary_container_1/dpu.xclbin \
/home/fl/Documents/DPU-PYNQ-2.5.0/boards/kv260_my/dpu.xclbin
3.2.5 在 PYNQ 中使用
执行 make BOARD=kv260_som 得到如下三个文件:
- dpu.bit
- dpu.hwh
- dpu.xclbin
拷贝至 jupyter 中,即可使用。
3.3 关键语句
1)vivado 命令
<...>/vivado -mode batch \
-source scripts/gen_dpu_xo.tcl \
-notrace \
-tclargs binary_container_1/dpu.xo DPUCZDX8G hw kv260_my
- -mode batch:这个选项告诉Vivado以批处理模式运行,即不弹出图形用户界面,而是直接按照脚本执行命令。
- -source scripts/gen_dpu_xo.tcl:指定了要执行的Tcl脚本。
- -notrace:这个选项关闭了Vivado的跟踪功能,可以减少输出信息,提高执行效率。
- -tclargs 后面跟着的参数会传递给 Tcl 脚本。
2) v++ 命令
v++ -t hw
--platform <...>/boards/kv260_som/platform.xsa
--save-temps
--config <...>/boards/kv260_som/prj_config
--xp param:compiler.userPostSysLinkOverlayTcl=<...>/boards/DPUCZDX8G/prj/Vitis/syslink/strip_interconnects.tcl
-l
--temp_dir binary_container_1
--log_dir binary_container_1/logs
--package.no_image
--remote_ip_cache binary_container_1/ip_cache
-o <...>/boards/kv260_som/binary_container_1/dpu.xclbin
binary_container_1/dpu.xo
3)xclbinutil 命令
xclbinutil --add-section BITSTREAM:RAW:<...>/boards/kv260_som/binary_container_1/link/int/system.bit
--force
--target hw
--key-value SYS:dfx_enable:false
--add-section :JSON:<...>/boards/kv260_som/binary_container_1/link/int/dpu.rtd
--add-section CLOCK_FREQ_TOPOLOGY:JSON:<...>/boards/kv260_som/binary_container_1/link/int/dpu_xml.rtd
--add-section BUILD_METADATA:JSON:<...>/boards/kv260_som/binary_container_1/link/int/dpu_build.rtd
--add-section EMBEDDED_METADATA:RAW:<...>/boards/kv260_som/binary_container_1/link/int/dpu.xml
--add-section SYSTEM_METADATA:RAW:<...>/boards/kv260_som/binary_container_1/link/int/systemDiagramModelSlrBaseAddress.json
--key-value SYS:PlatformVBNV:xilinx_board_kv260_pfm_0_0
--output <...>/boards/kv260_som/binary_container_1/dpu.xclbin
xclbinutil --quiet
--force
--info <...>/boards/kv260_som/binary_container_1/dpu.xclbin.info
--input <...>/boards/kv260_som/binary_container_1/dpu.xclbin
4. 重要命令和概念解释
4.1 XO 文件
.xo 文件是 Xilinx 对象文件(Xilinx Object File),主要用于 Vitis 开发环境中。它包含了硬件加速内核的二进制表示,可以在 FPGA 上执行。以下是 .xo 文件的一些关键点:
- 生成:.xo 文件通常由 Vivado 或 Vitis HLS 生成,包含了硬件内核的实现。
- 用途:在 Vitis 中,.xo 文件被用作硬件加速内核,可以与其他内核和主机代码链接,生成最终的 FPGA 可执行文件(.xclbin)。
- 集成:您可以将 .xo 文件导入 Vitis 项目中,作为硬件加速部分的一部分进行编译和链接。
4.1.1 通过 HLS 导出 xo
1)在 Synthesis Settings 中,选择 Vitis Kernel Flow Target
// Read Data from Global Memory and write into Stream inStream
static void read_input(uint32_t* in, hls::stream<uint32_t>& inStream, int vSize) {
// Auto-pipeline is going to apply pipeline to this loop
mem_rd:
for (int i = 0; i < vSize; i++) {
#pragma HLS LOOP_TRIPCOUNT min = size max = size
// Blocking write command to inStream
inStream << in[i];
}
}
// Read Input data from inStream and write the result into outStream
static void compute_add(hls::stream<uint32_t>& inStream1,
hls::stream<uint32_t>& inStream2,
hls::stream<uint32_t>& outStream,
int vSize) {
// Auto-pipeline is going to apply pipeline to this loop
execute:
for (int i = 0; i < vSize; i++) {
#pragma HLS LOOP_TRIPCOUNT min = size max = size
// Blocking read command from inStream and Blocking write command
// to outStream
outStream << (inStream1.read() + inStream2.read());
}
}
// Read result from outStream and write the result to Global Memory
static void write_result(uint32_t* out, hls::stream<uint32_t>& outStream, int vSize) {
// Auto-pipeline is going to apply pipeline to this loop
mem_wr:
for (int i = 0; i < vSize; i++) {
#pragma HLS LOOP_TRIPCOUNT min = size max = size
// Blocking read command to inStream
out[i] = outStream.read();
}
}
extern "C" {
/*
Vector Addition Kernel Implementation using dataflow
Arguments:
in1 (input) --> Input Vector 1
in2 (input) --> Input Vector 2
out (output) --> Output Vector
vSize (input) --> Size of Vector in Integer
*/
void krnl_vadd(uint32_t* in1, uint32_t* in2, uint32_t* out, int vSize) {
static hls::stream<uint32_t> inStream1("input_stream_1");
static hls::stream<uint32_t> inStream2("input_stream_2");
static hls::stream<uint32_t> outStream("output_stream");
#pragma HLS INTERFACE m_axi port = in1 bundle = gmem0 depth = 4096
#pragma HLS INTERFACE m_axi port = in2 bundle = gmem1 depth = 4096
#pragma HLS INTERFACE m_axi port = out bundle = gmem0 depth = 4096
#pragma HLS dataflow
// dataflow pragma instruct compiler to run following three APIs in parallel
read_input(in1, inStream1, vSize);
read_input(in2, inStream2, vSize);
compute_add(inStream1, inStream2, outStream, vSize);
write_result(out, outStream, vSize);
}
}
2)完成 C Synthesis 后,导出 RTL
注意,如果选择 Vivado IP Flow Target 流程,则无 xo 选项。
4.2 xclbin 文件
xclbin container format(也称为 AXLF,Accelerated eXecutable and Linkable Format),由文件 xclbin.h 定义 。该文件使用 xclbin2 作为魔术字。 AXLF 是基于 section 的可扩展容器。不同 section 存储已编译应用程序的不同部分,例如 PL(FPGA)的比特流、AIE 块的 ELF 以及 Microblaze 等嵌入式处理器。它还包含结构良好的元数据,用于定义内存拓扑、实例化外设和计算内核的 IP 布局、每个计算内核的时钟详细信息和内核连接。
编译器为每个编译的应用程序生成带有 UUID 标记的唯一 xclbin 文件。每个 xclbin 还有一个另一个 UUID,用于定义其与 Shell 的兼容性。 Vitis 编译器、v++ 生成此文件作为链接阶段的一部分。最终用户通过 XRT xclLoadXclbin() API 加载此文件。 XRT 用户空间和内核空间组件通过对硬件进行编程并初始化 XRT 用户空间库和 XRT 内核驱动程序中的关键数据结构来消耗 xclbin 的不同部分。
xclbin.h 的路径是 XRT 安装目录中的 xrt/include/xclbin.h
struct axlf_section_header {
uint32_t m_sectionKind; /* Section type */
char m_sectionName[16]; /* Examples: "stage2", "clear1", "clear2", "ocl1", "ocl2, "ublaze", "sched" */
uint64_t m_sectionOffset; /* File offset of section data */
uint64_t m_sectionSize; /* Size of section data */
};
struct axlf {
char m_magic[8]; /* Should be "xclbin2\0" */
int32_t m_signature_length; /* Length of the signature. -1 indicates no signature */
unsigned char reserved[28]; /* Note: Initialized to 0xFFs */
unsigned char m_keyBlock[256]; /* Signature for validation of binary */
uint64_t m_uniqueId; /* axlf's uniqueId, use it to skip redownload etc */
struct axlf_header m_header; /* Inline header */
struct axlf_section_header m_sections[1]; /* One or more section headers follow */
};
XRT 提供了一个非常强大的实用程序 xclbinutil ,可用于读取/写入/更改 xclbins。
4.3 xclbinutil
xclbinutil可以创建、修改和报告 xclbin 内容信息。
通过 -info 选项可以获取各种信息,包括创建日期、硬件平台、时钟、内存配置、内核、工具生成选项等信息。请注意,可以选择指定输出文件。如果未指定,则输出将转到控制台。
5. 总结
本文演示相关步骤和代码示例,分享了在 PYNQ 框架下搭建自定义包含 DPU 的 overlay 的完整指南。
有以下难点并未完全理解,持续探索中:
- xclbin 文件的完整生成步骤。
- v++ 命令的详细用法。