← ブログ一覧へ
FPGAZynqPYNQPythonJupyterOverlayAXI DMAPL控制

Zynq 实战 16|PYNQ 框架:Jupyter 里 Python 控 PL

この記事は中国語で書かれ、Google 翻訳で自動翻訳されています。
中国語の原文を見る →

Zynq 实战 16|PYNQ 框架:Jupyter 里 Python 控 PL

这是《Zynq FPGA 嵌入式系统设计实战》系列的第 16 篇。 板子:Pynq-Z2(XC7Z020)。工具链:Vivado / Vitis / PetaLinux 2023.2。 上一篇:《Zynq 实战 15|Vitis HLS:用 C 写硬件 IP 的正确姿势》


0. 这一篇要解决什么问题

第 06 篇做了 PWM IP,第 12 篇做了 AXI DMA,之前的驱动都是 C 或 PetaLinux 内核驱动。这一篇换个思路:用 PYNQ 框架,在 Jupyter Notebook 里用 Python 直接操控 PL

PYNQ 的核心卖点:把 FPGA 配置(bitstream)和 Python API 打包成一个 Overlay,用 3 行代码加载 PL,再用 mmio.write(offset, value) 写寄存器。对于快速原型、教学演示、算法验证场景,比写 Linux 驱动快 10 倍。

本篇覆盖:

  • PYNQ v3.0 SD 卡镜像烧录、WiFi 连接、Jupyter 访问
  • Overlay 三件套(.bit + .hwh + .tcl)是什么、从哪来
  • 用 PYNQ API 控制第 06 篇 PWM IP + 第 12 篇 AXI DMA
  • 自己从 Vivado 工程生成 Overlay(不依赖官方预装的 overlay)
  • PYNQ vs 原生 PetaLinux:什么时候该用哪个

本篇不覆盖 PYNQ 的 RFSoC 扩展和 Composable Pipeline——那是 UltraScale+ 的内容。


1. PYNQ v3.0 环境搭建

1.1 SD 卡镜像下载与烧录

PYNQ v3.0 是目前(2023 年底)Pynq-Z2 的最新稳定版,基于 Ubuntu 22.04 LTS,Python 3.10。

下载

https://github.com/Xilinx/PYNQ/releases/tag/v3.0.0
→ pynq_z2_v3.0.0.img.zip(约 3.4 GB)

🚧 避坑:PYNQ 官网(pynq.io)的下载链接有时会 404 或者重定向到旧版本。直接去 GitHub Releases 页面下载最可靠。另外,v3.0.0 和 v2.x 的 API 有破坏性变更——from pynq import Overlay 的方式没变,但 allocate() 的行为和 DMA API 有调整。本篇全部基于 v3.0.0。

烧录(macOS/Linux):

# 解压
unzip pynq_z2_v3.0.0.img.zip

# 找到 SD 卡设备(macOS 示例)
diskutil list
# 假设 SD 卡是 /dev/disk4

# 卸载(不是 eject)
diskutil unmountDisk /dev/disk4

# 烧录(约 10 分钟)
sudo dd if=pynq_z2_v3.0.0.img of=/dev/rdisk4 bs=4m status=progress

# 烧录完毕后 eject
diskutil eject /dev/disk4

Windows 用 Balena Etcher,流程一样,选镜像文件和目标设备。

1.2 首次启动与网络

Pynq-Z2 的启动模式拨码开关(JP4):

  • SD 卡启动:SD(靠近 HDMI 那侧拨到 SD 位置)

用网线把 Pynq-Z2 的 ETH0 接到电脑或路由器,启动后约 60 秒:

# 固定 IP(网线直连电脑时)
http://192.168.2.99

# DHCP(接路由器时)
# 查 IP:路由器管理界面,或 nmap -sn 192.168.1.0/24 | grep pynq

默认密码:xilinx

WiFi 配置(可选):

# SSH 登录
ssh xilinx@192.168.2.99

# 配置 WiFi
nmcli dev wifi connect "你的WiFi名" password "密码"
ip addr show wlan0
# 之后可以拔掉网线用 WiFi

1.3 Jupyter Notebook 访问

浏览器打开 http://192.168.2.99,输入密码 xilinx,进入 JupyterLab 界面。

默认目录结构:

/home/xilinx/jupyter_notebooks/
├── base/               ← 官方示例(GPIO、LED、DMA)
├── common/             ← 共享工具
└── pynq/               ← 你的工程目录

2. Overlay 概念:三件套是什么

PYNQ Overlay 三件套 → FPGA 配置 + Python API design.bit FPGA Bitstream PL 配置文件 Vivado → Generate Bitstream design.hwh Hardware Handoff IP 地址 / 参数 XML Export Hardware → .xsa 内含 design.tcl Block Design Tcl 脚本 可选,用于重建工程 File → Export → Block Design PYNQ Overlay 加载 ol = Overlay("design.bit") # .hwh 自动找同名文件 Python API 层 ol.pwm_ctrl_0 → MMIO | ol.axi_gpio_0 → AxiGPIO | ol.axi_dma_0 → DMA
图 1. PYNQ Overlay 三件套:bitstream 配置 PL,HWH 描述 IP 地址,PYNQ 自动映射到 Python 对象

三件套各自的作用

文件内容必须?来源
design.bitFPGA 配置数据(二进制)✅ 必须Vivado Generate Bitstream
design.hwhIP 实例名/地址/参数的 XML 描述✅ 必须(PYNQ v2.5+).xsa 解压出来
design.tclBlock Design 重建脚本可选Vivado File→Export→Block Design

PYNQ 如何用 HWHOverlay("design.bit") 加载时,PYNQ 解析同名的 design.hwh,自动发现里面所有 IP 实例(名字、类型、地址),然后为每个 IP 构造对应的 Python 对象:

  • AXI GPIO → pynq.lib.AxiGPIO
  • AXI DMA → pynq.lib.dma.DMA
  • 未知 IP → pynq.MMIO(直接寄存器读写)

3. 加载官方 Base Overlay

PYNQ 镜像里预装了 base overlay,包含 Pynq-Z2 所有外设的 PL 配置。先用它验证环境:

# Notebook Cell 1 — 加载 base overlay
from pynq import Overlay
from pynq.lib import AxiGPIO
import time

# 加载 PL 配置(约 1 秒)
ol = Overlay('/home/xilinx/pynq/overlays/base/base.bit')
print("Overlay 加载成功")
print(ol.ip_dict.keys())  # 查看所有 IP 实例名

输出(节选):

Overlay 加载成功
dict_keys(['axi_gpio_0', 'axi_gpio_1', 'axi_timer_0', 'axi_dma_0', ...])
# Notebook Cell 2 — 控制 4 个 LED(Pynq-Z2 板载)
# base overlay 里 LED 连接在 axi_gpio_0 的 channel 1

leds = ol.leds            # base overlay 里有预定义的快捷属性
# 等价写法:leds = AxiGPIO(ol.ip_dict['axi_gpio_0']).channel1

for i in range(4):
    leds[i].toggle()
    time.sleep(0.1)
    print(f"LED[{i}] 切换")

# 全亮
leds.write(0xf, 0xf)
time.sleep(0.5)
# 全灭
leds.write(0x0, 0xf)
print("LED 控制完成")

4. 用 MMIO 控制第 06 篇 PWM IP

如果你的 Vivado 工程里有第 06 篇的 PWM IP(基地址 0x43C00000),可以直接用 MMIO:

# Notebook Cell 3 — 用 MMIO 控制 PWM IP
# 不需要专属 Python class,直接读写寄存器

from pynq import MMIO

# PWM IP 寄存器映射(和第 06、11 篇一致)
PWM_BASE   = 0x43C00000
PWM_RANGE  = 0x10000    # 64KB

# 偏移定义
CTRL_OFF   = 0x00  # [0]=enable, [1]=reset
PERIOD_OFF = 0x04  # PWM 周期(100MHz 时钟周期数)
HIGH_OFF   = 0x08  # 高电平时长
STATUS_OFF = 0x0C  # [0]=busy, [1]=irq_pending

# 创建 MMIO 对象
pwm = MMIO(PWM_BASE, PWM_RANGE)

def pwm_set_duty(duty_pct, freq_hz=1000):
    """设置 PWM 占空比和频率
    
    Args:
        duty_pct: 占空比 0-100
        freq_hz:  频率(Hz),默认 1kHz
    """
    PL_CLK_HZ = 100_000_000  # FCLK_CLK0 = 100 MHz
    period = PL_CLK_HZ // freq_hz          # = 100000 for 1kHz
    high   = int(period * duty_pct / 100)
    
    pwm.write(CTRL_OFF, 0x00)    # 先停止(防止毛刺)
    pwm.write(PERIOD_OFF, period)
    pwm.write(HIGH_OFF, high)
    pwm.write(CTRL_OFF, 0x01)    # 启动
    
    print(f"PWM: {freq_hz}Hz, 占空比 {duty_pct}%, PERIOD={period}, HIGH={high}")

def pwm_read_status():
    ctrl   = pwm.read(CTRL_OFF)
    period = pwm.read(PERIOD_OFF)
    high   = pwm.read(HIGH_OFF)
    status = pwm.read(STATUS_OFF)
    duty   = high / period * 100 if period > 0 else 0
    print(f"CTRL=0x{ctrl:08x}  PERIOD={period}  HIGH={high}  STATUS=0x{status:08x}")
    print(f"实际占空比: {duty:.1f}%")
    return duty

# 测试:从 10% 增到 90%
print("=== PWM 占空比扫描 ===")
for d in [10, 25, 50, 75, 90]:
    pwm_set_duty(d)
    time.sleep(0.2)
    pwm_read_status()
    print()

# 停止
pwm.write(CTRL_OFF, 0x00)
print("PWM 已停止")

5. 完整 Notebook:LED 控制 + PWM 状态监控

# ============================================================
# zynq_pwm_led_demo.ipynb — Zynq 实战 16 演示 Notebook
# 依赖:PYNQ v3.0,自定义 overlay(含 PWM IP + AXI GPIO)
# ============================================================

# Cell 1: 导入 & 加载 overlay
# ─────────────────────────────────────────────────────────────
from pynq import Overlay, MMIO
from pynq.lib import AxiGPIO
import numpy as np
import matplotlib.pyplot as plt
import time, ipywidgets as widgets
from IPython.display import display

# 加载你自定义的 overlay(见第 7 节如何生成)
# 如果先用 base overlay 测试,改成 '/home/xilinx/pynq/overlays/base/base.bit'
ol = Overlay('/home/xilinx/pynq/overlays/pwm_demo/pwm_demo.bit')
print("✅ Overlay 已加载")
print(f"包含 IP: {list(ol.ip_dict.keys())}")

# Cell 2: 初始化外设
# ─────────────────────────────────────────────────────────────
# LED(AXI GPIO)
gpio = AxiGPIO(ol.ip_dict['axi_gpio_0'])
leds = gpio.channel1   # channel1 = output(LED)
btns = gpio.channel2   # channel2 = input(按钮)

# PWM IP(MMIO 直接访问)
PWM_BASE = 0x43C00000
pwm = MMIO(PWM_BASE, 0x10000)

print("✅ GPIO & PWM 初始化完成")

# Cell 3: 交互式 PWM 控制 Widget
# ─────────────────────────────────────────────────────────────
duty_slider = widgets.IntSlider(
    value=50, min=0, max=100, step=1,
    description='占空比%:', style={'description_width': 'initial'}
)
freq_dropdown = widgets.Dropdown(
    options=[('100 Hz', 100), ('1 kHz', 1000), ('10 kHz', 10000)],
    value=1000,
    description='频率:'
)
start_btn  = widgets.Button(description='启动 PWM', button_style='success')
stop_btn   = widgets.Button(description='停止 PWM',  button_style='danger')
status_out = widgets.Output()

def on_start(_):
    d = duty_slider.value
    f = freq_dropdown.value
    PL_CLK = 100_000_000
    period = PL_CLK // f
    high   = period * d // 100
    pwm.write(0x00, 0)          # 停止
    pwm.write(0x04, period)
    pwm.write(0x08, high)
    pwm.write(0x00, 1)          # 启动
    with status_out:
        status_out.clear_output(wait=True)
        print(f"▶ 运行中: {f}Hz, {d}% 占空比")
        print(f"  PERIOD={period}, HIGH={high}")
    # LED 用占空比编码(高 2 位 → 4 颗 LED)
    led_val = int(d / 25)  # 0,1,2,3,4 → 0~4 颗 LED 亮
    leds.write(min(led_val, 0xF), 0xF)

def on_stop(_):
    pwm.write(0x00, 0)
    leds.write(0x0, 0xF)
    with status_out:
        status_out.clear_output(wait=True)
        print("⏹ PWM 已停止")

start_btn.on_click(on_start)
stop_btn.on_click(on_stop)

display(widgets.VBox([
    widgets.HBox([duty_slider, freq_dropdown]),
    widgets.HBox([start_btn, stop_btn]),
    status_out
]))

# Cell 4: 读取按钮状态
# ─────────────────────────────────────────────────────────────
print("按钮状态(按 Pynq-Z2 的 BTN0-3):")
btn_val = btns.read()
for i in range(4):
    state = "按下" if (btn_val >> i) & 1 else "未按"
    print(f"  BTN{i}: {state}")

# Cell 5: 占空比扫描 + 实时绘图
# ─────────────────────────────────────────────────────────────
duties = list(range(0, 101, 5))
measured = []

for d in duties:
    pwm.write(0x00, 0)
    period = 100000
    high   = period * d // 100
    pwm.write(0x04, period)
    pwm.write(0x08, high)
    pwm.write(0x00, 1)
    time.sleep(0.05)
    # 回读验证
    rd_high = pwm.read(0x08)
    actual  = rd_high / period * 100
    measured.append(actual)

pwm.write(0x00, 0)

plt.figure(figsize=(8, 4))
plt.plot(duties, measured, 'o-', color='#3b82f6', label='实测占空比')
plt.plot(duties, duties,   '--', color='#94a3b8', label='理想值')
plt.xlabel('设定占空比 (%)')
plt.ylabel('实测占空比 (%)')
plt.title('PWM IP 占空比线性度(PYNQ MMIO 读回验证)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('/home/xilinx/pwm_linearity.png', dpi=150)
plt.show()
print("图表已保存")

6. 用 pynq.lib.dma 做 AXI DMA 数据搬运

接第 12 篇的 AXI DMA 工程(MM2S + S2MM 通道):

# Cell 6: AXI DMA 数据搬运演示
# ─────────────────────────────────────────────────────────────
# 依赖:overlay 里有 axi_dma_0,连接了 loopback(S2MM 接 MM2S)
# 或者接第 15 篇的 FIR IP

import pynq
from pynq import allocate
import numpy as np

# 获取 DMA 对象(PYNQ v3.0 API)
dma = ol.axi_dma_0

N = 1024  # 传输样本数

# allocate: 在 PS DDR 中分配 CMA(连续物理内存)buffer
# dtype=np.int16 对应 FIR IP 的 16-bit AXI Stream 数据
# PYNQ v3.0 里 allocate 返回 pynq.buffer.PynqBuffer,支持 numpy 操作

in_buf  = allocate(shape=(N,), dtype=np.int16, cacheable=False)
out_buf = allocate(shape=(N,), dtype=np.int16, cacheable=False)

# 生成测试数据:100Hz 正弦波(Q2.13 定点,8192 = 1.0)
FS = 48000
t  = np.arange(N) / FS
sig = 0.4 * np.sin(2 * np.pi * 100 * t) + 0.4 * np.sin(2 * np.pi * 20000 * t)
in_buf[:] = np.clip(sig * 8192, -8192, 8191).astype(np.int16)

print(f"输入 buffer: {N} 样本, 物理地址 = 0x{in_buf.physical_address:08x}")
print(f"输出 buffer: {N} 样本, 物理地址 = 0x{out_buf.physical_address:08x}")

# 启动 DMA 传输(S2MM 先 start,等待接收数据)
t_start = time.perf_counter()

# sendchannel: MM2S(PS DDR → PL)
# recvchannel: S2MM(PL → PS DDR)
dma.recvchannel.transfer(out_buf)
dma.sendchannel.transfer(in_buf)

# 等待两个通道都完成
dma.sendchannel.wait()
dma.recvchannel.wait()

t_end = time.perf_counter()
elapsed_ms = (t_end - t_start) * 1000

# 计算吞吐量
bytes_transferred = N * 2 * 2  # 双向,每样本 2 字节
throughput_mb = bytes_transferred / (t_end - t_start) / 1e6
print(f"\nDMA 传输完成")
print(f"  耗时: {elapsed_ms:.2f} ms")
print(f"  吞吐: {throughput_mb:.1f} MB/s(双向)")
print(f"  输出前 8 样本: {out_buf[:8]}")

# 如果接 FIR IP,验证滤波效果
input_rms  = np.sqrt(np.mean(in_buf.astype(float) ** 2))
output_rms = np.sqrt(np.mean(out_buf.astype(float) ** 2))
print(f"\n输入 RMS: {input_rms:.1f} LSB")
print(f"输出 RMS: {output_rms:.1f} LSB(低通滤波后 20kHz 分量被衰减)")

# 释放 CMA buffer
in_buf.freebuffer()
out_buf.freebuffer()
print("Buffer 已释放")

实测数字(PYNQ v3.0, Pynq-Z2, N=1024 int16):

指标数值
单次 DMA 传输(1024×2B MM2S+S2MM)1.3 ms
等效吞吐(双向)~3.1 MB/s
AXI HP 理论带宽1200 MB/s

🚧 避坑:PYNQ v3.0 的 allocate() 默认 cacheable=False,即 buffer 是非缓存映射(等价于第 11 篇讲的 pgprot_noncached)。不要设成 cacheable=True——那样 Python 侧的写操作会停在 CPU cache 里,DMA 从物理地址读到的是旧数据。这是 PYNQ 初学者最常见的数据不一致问题。


7. 从自己的 Vivado 工程生成 PYNQ Overlay

官方预装的 base overlay 功能固定。要控制你自己设计的 IP,需要生成自己的 overlay。

7.1 Vivado 工程准备

确保你的 Block Design 包含:

  • processing_system7_0(配置好 FCLK_CLK0 = 100 MHz)
  • 你的自定义 IP(比如第 06 篇 PWM IP + 第 15 篇 FIR IP)
  • AXI Interconnect / SmartConnect 连接好
  • 地址已在 Address Editor 分配

7.2 生成 Bitstream

Flow Navigator → Generate Bitstream(Vivado 2023.2)

完成后(约 5-10 分钟),bitstream 在:

<vivado-project>/<project-name>.runs/impl_1/<top_name>.bit

7.3 导出 Hardware(获取 .hwh)

Vivado → File → Export → Export Hardware
Include bitstream: ✅ 勾选
Output file: pwm_demo.xsa

.xsa 是一个 zip 包,里面包含 .hwh 文件:

# 解压 .xsa 获取 .hwh
cp pwm_demo.xsa /tmp/pwm_demo.zip
cd /tmp && unzip pwm_demo.zip
ls *.hwh
# → pwm_demo.hwh(或类似名字,看 Block Design 顶层名)

🚧 避坑.hwh 文件名必须和 .bit 文件名完全一致(除了后缀)。如果 .bitpwm_demo.bit,那 .hwh 必须叫 pwm_demo.hwh。PYNQ 在 Overlay("pwm_demo.bit") 时会自动查找同目录下的 pwm_demo.hwh,文件名不匹配则报错 FileNotFoundError: hwh file not found

7.4 传到板子并加载

# 在开发机上
scp pwm_demo.bit xilinx@192.168.2.99:/home/xilinx/pynq/overlays/pwm_demo/
scp pwm_demo.hwh xilinx@192.168.2.99:/home/xilinx/pynq/overlays/pwm_demo/
# Jupyter Notebook
from pynq import Overlay
ol = Overlay('/home/xilinx/pynq/overlays/pwm_demo/pwm_demo.bit')

# 查看 PYNQ 自动识别的 IP
print(ol.ip_dict)
# 输出类似:
# {'pwm_ctrl_0': {'phys_addr': 0x43c00000, 'addr_range': 65536, ...},
#  'fir5_filter_0': {'phys_addr': 0x43c10000, ...}}

# 直接访问自定义 IP(未知 IP 类型 → 自动用 MMIO 包装)
pwm = ol.pwm_ctrl_0
print(type(pwm))  # <class 'pynq.mmio.MMIO'>
pwm.write(0x04, 100000)  # 写 PERIOD 寄存器

7.5 为自定义 IP 写 Python Driver(可选)

PYNQ 允许你为自定义 IP 绑定 Python 类:

# pwm_driver.py — 放到 overlay 同目录下,或加入 PYTHONPATH
from pynq import DefaultIP

class PwmDriver(DefaultIP):
    """Pynq-Z2 自定义 PWM IP 的 Python 驱动"""
    
    bindto = ['kaiyo:hls:pwm_ctrl:1.0']  # 必须和 IP component.xml 里的 VLNV 一致
    
    PL_CLK_HZ = 100_000_000
    CTRL_OFF   = 0x00
    PERIOD_OFF = 0x04
    HIGH_OFF   = 0x08
    STATUS_OFF = 0x0C
    
    def __init__(self, description):
        super().__init__(description)
    
    def start(self):
        self.write(self.CTRL_OFF, 0x01)
    
    def stop(self):
        self.write(self.CTRL_OFF, 0x00)
    
    def set_duty(self, duty_pct, freq_hz=1000):
        """设置占空比(0-100)和频率(Hz)"""
        period = self.PL_CLK_HZ // freq_hz
        high   = period * duty_pct // 100
        self.stop()
        self.write(self.PERIOD_OFF, period)
        self.write(self.HIGH_OFF, high)
        self.start()
        return period, high
    
    @property
    def duty_pct(self):
        period = self.read(self.PERIOD_OFF)
        high   = self.read(self.HIGH_OFF)
        return high / period * 100 if period > 0 else 0
    
    @property
    def is_running(self):
        return bool(self.read(self.CTRL_OFF) & 0x01)

加载后,ol.pwm_ctrl_0 会自动变成 PwmDriver 实例:

from pwm_driver import PwmDriver  # 或加入 overlay 目录的 __init__.py
ol = Overlay('pwm_demo.bit')

pwm = ol.pwm_ctrl_0   # 现在是 PwmDriver 实例
pwm.set_duty(75, freq_hz=1000)
print(f"当前占空比: {pwm.duty_pct:.1f}%")
print(f"运行中: {pwm.is_running}")

8. PYNQ vs 原生 PetaLinux:取舍判断

这是一个值得认真回答的问题,不是 “PYNQ 更简单所以都用 PYNQ”。

PYNQ 的真实代价

项目PYNQ v3.0原生 PetaLinux 2023.2
镜像大小3.4 GB(SD 卡)~500 MB
启动时间~75 秒(Ubuntu 完整启动)~20 秒(精简 rootfs)
DDR 占用(空闲)~420 MB(Ubuntu + Jupyter)~80 MB
Python 调用 MMIO 延迟~15 μs/次(含 Python 解释开销)~1 μs(C 直接 mmap)
中断处理延迟不适合(Python GIL 问题)适合(内核驱动 ISR μs 级)

用 PYNQ 的场景

  • 算法快速原型:新 IP 验证,从 HLS 导出到 Python 控制只需 30 分钟
  • 教学演示:Jupyter Notebook 结合图表,展示效果直观
  • 数据科学 + FPGA:numpy/pandas 处理 DMA 数据,快速分析
  • 竞赛/黑客松:要在几小时内出演示,PYNQ 是正确选择

用原生 PetaLinux 的场景

  • 商业产品:发货的产品固件,不应该带 Jupyter 服务
  • 实时性要求:中断延迟 <10 μs,调度抖动敏感
  • 资源受限:DDR 只有 256 MB,装不下 PYNQ
  • 安全合规:Ubuntu 的 attack surface 太大,产品需要最小化 rootfs
  • 多核/AMP 场景:第 18 篇 OpenAMP,需要精确控制核心分配

🚧 避坑:有人把 PYNQ 直接用在量产产品里,然后遇到 Ubuntu 22.04 的安全更新破坏了 PYNQ 的 Python 包依赖,导致 Overlay 加载失败。PYNQ 的依赖链(Ubuntu + Python 包)更新频率远高于 Xilinx BSP,量产版本锁死依赖版本很麻烦。PYNQ 是开发工具,不是产品平台


9. 本篇你应该带走的判断

  • 能独立烧录 PYNQ v3.0 SD 卡,访问 Jupyter Notebook
  • 理解 Overlay 三件套(.bit/.hwh/.tcl)各自的作用
  • 知道 .hwh 从 Vivado Export Hardware 的 .xsa 解压得到
  • 能用 MMIO 读写自定义 IP 寄存器,用 AxiGPIO 控制 GPIO
  • 知道 allocate(cacheable=False) 和 DMA 一致性的关系
  • 能判断自己的项目该用 PYNQ 还是原生 PetaLinux

10. 下一篇预告

下一篇 《Zynq 实战 17|lwIP + 千兆以太网:一个能用的 HTTP Server》,我们会:

  • 在裸机(Vitis 无 OS)下用 lwIP 2.1.x 写一个 HTTP Server
  • 返回板子状态 JSON(温度、PWM 占空比、运行时间)
  • 测试 PS GEM0(千兆 PHY RTL8211E)的实际吞吐:940 Mbps RX / 880 Mbps TX
  • 踩透 PHY reset 时序、MIO 配置、jumbo frame 的坑

参考资料

文档号名称用途
PYNQ DocsPYNQ v3.0.0 DocumentationOverlay/MMIO/DMA API 完整参考
UG1144PetaLinux Tools Reference Guide 2023.2对比 PYNQ 和 PetaLinux 工作流
UG994Vivado Design Suite User Guide: Designing IP SubsystemsBlock Design → Export Hardware → .xsa 流程
PG021AXI GPIO Product GuideAxiGPIO IP 寄存器映射,与 PYNQ AxiGPIO API 对照
PG021AXI DMA Product Guide (PG021)DMA MM2S/S2MM 通道控制,对应 pynq.lib.dma
GitHubXilinx/PYNQ releasesv3.0.0 镜像下载,API 变更说明

这是《Zynq FPGA 嵌入式系统设计实战》系列第 16 篇。 如果你在 Overlay 加载失败(hwh 找不到)或 DMA 数据不一致上踩了坑,欢迎留言描述症状——尤其说明 PYNQ 版本和 Vivado 版本,这两个版本不匹配是最常见的根因。