FPGA赛道突击(五)–使用xilinx vitis和vivado在pynq-z2开发板上部署神经网络加速器的全流程
FPGA赛道突击(五)–使用xilinx vitis和vivado在pynq-z2开发板上部署神经网络加速器的全流程

FPGA赛道突击(五)–使用xilinx vitis和vivado在pynq-z2开发板上部署神经网络加速器的全流程

前提与工具

开发板: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)。


总体步骤概览(高层)

  1. 模型准备与量化/压缩(在 PC 端)。
  2. 设计算子与数据流(tile、buffer、dataflow 策略)。
  3. 用 Vitis HLS(或 Vivado HLS)实现并验证加速内核(C/C++)。
  4. 将 HLS 生成的 IP 集成到 Vivado Block Design(PS + AXI Interconnect + AXI DMA / BRAM 等)。
  5. 生成 bitstream、导出硬件描述(.xsa 或 .hwh/.bit)。
  6. 在 Vitis(或直接在主机上)创建应用或封装成 PYNQ overlay。
  7. 把 Overlay 部署到 PYNQ‑Z2,使用 Python/Jupyter 调用 DMA/寄存器完成推理。
  8. 性能分析与迭代优化(HLS pragma、tile、双缓冲、资源折中)。

第 1 部分 — 模型准备(PC)

  1. 目标
    • 把模型的权重和算子规格(输入维度、batch)确定好,并量化为合适的定点格式。
  2. 建议
    • 量化到定点(例如 int8 / ap_fixed<16, ? > / ap_fixed<8, ? >)以节省资源。对每层记录 scale/zero‑point(若用量化感知训练)。
    • 将权重导出为二进制文件(raw float 或 quantized int)或 C header(const数组)以便 HLS 测试/打包。
    • 若模型较大,考虑对权重做分块/分片或放 DDR(外存),关键权重能放 BRAM/Block RAM 则放 BRAM。
  3. 输出
    • weights.bin / weights.h / model config(每层维度、scale);
    • 测试输入 / golden 输出(用于验证)。


第 2 部分 — 算法、内存与数据流设计(在纸上/脚本)

  1. 明确算子:在 PYNQ‑Z2 上通常实现 FC 层、GEMV/GEMM、ReLU、softmax(部分在 CPU 上)等。
  2. 决策项:
    • 批处理 (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 与计算。
  3. 产出例:tile_M(输出块行数)、tile_N(输入块列数)、PE 数、buffer 大小(字节)。


第 3 部分 — Vitis HLS(或 Vivado HLS)实现内核

  1. 新建 HLS 工程(Vitis HLS)。开发语言 C/C++,使用 ap_int/ap_fixed 或 int8_t。
  2. 内核接口:
    • 控制接口:s_axilite (control registers for start/arg)。
    • 数据接口:AXI4‑Master (m_axi) 指向 DDR(weights/input/output),或 AXI‑Stream 用于与 AXI DMA 互连。
  3. 常用 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 // 若使用子函数流水并行
  4. 示例(伪代码):
    kernel signature: void fc_accel( const int *in, const int *weights, int *out, int M, int N )
    实现 tiled loop,内部使用 pragma PIPELINE 和 UNROLL。
  5. 仿真:
    在 HLS 中写 testbench,做 C simulation, C/RTL co‑simulation 验证功能。
  6. Synthesis:
    运行综合,观察性能估算(latency, II)、资源利用(LUT/DSP/BRAM)。
    调整 pragma、数据布局(array_partition)以满足频率/资源需求。
  7. 导出 IP:
    HLS 生成 IP(IP Packager)— 生成可在 Vivado 中直接使用的 AXI4 IP(包含 RTL wrapper)。


第 4 部分 — Vivado 中集成 IP(Block Design)

  1. 新建 Vivado 工程,target part 设为 xc7z020clg484‑1。
  2. 新建 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。
  3. 插入 HLS 生成的 IP:
    • 如果 HLS IP 使用 AXI4‑Lite control + AXI4 master interfaces,连接其 s_axi to S_AXI interconnect(AXI lite)用于控制,m_axi 通过 AXI interconnect 连接到 DDR。
  4. 插入 AXI DMA(可选但常用):
    • 常见方案:把输入/输出通过 AXI DMA 发送到 PL 的 AXI‑Stream 接口(如果 HLS IP 支持 streaming)。AXI DMA 负责 DDR <-> PL 数据搬运。
    • 若 HLS 内核直接做 m_axi 访问 DDR(memory mapped),AXI DMA 可以省略,但 DMA 能更方便做双缓冲与同步。
  5. BRAM/Block RAM:
    • 若权重能放在 BRAM,可添加 BRAM controller、BRAM block 连接到 IP,降低 DDR 带宽。
  6. 时钟、复位与中断:
    • 连接 clk、rst,若 IP 发中断连接到 PS(注意在 device tree 中启用中断)。
  7. Validate design(Validate Design),生成 BD 的 wrapper。
  8. Constraints:设置主时钟频率(PL 部分,例如 100MHz),生成 XDC(时序约束)。
  9. Implementation & Bitstream:
    • Launch Synthesis -> Implementation -> Generate Bitstream(可能耗时很久)。若资源超限或时序失败需回到 HLS/BD 做调整。
  10. 导出硬件与生成配套文件
    • 在 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)

  1. PYNQ Overlay 需要 bit + hwh + 一个 Python driver(class)来封装 IP(便于 Jupyter 使用)。
  2. 在 Vivado 中确保导出 .hwh(或 HDF 然后使用 xsct 转换):
    • 在 Vivado TCL:write_hw_handoff .hwh
    • 也可以使用 export_gui or File->Export Hardware -> Include bitstream -> HDF(旧流程)。
  3. Overlay 目录结构示例:
    • /.bit
    • /.hwh
    • /overlay.py (Python driver,包装 DMA、寄存器读写)
    • /README.md 或 notebooks/
  4. 在 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。
  1. 简单示例(伪代码):
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 上部署与运行

  1. 刷写 PYNQ 镜像到 SD 卡,启动板子(串口查看),确保网络/SSH 可用。
  2. 将 overlay 目录拷贝到板上(scp)。
  3. 在 PYNQ 上运行:
from pynq import Overlay
ol = Overlay('/home/xilinx/…/.bit')
ol.download() # 或直接 ol = Overlay(…) 会加载 bit。
# 调用 overlay 的 Python driver,分配 buffers,发 DMA,等待完成,读取结果。
  1. 验证输出与 PC 上 golden output 相符(考虑量化误差)。

第 8 部分 — 性能分析与优化循环

  1. 性能测量:
    • 在 PYNQ 上测量单次推理延时、吞吐(samples/s),利用 timer(Python time.perf_counter 或 PS 测试)。
    • 使用 Vivado HLS 估算与实际 resource 使用对比,查看 BRAM / DSP 是否满足。
    • 在 Vivado implement report 查看时序是否违例(Slack)。
  2. 常见优化手段:
    • 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)。
  3. 查找瓶颈:
    • 若 CPU 等待数据 -> 内存带宽或 DMA 有问题。
    • 若 PL 计算单元未满载 -> 增大并行度或减少数据移动。
  4. 迭代:
    • 回到 HLS 修改 pragma/算法或回到 BD 改变内存拓扑,重新生成 bitstream。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注