前提与工具
开发板:PYNQ‑Z2(xc7z020clg484‑1),SD 卡镜像(PYNQ 官方 image 推荐使用与 Vivado/Vitis 版本匹配的镜像,例如 PYNQ v2.x/2.6/2.7;注意版本对应关系)。
主机:安装 Vivado + Vitis + Vitis HLS(版本建议一致,例如 2019.2、2020.1、2020.2、2021.1 等)。本文不依赖 Vitis AI(Vitis AI 多用于 MPSoC 和 Alveo,PYNQ‑Z2 可能不被 Vitis AI 支持)。
其它:Python(用于模型导出、测试)、串口终端、SSH、Jupyter(PYNQ)。
总体步骤概览(高层)
- 模型准备与量化/压缩(在 PC 端)。
- 设计算子与数据流(tile、buffer、dataflow 策略)。
- 用 Vitis HLS(或 Vivado HLS)实现并验证加速内核(C/C++)。
- 将 HLS 生成的 IP 集成到 Vivado Block Design(PS + AXI Interconnect + AXI DMA / BRAM 等)。
- 生成 bitstream、导出硬件描述(.xsa 或 .hwh/.bit)。
- 在 Vitis(或直接在主机上)创建应用或封装成 PYNQ overlay。
- 把 Overlay 部署到 PYNQ‑Z2,使用 Python/Jupyter 调用 DMA/寄存器完成推理。
- 性能分析与迭代优化(HLS pragma、tile、双缓冲、资源折中)。
第 1 部分 — 模型准备(PC)
- 目标:
- 把模型的权重和算子规格(输入维度、batch)确定好,并量化为合适的定点格式。
- 建议:
- 量化到定点(例如 int8 / ap_fixed<16, ? > / ap_fixed<8, ? >)以节省资源。对每层记录 scale/zero‑point(若用量化感知训练)。
- 将权重导出为二进制文件(raw float 或 quantized int)或 C header(const数组)以便 HLS 测试/打包。
- 若模型较大,考虑对权重做分块/分片或放 DDR(外存),关键权重能放 BRAM/Block RAM 则放 BRAM。
- 输出:
- weights.bin / weights.h / model config(每层维度、scale);
- 测试输入 / golden 输出(用于验证)。
第 2 部分 — 算法、内存与数据流设计(在纸上/脚本)
- 明确算子:在 PYNQ‑Z2 上通常实现 FC 层、GEMV/GEMM、ReLU、softmax(部分在 CPU 上)等。
- 决策项:
- 批处理 (batch) 大小:在延时敏感时用 batch=1;若追求吞吐用 batch >1 把 GEMV -> GEMM。
- 数据流:weight‑stationary(把权重放在本地 BRAM,流入 activation),input‑stationary,或 output‑stationary。FC 层常用 weight‑stationary(如果权重能常驻片上)。
- 切块(tiling)策略:按行/列切块使块尺寸适配 BRAM buffers 和计算单元数量。
- 并行度:定义 PE 数(并行乘加单元),决定资源与吞吐的 tradeoff。
- 双缓冲设计:用于 overlap DMA transfer 与计算。
- 产出例:tile_M(输出块行数)、tile_N(输入块列数)、PE 数、buffer 大小(字节)。
第 3 部分 — Vitis HLS(或 Vivado HLS)实现内核
- 新建 HLS 工程(Vitis HLS)。开发语言 C/C++,使用 ap_int/ap_fixed 或 int8_t。
- 内核接口:
- 控制接口:s_axilite (control registers for start/arg)。
- 数据接口:AXI4‑Master (m_axi) 指向 DDR(weights/input/output),或 AXI‑Stream 用于与 AXI DMA 互连。
- 常用 pragma:
- pragma HLS INTERFACE m_axi port=…, bundle=gmem0 offset=slave
- pragma HLS INTERFACE s_axilite port=… bundle=control
- pragma HLS PIPELINE II=1
- pragma HLS UNROLL factor=…
- pragma HLS ARRAY_PARTITION variable=… complete dim=1
- pragma HLS DATAFLOW // 若使用子函数流水并行
- 示例(伪代码):
kernel signature: void fc_accel( const int *in, const int *weights, int *out, int M, int N )
实现 tiled loop,内部使用 pragma PIPELINE 和 UNROLL。 - 仿真:
在 HLS 中写 testbench,做 C simulation, C/RTL co‑simulation 验证功能。 - Synthesis:
运行综合,观察性能估算(latency, II)、资源利用(LUT/DSP/BRAM)。
调整 pragma、数据布局(array_partition)以满足频率/资源需求。 - 导出 IP:
HLS 生成 IP(IP Packager)— 生成可在 Vivado 中直接使用的 AXI4 IP(包含 RTL wrapper)。
第 4 部分 — Vivado 中集成 IP(Block Design)
- 新建 Vivado 工程,target part 设为 xc7z020clg484‑1。
- 新建 IP Integrator block design:
- 插入 ZYNQ7 Processing System(PS7),运行 block automation,启用必要的 AXI GP master(AXI HP 在 7x 是不同,Zynq‑7000 有 2GP master ports)。通常启用 GP0 外挂到 PL 用于 AXI总线。
- 配置 DDR MIO(PS DDR)以便 DMA/AXI master 可以访问 DDR。
- 插入 HLS 生成的 IP:
- 如果 HLS IP 使用 AXI4‑Lite control + AXI4 master interfaces,连接其 s_axi to S_AXI interconnect(AXI lite)用于控制,m_axi 通过 AXI interconnect 连接到 DDR。
- 插入 AXI DMA(可选但常用):
- 常见方案:把输入/输出通过 AXI DMA 发送到 PL 的 AXI‑Stream 接口(如果 HLS IP 支持 streaming)。AXI DMA 负责 DDR <-> PL 数据搬运。
- 若 HLS 内核直接做 m_axi 访问 DDR(memory mapped),AXI DMA 可以省略,但 DMA 能更方便做双缓冲与同步。
- BRAM/Block RAM:
- 若权重能放在 BRAM,可添加 BRAM controller、BRAM block 连接到 IP,降低 DDR 带宽。
- 时钟、复位与中断:
- 连接 clk、rst,若 IP 发中断连接到 PS(注意在 device tree 中启用中断)。
- Validate design(Validate Design),生成 BD 的 wrapper。
- Constraints:设置主时钟频率(PL 部分,例如 100MHz),生成 XDC(时序约束)。
- Implementation & Bitstream:
- Launch Synthesis -> Implementation -> Generate Bitstream(可能耗时很久)。若资源超限或时序失败需回到 HLS/BD 做调整。
- 导出硬件与生成配套文件
- 在 Vivado 中:File -> Export -> Export Hardware,勾选”Include bitstream”。生成 .xsa(或 .hdf/.hwh)文件供 Vitis 和 PYNQ 使用。
- 为 PYNQ:同时生成 .hwh(Hardware HWH 文件)或使用 Vitis 的 XSA。PYNQ overlay 加载通常需要 .bit 和 .hwh(或 .xsa 转换)以便 pynq.Overlay 识别 IP。
第 5 部分 — 在 Vitis 中创建软件应用(可选:Baremetal 或 Linux)
如果打算在 PS 的 Linux(PYNQ)上跑用户态程序,通常不需要在 Vitis 里做完整 Linux app 编译,但如果想做 baremetal 或驱动测试,可以在 Vitis 创建 Platform Project:
- 新建 Platform,导入导出的 XSA。
- 新建 Application Project(BSP + Application)。选择 Linux or standalone。
- 编写用于配置/控制 IP 的示例代码(例如 使用 Xil DMA 驱动或直接寄存器访问)。
- 生成 ELF 用于 baremetal 或 Linux kernel module 测试(若需要)。
第 6 部分 — 打包成 PYNQ Overlay(推荐用于 PYNQ)
- PYNQ Overlay 需要 bit + hwh + 一个 Python driver(class)来封装 IP(便于 Jupyter 使用)。
- 在 Vivado 中确保导出 .hwh(或 HDF 然后使用 xsct 转换):
- 在 Vivado TCL:write_hw_handoff .hwh
- 也可以使用 export_gui or File->Export Hardware -> Include bitstream -> HDF(旧流程)。
- Overlay 目录结构示例:
- /.bit
- /.hwh
- /overlay.py (Python driver,包装 DMA、寄存器读写)
- /README.md 或 notebooks/
- 在 Python driver 中利用 pynq 库:
from pynq import Overlay, allocate
ol = Overlay('/home/xilinx/overlay/.bit')
ip = ol. # pynq 根据 hwh 自动创建 ip 对象(例如 ip_dict)
# 使用 pynq.allocate() 申请连续内存 buffer,使用 ip.axi_dma.transfer(buf) 发起 DMA。
- 简单示例(伪代码):
inp_buf = allocate(shape=(N,), dtype=np.int8)
out_buf = allocate(shape=(M,), dtype=np.int32)
dma = ol.axi_dma_0 # name 取决于 HWH 的名字
dma.sendchannel.transfer(inp_buf)
dma.recvchannel.transfer(out_buf)
dma.sendchannel.wait()
dma.recvchannel.wait()
第 7 部分 — 在 PYNQ‑Z2 上部署与运行
- 刷写 PYNQ 镜像到 SD 卡,启动板子(串口查看),确保网络/SSH 可用。
- 将 overlay 目录拷贝到板上(scp)。
- 在 PYNQ 上运行:
from pynq import Overlay
ol = Overlay('/home/xilinx/…/.bit')
ol.download() # 或直接 ol = Overlay(…) 会加载 bit。
# 调用 overlay 的 Python driver,分配 buffers,发 DMA,等待完成,读取结果。
- 验证输出与 PC 上 golden output 相符(考虑量化误差)。
第 8 部分 — 性能分析与优化循环
- 性能测量:
- 在 PYNQ 上测量单次推理延时、吞吐(samples/s),利用 timer(Python time.perf_counter 或 PS 测试)。
- 使用 Vivado HLS 估算与实际 resource 使用对比,查看 BRAM / DSP 是否满足。
- 在 Vivado implement report 查看时序是否违例(Slack)。
- 常见优化手段:
- HLS 优化:降低 II,增加 UNROLL,ARRAY_PARTITION,将热点数组完全切分(trade resource)。
- 增加 PE 数、使用 DSPs:若资源允许可提高并行度。
- 减少外存带宽:把权重放在 BRAM/BRAM+BRAM Controller 或使用 weight‑stationary;采用 quantization 减少字节数。
- 双缓冲:在 HLS 或软件层面做 Ping‑Pong buffers 以 overlap DMA 与计算。
- 调整 tile 尺寸以提高算重比(arithmetic intensity)。
- 查找瓶颈:
- 若 CPU 等待数据 -> 内存带宽或 DMA 有问题。
- 若 PL 计算单元未满载 -> 增大并行度或减少数据移动。
- 迭代:
- 回到 HLS 修改 pragma/算法或回到 BD 改变内存拓扑,重新生成 bitstream。