本記事の目的
PYNQ の基礎事項について、初学者を対象として、デバイスドライバ、AXI バス、DMA、Overlay、RFSoC との関係を踏まえて理解することを目指しています。
0. はじめに
Zynq / RFSoC を使った FPGA 開発では、
- PL(Programmable Logic = FPGA 部分)
- PS(Processing System = ARM CPU)
- Linux
- AXI バス
- DMA (Direct Memory Access ダイレクトメモリーアクセス)
- デバイスドライバ(decive driver 略してデバドラ)
- PYNQ
など、学部ではあまり触れない概念が一気に出てきます。特に PYNQ は、
「FPGA を Python から触れるようにした便利ツール」
とだけ理解されがちですが、その裏には
- なぜデバイスドライバを書かなくて済むのか?
- PS と PL のメモリ空間はどうつながっているのか?
- AXI バスはどこで出てくるのか?
といった コンピュータアーキテクチャの本質的な話 が隠れています。
この記事では、
- PYNQ はなぜ生まれたのか?
- PL のメモリ空間を PS からどうアクセスしているのか?
- Overlay は何をしているのか?
- カプセル化のメリット/デメリット
- DMA の使い方と抽象化
- メモリマップと AXI バス
- デバイスドライバは本当にもう不要なのか?
- 低レベル開発が必要になるのはどんなときか?
- RFSoC と PYNQ の関係、どう使い分けるべきか?
を 用語解説つきで整理します。
0.5 用語メモ(ざっくり)
まずざっくり主要な用語だけ先に一覧しておきます。
(本文ではもう少し丁寧に繰り返し説明します。)
- FPGA:Field Programmable Gate Array。論理回路を「あとから書き換えられる」LSI。
- SoC:System on Chip。CPU + メモリコントローラ + 周辺回路などを 1 チップに集約したもの。
- Zynq / RFSoC:ARM CPU(PS) + FPGA(PL)が 1 チップになった SoC。RFSoC はさらに ADC/DAC 付き。
- PS (Processing System):Zynq/RFSoC の中の ARM CPU や DDR コントローラなど、ソフトウェア側の世界。
- PL (Programmable Logic):同じチップ内の FPGA ロジック部分。HLS/Verilog/VHDL で作る側。
- AXI バス:AMBA AXI。ARM 社が定めた標準オンチップバス。メモリマップド/ストリーム両方あり。
- AXI4-Lite:レジスタ制御用の簡易 AXI バス(単発 read/write)。
- AXI4-Stream (AXIS):アドレス無しでデータを連続ストリームとして流すためのバス。
- DMA:Direct Memory Access。CPU を介さず、デバイス ↔ メモリ間でデータを転送する仕組み。
- Device Driver(デバイスドライバ):OS に「このハードはこう扱う」と教えるソフトウェア。
- PYNQ:Python + Zynq。FPGA アプリを Python から扱えるようにしたライブラリ+Linux 環境。
-
Overlay:PYNQ における FPGA デザイン+メタ情報のパッケージ。
.bit+.hwhを読み込む仕組み。 - UIO:Userspace I/O。Linux の「ユーザー空間からハードウェアに直接アクセスするための汎用ドライバ」仕組み。
- MMIO:Memory Mapped I/O。デバイスをメモリアドレス空間の一部としてアクセスする方式。
- Device Tree (DT):ARM/Linuxで使う“ハードウェア構造の説明書”のようなデータ。
- IOMMU:DMA 用の MMU。デバイスから見えるアドレスと物理メモリをマッピングする仕組み。
1. PYNQ とは何か?なぜ誕生したのか?
1.1 PYNQ = Python + Zynq
PYNQ は、Xilinx(現 AMD)が提供している
「Zynq / RFSoC を Python から使いやすくするための環境」
です。
- ベースは Linux(Ubuntu 系)
- Jupyter Notebook が標準で動く
- FPGA 用の bitstream をロードして、その上の IP を Python オブジェクトとして扱える
という構成になっています。
1.2 なぜ PYNQ が必要になったのか?
従来、Zynq / RFSoC を使って PS と PL を連携させるには:
- Vivado で PL(FPGA 回路)を設計
- Zynq PS との接続(AXI バス)を設定
- ハードウェアを export
- Linux デバイスドライバを書く
-
/dev/mydeviceを作り、ioctl / read / write を実装 - DMA 設定、キャッシュ制御、割り込み処理…
と、FPGA なのかカーネルハッカーなのか分からなくなるレベルで色々やる必要がありました。
「FPGA のアルゴリズムを試したいだけなのに、OS とドライバの世界を一通り学ばないといけない」
という状態を解消したくて生まれたのが PYNQ 、と考えると良いでしょう。
用語メモ:Zynq / RFSoC / PS / PL
-
Zynq
ARM Cortex-A9 (Zynq-7000) や Cortex-A53 (Zynq UltraScale+) と FPGA が 1 チップになった SoC。 -
RFSoC
Zynq UltraScale+ に RF ADC/DAC(数 GHz 帯域)まで載せた最先端の SoC。 -
PS (Processing System)
ARM CPU 側。Linux や Bare-metal C が動く側。 -
PL (Programmable Logic)
FPGA ロジック側。HLS / Verilog / VHDL で設計する部分。
2. Overlay は何をしているのか?
PYNQ を使うとき、こんなコードを書きます:
from pynq import Overlay
ol = Overlay("design.bit")
この Overlay は 何をしているのか?
2.1 bitstream をロードする
まず、.bit ファイル(FPGA のコンフィグ情報)を FPGA(PL)に書き込みます。
これによって PL の LUT / 配線 / DSP / BRAM 等がそのデザインに従って構成されます。
ここは「普通の FPGA 開発」と同じ。
2.2 .hwh を読み取り、ハードウェア構造を解析する
design.bit と同じ名前の .hwh ファイル(例:design.hwh)が存在すると、PYNQ はそれも読みます。
.hwh には:
- どんな IP が Block Design (BD) にあるか
- 各 AXI-Lite インターフェースの base address / range
- IP のレジスタ名とオフセット(例:
phase_incが offset 0x10 など)
といった Vivado Block Design の情報 が XML 形式で書かれています。
PYNQ はこれをパースして:
ol.ip_dict # IP 名とアドレス情報
ol.ddc_axi_0 # HLS IP への Python オブジェクト
ol.ddc_axi_0.register_map.phase_inc
のような 高レベル API を自動で生成します。
2.3 Overlay の役割まとめ
- FPGA を bitstream で構成する(PL の中身を決める)
-
.hwhを読んで AXI-Lite / DMA / IP の構造を知る - それを Python からアクセスできるオブジェクト(
Overlayインスタンス)に変換する
Overlay = 「FPGA デザイン + その上に乗る Python API のセット」 というイメージ
用語メモ:bitstream / .hwh / Block Design
-
bitstream (.bit)
FPGA の構成情報(ビット列)。LUT の設定、配線、DSP の接続などハードそのもの。 -
.hwh
Hardware Handoff(XML)に近いイメージ。Block Design の構造(IP名・AXI 接続・レジスタ情報)を人間可読な形で記述したもの。 -
Block Design (BD)
Vivado の「箱を並べる GUI」で作る構成。PS、IP、AXI 接続などの図。
3. PL(FPGA)のレジスタは PS/Linux からどう読めるのか?
3.1 メモリマップド I/O(MMIO)の基本
CPU が周辺デバイスとやりとりする方法の一つが MMIO(Memory-Mapped I/O) です。
「このアドレス(例:0xA0000000)に書き込むと、FPGA のレジスタに書き込まれる」
という約束を OS / ハードウェア側で決めておく。
Zynq / RFSoC では:
- PL 側の AXI-Lite スレーブポートが
- PS 側のアドレス空間(e.g. 0xA0000000〜)にマップされる
ので、PS から見ると「メモリにアクセスしているだけなのに、実際は PL のレジスタにアクセスしている」 状態になります。
3.2 デバイスツリー(Device Tree)
Linux は起動時に 「この SoC にはどんなデバイスが、どのアドレスにいるのか」 を知る必要があります。
それを知らせているのが Device Tree (DT) です。
- Vivado で
export hardware→ XSA - そこから Device Tree Generator が .dts を生成
- .dts → .dtb(バイナリ)として Linux に渡される
という流れで、Linux は
「
ddc_axi_0は 0xA0000000〜0xA000FFFF を使う AXI-Lite デバイスだ」
などと認識します。
3.3 UIO(Userspace I/O)と MMIO クラス
Linux には UIO (Userspace I/O) という汎用ドライバがあり、
「このデバイスのアドレス範囲を、ユーザ空間に丸ごと mmap してよい」
という扱いをしてくれます。
PYNQ は:
-
/dev/uioXを open -
mmap()でその範囲をユーザ空間にマップ - それを
MMIOクラスとしてラップ
しているので、Python からは:
from pynq import MMIO
mmio = MMIO(0xA0000000, 0x10000)
mmio.write(0x10, 0x1234)
val = mmio.read(0x10)
のように “メモリアクセス=レジスタアクセス” として扱えます。
Overlay を使うとさらに:
ddc = ol.ddc_axi_0
ddc.register_map.phase_inc = 0x1234 # 内部では mmio.write が呼ばれている
という形に抽象化されます。
用語メモ:MMIO / Device Tree / UIO
-
MMIO (Memory-Mapped I/O)
デバイスを「メモリの一部」のように見せる方式。*(volatile uint32_t*)0xA0000010 = 0x1234;みたいなアクセス。 -
Device Tree (DT)
ARM/Linux で使われる「このボードには何のデバイスがどのアドレスにあるか」を記述した“構造体”データ。 -
UIO (Userspace I/O)
Linux カーネルが提供する「汎用デバイスドライバ」。特定のアドレス範囲を/dev/uioX経由でユーザ空間に mmap させる。
4. AXI バスとメモリマップの関係
Zynq / RFSoC 内部では、PS と PL、PL 内の IP 同士が AXI (Advanced eXtensible Interface) というバスで接続されています。
4.1 AXI の種類
-
AXI4(メモリマップド)
DDR など大容量メモリとのやり取り用。バースト転送が可能。 -
AXI4-Lite
軽量なメモリマップド。レジスタ read/write 用。バースト無し。 -
AXI4-Stream
アドレス無しで「ただひたすらデータを流す」ストリームバス。ADC/DAC データなどに最適。
Vivado の Block Design で、
- PS の M_AXI_HPM0(Master)
- PL の HLS IP の S_AXI_CTRL(Slave)
などを AXI4-Lite でつなぐことで、PS のアドレス空間に PL のレジスタがマップされるようになります。
用語メモ:AXI / AXI4 / AXI4-Lite / AXI4-Stream
-
AXI
ARM が定義したオンチップバス規格。読み書きチャネルが分離していて高スループット。 -
AXI4
バースト転送も可能な汎用メモリバス。DDR や DMA の主戦場。 -
AXI4-Lite
軽量な制御用バス。IP のレジスタ設定に使う。 -
AXI4-Stream (AXIS)
TVALID/TREADYハンドシェイクでデータを流すストリーム型。アドレス概念なし。
5. DMA はどうやって使う?(PYNQ から見たとき)
DMA(Direct Memory Access)は:
CPU を介さずに、デバイス ↔ メモリ間で高速にデータを転送する仕組み
です。
Zynq / RFSoC + PYNQ よくある構成:
RFDC(ADC) → HLS DDC IP → AXI DMA → DDR → PS (Pythonで読む)
PYNQ での典型コード:
from pynq import allocate
buf = allocate(shape=(4096,), dtype=np.uint32)
dma.recvchannel.transfer(buf)
dma.recvchannel.wait()
# buf に FPGA からのデータが入っている
PYNQ の DMA ラッパは:
- AXI DMA のレジスタ設定
- DMA 開始
- 転送完了待ち(ポーリングまたは割り込み連携)
- キャッシュの invalidate
などを内部でやってくれるので、ユーザーは
「バッファ用意して transfer() と wait() すればデータが取れる」
という感覚で使えます。
用語メモ:DMA / allocate()
-
DMA (Direct Memory Access)
CPU を介さずにデバイスとメモリを直接つなぐ仕組み。CPU の負荷を減らし高速転送を実現する。 -
allocate()(PYNQ)
DMA 用の“いい感じのバッファ”を作ってくれる関数。物理的に連続・キャッシュコヒーレントなど、低レベルな条件を満たしたメモリを返してくれる。
6. カプセル化(抽象化)のメリット
PYNQ による抽象化のメリットを整理すると:
- デバイスドライバを書かなくてよい
- AXI アドレスや物理アドレスを意識しなくてよい
- Python / Jupyter から試行錯誤できる
- 教育・研究でのハードルが劇的に下がる
「FPGA のアルゴリズムそのもの」や「信号処理の設計」に集中したい、
ことが多いので、OS・ドライバ部分を PYNQ に任せられるのは非常に大きいです。
7. カプセル化のデメリット(限界)
一方で、抽象化が働くということは 「裏側が見えなくなる」 ということでもあります。
代表的な“見えにくくなるポイント”
- DMA のバッファ管理(リングバッファ、SGDMA、オーバラン防止)
- AXI バスの帯域とバックプレッシャ(TREADY/TVALID の挙動)
- キャッシュと DMA の整合性(flush / invalidate のタイミング)
- 複数 DMA の同期やトリガ制御
- 将来的な製品化を想定したエラー処理・リアルタイム性
つまり、
「とりあえず動かす」には最高だが、
「限界まで性能を引き出したい / 製品にしたい」となると低レベル理解が必要
というバランスになっています。
用語メモ:リングバッファ / SGDMA
-
リングバッファ(循環バッファ)
メモリバッファを円環状に使い、古いデータを新しいデータで上書きしていく方式。連続データ取り込みでよく使う。 -
SGDMA(Scatter-Gather DMA)
複数のバッファ(散らばったメモリアドレス)を順番に転送する DMA 方式。連続ストリーミングや複数フレーム管理で必須になる。
8. デバイスドライバは “もう書かなくてよい” のか?
結論を言うと:
-
教育 / 研究 / プロトタイプ
→ PYNQ の抽象化で十分なことが多い(ドライバ不要) -
本番向け・高信頼・超高速なシステム
→ 自前のドライバや bare-metal 実装が必要になる
8.1 PYNQ で十分なケース
- FPGA のアルゴリズム検証
- RFSoC で DDC / FFT を試してスペクトルを見る
- Python で数万サンプル程度の波形をキャプチャして解析
- 教育で「FPGA で FIR フィルタを作って波形を見せる」
8.2 ドライバ / 低レベルが必要になるケース
- 常時 24時間連続でデータを取り続けたい
- Gbps 級のストリーミング(無線・レーダー・高速データロガー)
- 複数 DMA を同期して取りたい
- 誤動作が許されない本番機器(宇宙機器など)
このあたりでは、
- Scatter-Gather DMA
- 割り込みベースのリングバッファ処理
- cache coherency / IOMMU
- 自前の Linux driver or bare-metal
などが必要になるかもしれません。
用語メモ:IOMMU / キャッシュフラッシュ
-
IOMMU (Input/Output Memory Management Unit)
DMA などの I/O デバイス向けの MMU。DMA にも“仮想アドレス”を見せて、安全かつ柔軟にメモリを使わせる仕組み。 -
キャッシュフラッシュ / invalidate
CPU のキャッシュ内容とメインメモリの内容を一致させるための操作。DMA と CPU が同じメモリを共有するときに重要。
9. RFSoC と PYNQ の関係
RFSoC は:
- 高速 ADC/DAC(数 GSPS)
- FPGA(PL)
- ARM CPU(PS)
が 1 パッケージになっている SoC で、
「超高速信号処理のための実験プラットフォーム」として理想的です。
PYNQ を RFSoC で使うと:
- RFDC IP の設定を Python から操作
- HLS で書いた DDC / フィルタを Overlay としてロード
- DMA でデータを DDR → NumPy で FFT / 可視化
といったことが ノートPCからのリモート Jupyter Notebook だけで完結します。
判断基準
-
「RFSoC をまず触ってみたい / DDC / FFT を試したい」
→ PYNQ で始めるのが非常に良い -
「RFSoC を用いた製品レベルのリアルタイム処理を作りたい」
→ PYNQ でアルゴリズムと構成案を検証した上で、
最終的には bare-metal / Petalinux / 自作ドライバへ移行する、という流れが必要になる可能性はあります。
10. まとめ:PYNQ をどう位置づけるか?
最後に、PYNQ の立ち位置を一言でまとめると:
PYNQ は、FPGA+SoC システムの「学習・研究・初期プロトタイプを爆速に進めるための抽象化レイヤ」です。
- ドライバは書かなくてよい
- AXI アドレスを意識しなくてよい
- DMA も Python のメソッドで使える
- Overlay が PL のハード構成を自動的に Python オブジェクト化してくれる
一方で:
- 高速連続ストリーミング
- 本番運用
- 超高信頼・リアルタイム
まで行くと、抽象化の裏側(AXI / DMA / キャッシュ / IOMMU / ドライバ) を理解して、
自前実装に踏み込む必要があるでしょう。
この辺りを踏まえてから、
などの公式文書にも目を通すと良いでしょう。