• 正文
  • 相关推荐
申请入驻 产业图谱

PYNQ:不写一行 HDL,也能把 ZYNQ 的 PL 变成 Python 对象

2小时前
63
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

很多人把它当成教学框架,其实它是 AMD 官方的运行时。真正值钱的只有一个机制——overlay。

01为什么把它排在第一期

因为它是整条链路上投入产出比最高的一段。

绝大多数人第一次碰 ZYNQ,卡点完全一样:PL 里辛辛苦苦写的逻辑跑起来了,灯也闪了,但 PS 那边不知道该怎么把数据喂进去。你面前只有两条路——要么写裸机 C,翻 xparameters.h 找基地址、自己配中断;要么跑 Linux,写一个字符设备驱动,编译、insmod、创建设备节点。

两条路都要几个小时起步,而且第一次基本都会失败。失败的地方还特别隐蔽:地址对了但 cache 没刷、中断注册了但没使能、DMA 跑完了但你读的是旧 buffer。

PYNQ 把这一整段抹平了。一个 .bit 加一个 .hwh,Overlay() 一下,PL 里的 IP 就变成了 Python 对象,寄存器可以直接赋值,DMA 可以 transfer() 然后 wait()。

四条路的成本对比,很能说明问题:

方式 首次打通 后续改 IP 适合谁
裸机 C 半天到两天 重新生成 BSP、重编译 产品固件
字符设备驱动 两到三天 改驱动、重新编译内核模块 要进内核的正式方案
UIO 半天 改设备树、重载 轻量用户态驱动
PYNQ 十分钟 换个 .bit 就行 算法验证、快速迭代

注意最后一行不是"更快",是"改 IP 不用改软件"。这个性质才是它真正的价值。

02它到底是什么

REPOSITORY

https://github.com/Xilinx/PYNQ

拆开看是两层东西,很多人只知道第一层:

• 一个跑在 PS 上的 Linux 镜像,自带 Jupyter Notebook,浏览器打开就能写代码;

• 一个叫 pynq 的 Python 包,负责加载比特流、解析硬件信息、管理内存、封装中断。

关键在第二个文件:.hwh。它是 Vivado 导出比特流时一起生成的 XML,里面老老实实记着每个 IP 的基地址、寄存器偏移、位宽、中断号、甚至 IP 之间的连接关系。

PYNQ 在 Overlay() 的时候读这个文件,然后动态生成 Python 属性和方法。所以 ol.accel_0.register_map.CTRL = 1 这一行,背后是一次真实的 AXI-Lite 写事务——只不过地址是运行时从 XML 里查出来的。

这就是为什么你可以完全不写 C。硬件的"接口描述"被固化在比特流旁边,软件侧照着描述自动生成。

TIP

.hwh 是 Vivado 2019.2 之后的名字,更早的版本叫 .tcl。网上老教程里让你 Overlay("x.bit", "x.tcl") 的写法已经过时了,新版本只需要传 .bit,它自己去旁边找同名 .hwh。

03值得读的三处

▍pynq/overlay.py —— 整条链路的入口

看 Overlay.__init__ 和 _download()。它会先解析 hwh 建出 ip_dict(所有 IP 的名字、物理地址、地址区间、中断号),再按 IP 的 VLNV 类型去匹配驱动类。

匹配逻辑在 DefaultIP 和 _get_driver_class() 里。走通了这段你就明白:为什么有些 IP 能被识别成 DMA,有些只能当普通寄存器用——因为 PYNQ 内置了一张 VLNV 到驱动类的映射表,表里没有的,就退化成 DefaultIP。

这也解释了最常见的困惑:"为什么我的 IP 在 ip_dict 里,但没有 register_map?"——因为 register_map 依赖 hwh 里的寄存器描述,而只有走 AXI-Lite 且被 Vivado 正确标注的 IP 才有。

▍pynq/buffer.py —— 连续物理内存怎么分配

这是 90% 的人踩坑的地方。DMA 要的是物理连续内存,而 Linux 用户态拿到的是虚拟地址,页与页之间可能隔着十万八千里。

allocate() 走的是 xlnk 的替代方案:从内核的 CMA(连续内存分配器)池里拿物理连续页,再把物理地址映射到用户态。所以你在 PYNQ 里 allocate(shape=(4096,), dtype=np.uint32) 拿到的 buffer,buffer.physical_address 才是能写进 DMA 寄存器的那一个。

还有一个隐藏点:cache 一致性。ARM 的 L1/L2 是开启的,你往 buffer 写了数据,DMA 读到的可能是 cache 里的旧值。PYNQ 的 buffer 对象在 transfer() 前后会自动做 flush / invalidate,但这个"自动"只对通过它自己分配的 buffer 有效。你自己 numpy.zeros() 出来的数组传进去,它不会帮你刷——因为它压根不知道物理地址。

▍pynq/lib/ —— 一个"好用的 IP 封装"长什么样

里面是官方写好的 DMA、视频管线、RF 数据转换器、音频 codec 封装。想知道"如果我做的 IP 要给 20 个人用,接口该怎么设计",看这个目录比看任何规范都快。

04上手:十分钟跑通第一个 overlay

from pynq import Overlay, allocate
import numpy as np

ol = Overlay("accel.bit")
print(ol.ip_dict)          # 所有 IP 的名字/地址/中断

ol.accel_0.register_map.CTRL = 1   # 直接写寄存器

buf = allocate(shape=(4096,), dtype=np.uint32)
buf[:] = np.arange(4096)
ol.axi_dma_0.sendchannel.transfer(buf)
ol.axi_dma_0.sendchannel.wait()

四行核心代码对应四件事:加载、查表、配置、搬数据。

如果你手上还没有板子,PYNQ 官网提供了预编译镜像,Zynq-7000 系列(PYNQ-Z1/Z2、Zybo Z7)和 Zynq UltraScale+(KV260、ZCU104)都有。烧到 SD 卡,插上网线,浏览器输板子的 IP 就能进 Jupyter。

TIP

第一次用建议直接跑镜像里自带的 base overlay 的示例 notebook。它会带你走一遍 ip_dict 打印、寄存器读写、DMA 传输,比看文档快得多,而且能验证整条链路是通的。

05三个坑

一、.bit 和 .hwh 必须同名、同目录。少一个,Overlay 不会报错,但 ip_dict 是空的,然后你会以为是比特流没下进去,反复重启板子。
二、PYNQ 版本和 Vivado 版本是绑死的。PYNQ 2.7 对应 Vivado 2020.2 左右,3.0 之后要求 Vivado 2022.1 以上。你用一个 2023.1 导出的 hwh 去喂 2.7 的镜像,多半解析失败或者 IP 识别不出来。镜像文档里写了哪个版本,就用那个版本导出。
三、Xlnk 已经废弃。2020 年以前所有教程都在用 from pynq import Xlnk; xlnk = Xlnk(),现在请换成 pynq.allocate。搜到老代码先看它引的是哪个。

06它不适合谁

不适合做量产产品。

PYNQ 的镜像是几个 GB 的通用 Linux,启动要几十秒,内存占用不小,而且它把"通用性"放在了"确定性"前面——中断延迟、DMA 吞吐都不是硬实时级别。你要做的是工业设备、车载、医疗,最终还是要回到裸机或者精简的 Linux + 自写驱动。

但如果你在做算法验证、教学、原型演示、论文里的对比实验,PYNQ 能让你把"硬件对不对"和"软件怎么写"这两件事解耦开——先专心把 PL 调对,Python 那边随时能改。

07写在最后

PYNQ 不教你硬件设计,它只负责把"硬件已经做好了"之后的最后一百米铺平。

而对做算法验证的人来说,这一百米往往是最贵的。

…………… END ……………

相关推荐