【Vitis-AI】DPU-PYNQ自定义Overlay硬件设计 -> .bit .hwh .xclbin 【FPGA开发】
前景提要
【Vitis-AI】解析DPU-PYNQ
FPGA加速计算生态系统:从Vivado到Vitis AI的全面解析
https://github.com/Xilinx/DPU-PYNQ
makefile分析
https://github.com/Xilinx/DPU-PYNQ/blob/design_contest_3.5/boards/Makefile
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 clean check_env
all : check_env DPU_TRD dpu.xclbin
# If VITIS_PLATFORM not set by user, will default to expected file output
# of gen_platform.tcl in the board directory
VITIS_PLATFORM ?= ${DIR_PRJ}/platform.xsa
# If platform not provided, generate a platform using the gen_platform.tcl
# script.
${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
# The DPU IP HDL sources for MPSoC live in the DPU TRD flow, hosted on opendownloads
# more info: https://docs.xilinx.com/r/en-US/pg338-dpu/Vitis-DPU-TRD-Flow
DPU_TRD:
wget -O DPUCZDX8G.tar.gz https://www.xilinx.com/bin/public/openDownload?filename=DPUCZDX8G_VAI_v3.0.tar.gz && \
tar xf DPUCZDX8G.tar.gz && \
mv DPUCZDX8G_VAI_v3.0 DPUCZDX8G && \
rm DPUCZDX8G.tar.gz
# Optional parameters from the TRD flow
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_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_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
# Rules for copying the necessary scripts for building the DPU design
# Some paths are sed replaced to reflect our boards directory structure
${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 name must match kernel name in kernel.xml
DPU_KERN_NAME = DPUCZDX8G
SFM_KERN_NAME = sfm_xrt_top
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
# Added this rule so the sources get happily copied from the extracted TRD
# otherwise makefile will fail
${DPU_HDLSRCS}: ${DPU_TRD}
# Rule to build Vitis DPU kernel
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}
# Rule to generate the PYNQ overlay binaries, we only use the hardware platform
# software components get taken core of by the PYNQ image, so using the --package.no_image option
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
我们来详细分析这个 Makefile
的执行过程,以及它所依赖的工具和环境。
这个 Makefile
旨在自动化构建包含 DPU IP 的硬件设计,最终生成 PYNQ 可以使用的 overlay 文件 (.bit
, .hwh
) 以及 VART/XRT 需要的 .xclbin
文件。它遵循的是 Vitis 应用加速流程,而非纯粹的 Vivado 流程。
Make 过程详解:
当你在命令行运行 make BOARD=<Your_Board_Name>
时,make
工具会读取这个 Makefile
并执行以下主要步骤(按依赖关系):
-
check_env
目标:- 作用: 检查必要的环境变量和设置是否就绪。
- 动作:
- 打印用户指定的
BOARD
和 Vitis 平台路径 (VITIS_PLATFORM
)。 - 执行
bash check_env.sh
脚本。这个脚本的具体内容未给出,但通常会检查XILINX_VITIS
,XILINX_XRT
,XILINX_VIVADO
等环境变量是否设置,以及BOARD
是否有效,Xilinx Board Files 是否存在等。
- 打印用户指定的
- 依赖工具/环境:
bash
, 用户设置的BOARD
变量, 可能需要XILINX_*
环境变量(由check_env.sh
检查)。
-
DPU_TRD
目标:- 作用: 下载并解压 DPU 的硬件源代码 (TRD - Target Reference Design)。这是运行 DPU 所必需的 IP 核源文件。
- 动作:
- 使用
wget
从 Xilinx 网站下载 DPUCZDX8G TRD 的.tar.gz
压缩包。 - 使用
tar
解压压缩包。 - 使用
mv
将解压后的目录重命名为DPUCZDX8G
。 - 使用
rm
删除下载的压缩包。
- 使用
- 依赖工具/环境:
wget
(需要网络连接),tar
,mv
,rm
(标准的 Linux 命令)。
-
复制和修改脚本/配置文件 (由
.xo
文件的依赖触发):- 作用: 将 DPU TRD 中用于构建 Vitis 内核的 Tcl 脚本和
kernel.xml
文件复制到当前项目的工作目录 (${DIR_PRJ}
)下,并可能修改其中的路径以适应当前项目结构。 - 动作:
- 执行多个
mkdir
和cp
命令,将kernel.xml
,gen_dpu_xo.tcl
,gen_sfm_xo.tcl
,package_dpu_kernel.tcl
等文件从${DIR_TRD}/prj/Vitis/...
复制到${DIR_PRJ}/kernel_xml/
或${DIR_PRJ}/scripts/
。 - 对
package_dpu_kernel.tcl
和package_sfm_kernel.tcl
文件使用sed
命令,将其中的相对路径../../dpu_ip
修改为../DPUCZDX8G/dpu_ip
,以指向正确下载的 DPU TRD 源码位置。
- 执行多个
- 依赖工具/环境:
cp
,mkdir
,sed
。
- 作用: 将 DPU TRD 中用于构建 Vitis 内核的 Tcl 脚本和
-
${VITIS_PLATFORM}
目标 (例如${DIR_PRJ}/platform.xsa
):- 作用: 生成 Vitis 硬件平台文件 (
.xsa
)。这是 Vitis 流程的基础,定义了 Zynq PS 配置、时钟、可用 AXI 接口等。这是 Vitis 流程的第一步 (平台创建)。 - 动作:
cd ${DIR_PRJ}
: 进入特定板子的目录。vivado -mode batch -source gen_platform.tcl
: 启动 Vivado 工具,在批处理模式下运行该板子目录下的gen_platform.tcl
脚本。这个 Tcl 脚本负责创建 Vivado 工程、配置 Zynq PS、生成 Block Design(可能只包含 PS 和必要的接口),并最终导出.xsa
文件。
- 依赖工具/环境:
vivado
,gen_platform.tcl
脚本。需要XILINX_VIVADO
环境变量。
- 作用: 生成 Vitis 硬件平台文件 (
-
binary_container_1/dpu.xo
目标 (以及可能的softmax.xo
):- 作用: 将 DPU (和 Softmax) 的 RTL 源代码编译成 Vitis 内核对象 (
.xo
) 文件。这是 Vitis 流程的第二步 (内核编译)。 - 动作:
cd ${DIR_PRJ}
: 进入板子目录。$(VIVADO) -mode batch -source scripts/gen_dpu_xo.tcl ...
: 再次启动 Vivado 工具,运行之前复制过来的gen_dpu_xo.tcl
(或gen_sfm_xo.tcl
) 脚本。这个脚本使用 Vivado 的功能(可能是package_xo
Tcl 命令或类似机制)将 DPU 的 HDL 源代码 (${DPU_HDLSRCS}
) 打包编译成.xo
格式。注意,这里没有直接使用v++ -c
命令,而是通过 Vivado Tcl 脚本来完成内核打包。
- 依赖工具/环境:
vivado
,gen_dpu_xo.tcl
等脚本, DPU TRD 源代码文件 (${DPU_HDLSRCS}
). 需要XILINX_VIVADO
。
- 作用: 将 DPU (和 Softmax) 的 RTL 源代码编译成 Vitis 内核对象 (
-
dpu.xclbin
目标:- 作用: 将编译好的内核 (
.xo
文件) 链接 (link) 到硬件平台 (.xsa
),生成最终的.xclbin
文件。这是 Vitis 流程的第三步 (链接)。 - 动作:
cd ${DIR_PRJ}
: 进入板子目录。v++ $(XOCC_OPTS) -l ... -o ${DIR_PRJ}/binary_container_1/$@ $(kernel_xo)
: 启动v++
(Vitis 编译器/链接器) 工具。-l
: 指定为链接模式。$(XOCC_OPTS)
: 传递一系列选项,包括:-t hw
: 目标为硬件。--platform ${VITIS_PLATFORM}
: 指定之前生成的.xsa
平台文件。--config ${DIR_PRJ}/prj_config
: 指定链接时的配置文件。--save-temps
: 保留中间文件。--xp param:compiler.userPostSysLinkOverlayTcl=...
: 指定一个在系统链接后运行的 Tcl 脚本 (用于清理互联)。
--temp_dir
,--log_dir
,--remote_ip_cache
: 指定临时/日志/缓存目录。--package.no_image
: 重要! 告诉 Vitis 只生成硬件平台相关文件 (.xclbin
等),不要打包完整的 SD 卡镜像,因为 PYNQ 会提供自己的根文件系统。-o .../dpu.xclbin
: 指定输出的.xclbin
文件路径。$(kernel_xo)
: 指定要链接的内核对象文件列表 (例如binary_container_1/dpu.xo
)。
- 这个
v++ -l
命令内部会调用 Vivado 来执行布局布线,生成最终的比特流,并将比特流和所有必要的元数据打包进.xclbin
文件。
- 依赖工具/环境:
v++
,.xsa
平台文件,.xo
内核文件。需要XILINX_VITIS
和XILINX_XRT
环境变量。
- 作用: 将编译好的内核 (
-
复制最终产物 (在
dpu.xclbin
规则的末尾):- 作用: 将 Vitis 链接过程中生成的
.hwh
,.bit
, 和.xclbin
文件从 Vitis 的临时构建目录 (binary_container_1/...
) 复制到项目的主目录 (${DIR_PRJ}
) 并重命名为dpu.hwh
,dpu.bit
,dpu.xclbin
。这是为了方便 PYNQ 使用。 - 动作: 执行三个
cp
命令。 - 依赖工具/环境:
cp
。
- 作用: 将 Vitis 链接过程中生成的
总结:工具和环境
这个 Makefile 明确地使用了以下核心 Xilinx 工具:
- Vivado (
vivado
): 用于:- 生成 Vitis 硬件平台 (
.xsa
) (通过gen_platform.tcl
)。 - 将 DPU RTL 源代码编译/打包成 Vitis 内核对象 (
.xo
) (通过gen_dpu_xo.tcl
)。
- 生成 Vitis 硬件平台 (
- Vitis Compiler/Linker (
v++
): 用于:- 将内核 (
.xo
) 链接到平台 (.xsa
)。 - 生成最终的
.xclbin
文件 (内部包含比特流和元数据)。
- 将内核 (
必要的环境设置 (需要在运行 make
之前完成):
- 安装 Vitis: 需要完整的 Vitis 安装 (包括 Vivado)。
- 安装 XRT: Xilinx Runtime 是 Vitis 运行和构建的基础。
- 设置环境变量:
source <vitis-install-path>/Vitis/<version>/settings64.sh
: 设置 Vitis 和 Vivado 相关的环境变量 (包括XILINX_VITIS
,XILINX_VIVADO
, 并将工具添加到PATH
)。source /opt/xilinx/xrt/setup.sh
(或类似路径): 设置 XRT 相关的环境变量 (包括XILINX_XRT
, 并更新库路径)。
- Xilinx Board Files (XBF): 需要克隆或下载目标板的板级文件 (Makefile 提示从
XilinxBoardStore
获取)。 - 传递
BOARD
变量: 运行make
时必须指定BOARD
,例如make BOARD=zcu104
。 - 标准的 Linux 构建环境:
make
,bash
,wget
,tar
,cp
,mv
,rm
,mkdir
,sed
。 - 网络连接: 用于
wget
下载 DPU TRD。
这个 Makefile 完美地展示了如何使用 Vitis 流程来构建一个包含预定义 RTL 内核 (DPU) 的硬件设计,并最终生成 PYNQ 和 VART/XRT 所需的所有文件 (.bit
, .hwh
, .xclbin
)。
疑难解答
不能先用 Vivado 生成最终的 .bit
和 .hwh
文件,然后 再 独立地使用 v++
命令为 同一个设计 生成一个功能匹配的 .xclbin
文件。 你必须通过包含 Vitis 链接阶段的流程来生成 .xclbin
。
以下是详细解释:
-
Vivado 的角色和输出:
- Vivado 主要负责硬件设计(RTL、IP 集成)、综合、实现(布局布线)。
- 对于嵌入式系统(如 Zynq),Vivado 可以导出:
.bit
: FPGA 配置文件。.hwh
: 硬件描述文件,包含 IP 地址映射、中断等信息,主要供 PYNQ 和 Vitis 经典(SDK 风格)驱动生成使用。.xsa
: 硬件平台规范文件。这是关键!它定义了处理器配置、时钟、可用接口(AXI 等)、内存映射等,作为 Vitis 工具链的输入。
-
Vitis (
v++
) 的角色和输入/输出:- Vitis 设计用来构建加速应用程序,它工作在 Vivado 生成的平台 (
.xsa
) 之上。 v++ --compile
(v++ -c
): 将 HLS C++/RTL 等编译成内核对象文件 (.xo
)。v++ --link
(v++ -l
): 这是关键的链接阶段。它需要:- 平台文件 (
--platform <platform.xsa>
): 定义了基础硬件。 - 内核对象文件 (
<kernel1>.xo
,<kernel2>.xo
…): 要集成到平台中的加速器内核(比如你的 HLS IP,或者像 DPU 这样的预编译.xo
)。 - 可能的连接配置: 指定内核如何连接到平台的 AXI 接口、内存等。
- 平台文件 (
v++ --link
的输出是:.xclbin
: 这是 XRT 运行时需要的容器文件。它内部包含了最终的比特流 (.bit
),以及 XRT 管理内核所需的所有元数据(内核签名、内存连接拓扑、寄存器偏移、时钟信息等)。- 通常也会生成相关的报告文件。
- Vitis 设计用来构建加速应用程序,它工作在 Vivado 生成的平台 (
-
为什么不能“后添加”
.xclbin
信息?.xclbin
不是简单地给一个现成的.bit
文件加个“标签”。- Vitis 的
v++ --link
过程是一个集成和再实现的过程。它获取平台 (.xsa
) 和内核 (.xo
),然后决定如何将这些内核有效地放置在 FPGA 上并连接到平台接口。这个过程通常会重新运行部分或全部布局布线来生成最终的、包含平台和所有链接内核的比特流。 - Vivado 生成的
.bit
文件只包含那个特定 Vivado 项目的硬件实现。它不知道 Vitis 内核、XRT 运行时所需的特定元数据或潜在的链接时优化。 .hwh
文件虽然描述了硬件,但它缺少 XRT 需要的完整运行时上下文和元数据。
-
正确的流程是什么?
-
流程 A: Vitis 应用加速流程 (推荐用于 DPU + HLS IP)
- Vivado: 创建基础硬件平台。定义 PS 配置、时钟、可用 AXI 接口(比如连接 DPU 和 HLS IP 的接口)。导出
.xsa
文件。 (你可能也会在这个 Vivado 项目里放置一些“静态”的、非加速内核的 IP)。 - Vitis HLS (如果需要): 将你的 C++ 代码编译成
.xo
内核文件 (v++ -c
)。 - Vitis Link (
v++ -l
):- 使用 Vivado 导出的
.xsa
作为平台 (--platform
)。 - 提供你的 HLS 内核
.xo
文件。 - 提供 DPU 的
.xo
文件(通常由 Xilinx 提供)。 - 指定连接方式。
- 运行
v++ --link
命令。 - 输出得到
.xclbin
文件。 这个.xclbin
文件内部就包含了最终配置 FPGA 所需的比特流。
- 使用 Vivado 导出的
- 后续: 使用这个
.xclbin
文件配合 XRT/VART 运行你的应用程序。
- Vivado: 创建基础硬件平台。定义 PS 配置、时钟、可用 AXI 接口(比如连接 DPU 和 HLS IP 的接口)。导出
-
流程 B: 纯 Vivado -> PYNQ (不适合直接运行 DPU)
- Vivado: 完成包含 HLS IP 的完整硬件设计。生成
.bit
和.hwh
文件。 - PYNQ: 加载
.bit
/.hwh
,通过 PYNQ API 直接控制 HLS IP。
- Vivado: 完成包含 HLS IP 的完整硬件设计。生成
-
总结:
- 你不能将 Vivado 生成的
.bit
/.hwh
和 Vitis 生成的.xclbin
视为可以独立生成然后“混合”的东西。 - 要获得用于 XRT/VART(进而用于 DPU)的
.xclbin
文件,你必须使用 Vitis 工具链的链接步骤 (v++ --link
)。 - 这个链接步骤的输入是 Vivado 生成的平台文件 (
.xsa
) 和你的加速器内核 (.xo
文件)。 - 链接步骤的输出是
.xclbin
文件,它里面已经包含了最终的比特流以及所有 XRT 需要的元数据。