はじめに
RFSoC4x2 と RFDC IP Core を正しく扱うために、AXI バスを解説し、歴史・原理・実装・RFDC 実例までを紹介してみます。
対象読者:
RFSoC / FPGA 初学者〜中級者、特に RFSoC4x2 + HLS で ADC/DAC 処理(DDS → DAC、ADC → DDC)を実装する研究者・学生
この記事の目的:
RFDC を使いこなすには、AXI bus を「なんとなく」ではなく、歴史・思想 → 仕様 → 実際の信号 → RFDC での意味まで理解する必要がある。
本記事では、AXI の背景、AXI4 / AXI4-Lite / AXI4-Stream の違い、そして RFSoC の RFDC IP Core がどの AXI をどう使うかを体系的に整理する。
0. RFDC を使う前に AXI を理解する理由
Zynq UltraScale+ RFSoC RF Data Converter v2.6 Gen 1/2/3/DFE LogiCORE IP Product Guide (PG269)
RF Data Converter (以後、略してRFDC) というRFSoC4x2のIPコアの御本尊は、
- 制御レジスタ → AXI4-Lite
- ADC/DAC データ通路 → AXI4-Stream
- DMA / PS (Arm core) との通信 → AXI4
- HLS モジュールとの接続 → AXI4-Stream / AXI-Lite
を 同時に扱う。
つまり RFDC を使うと、知らない間に 3種類の AXI を使っている。
1. AXI バスとは何か?その歴史から理解する
1.1 ARM が支配する SoC の進化とともに AXI は生まれた
AXI(Advanced eXtensible Interface)は ARM (現 Arm Ltd.) が定める
AMBA(Advanced Microcontroller Bus Architecture) の一部。
AMBA の世代は以下のように進化してきた:
| 世代 | 名称 | 主な用途 |
|---|---|---|
| AMBA 1 (1990s) | ASB/APB | マイコン用。クロック同期の単一バス。 |
| AMBA 2 (2000s) | AHB/APB | FPGA にもよく登場。単一アドレスフェーズ。 |
| AMBA 3 (2003〜) | AXI3 | 高性能マルチマスタ SoC を支えるため誕生。 |
| AMBA 4 (2010s〜) | AXI4 / AXI4-Lite / AXI-Stream | 高スループットと柔軟性に特化。FPGA で主流。 |
AXI が生まれた理由
- CPU / DMA / GPU / DSP が 同時にメモリへアクセスする必要が出た
- ピン数を増やさずに 帯域を上げたい
- レイテンシではなく スループット最適化 が重要になった
- モジュールが独立開発され、標準化されたバスが必要になった
その結果、
AXI = マルチマスタ時代のために生まれた “SoC の血流”
と言える。
2. AXI の基本概念(握手、チャネル分離、Master/Slave)
AXI は以下の思想に基づく。
2.1 Master / Slave の思想
現代的には「Initiator / Target」と言ったほうが良いが、Arm 仕様では依然 Master/Slave という語が残っている。
- Master(Initiator):通信を開始する側(アドレス・リクエストを出す)
- Slave(Target):応答する側(メモリ、レジスタ、IP コア)
例:
- RFDC 制御の AXI-Lite → PS (Zynq の Arm) が Master、RFDC IP が Slave
- ADC の AXI-Stream → RFDC が Master、HLS モジュールが Slave(受信側)
2.2 5 チャネル構造(AXI4 / AXI3 共通)
AXI はバスを5つの独立チャネルに分解した:
- AW(Write Address)
- W(Write Data)
- B(Write Response)
- AR(Read Address)
- R(Read Data)
これにより、
読み書きが完全に非同期・独立化され、スループットが劇的に向上。
2.3 VALID / READY の握手
AXI の本質はこれ:
VALID = 「送る側はデータが用意できた」
READY = 「受け取る準備ができた」
両方が High になった瞬間にデータが転送される。
READY を遅くすれば、下流モジュールの処理能力に合わせて “流量制御 (back-pressure)” ができる。
これが AXI-Stream の強み。
3. AXI の三兄弟:AXI4 / AXI4-Lite / AXI4-Stream
3.1 AXI4(メモリマップド高性能)
- DDR や DMA など “大量のデータ読み書き” に使う
- バースト転送可能(最大256ビート)
用途:
- DMA → DDR メモリ転送
- Arm → メモリ IO
3.2 AXI4-Lite(レジスタ制御)
- バーストなし
- 単発の read/write のみ
- 低レイテンシで制御レジスタを操作するためのバス
用途:
- RFDC の制御
- HLS モジュールのパラメータ設定
3.3 AXI4-Stream(ストリームデータ)
- アドレスなし
- データをひたすら流す
- ハイレート信号処理向け
- VALID / READY のみで制御
用途:
- RFDC ADC → FPGA Fabric(IQ データ)
- DDS → DAC
- DDC(ダウンコンバート)処理
4. RFSoC の RFDC IP Core と AXI のつながり
RFDC は ADC と DAC の集合体だが、FPGA と通信する際には AXI に従って動作する。
4.1 RFDC IP Core で利用される AXI の一覧
| 用途 | バス種類 | Master/Slave | 内容 |
|---|---|---|---|
| RFDC の制御レジスタ | AXI4-Lite | PS(M) → RFDC(S) | サンプリング設定、Mixer、PLL |
| ADC 出力データ | AXI4-Stream | RFDC(M) → fabric(S) | IQ 信号、サンプルごとに流れる |
| DAC 入力データ | AXI4-Stream | fabric(M) → RFDC(S) | DAC へ送るサンプル |
| DMA 経由の転送 | AXI4 | DMA(M) → DDR(S) | データ保存など |
4.2 RFDC の ADC → AXI4-Stream の動作
RFDC はサンプルレートが数Gsps だが、FPGA fabric 側に送られる AXI-Stream は decimation 後のレート。
例:
4x decimation → 3.932Gsps → 約983 Msps
8x decimation → 491 Msps
このストリームを受け取る HLS モジュールは READY を常に High にしないと詰まる。
4.3 HLS で受ける場合のインタフェース例
void ddc(
hls::stream<axis_data>& adc_in,
hls::stream<axis_data>& i_out,
hls::stream<axis_data>& q_out
) {
#pragma HLS INTERFACE axis port=adc_in
#pragma HLS INTERFACE axis port=i_out
#pragma HLS INTERFACE axis port=q_out
#pragma HLS PIPELINE II=1
adc_in は RFDC が Master なので、この HLS モジュールは Slave(受け取り側) になる。
5. RFSoC4x2 で "最小構成のループバックシステム" を作るには
「RFDC ADC → HLS DDC → HLS DDS → RFDC DAC」
これに必要な AXI の理解をまとめる。
5.1 必要な AXI 知識
- ADC データは AXI4-Stream(Master)で流れてくる
- HLS DDC は AXI4-Stream Slave としてデータを受ける
- DDS は AXI4-Stream Master として DAC に流す
- DDS の周波数・振幅などは AXI4-Lite のレジスタとして実装
- RFDC 制御は Arm (PS) から AXI-Lite 経由で設定
6. なぜ AXI を理解しないと HLS モジュールが動かないのか
理由は 3 つ:
(1) READY を適切にハンドリングしないと データがロスする
RFDC は高速データを 容赦なく流す。
もし HLS で READY を 0 にすれば:
- RFDC 側で back-pressure が発生
- それでも止めきれないと データが破棄される
(2) AXI-Lite レジスタの割り当て方を理解しないと DDS の制御ができない
HLS モジュールでパラメータを外部から設定するには AXI-Lite。
(3) ADC/DAC の IQ データの順序は AXI protocol によって決まる
HLS が正しく解釈しないと I/Q がひっくり返る。
7. AXI を “物理的なもの” として理解する直感図
AXI は単なる論理仕様ではなく、
- AW/W/B/AR/R の 独立レーン
- VALID/READY の 握手
- Master/Slave の 流れの方向
- Stream の 常時連続のデータ流
これらの “物理的な動作” を想像すると理解が早い。
8. RFDC + HLS を成功させるための実践ポイント
RFDC と HLS モジュールを AXI4-Stream でつないだときに、
- シミュレーションでは動くのに実機で途切れる
- 謎の FIFO FULL / UNDERFLOW が出る
- 波形がところどころ欠ける
という問題は典型的です。ここでは、「連続サンプルを落とさずに処理する」ための最低限の設計ルールを整理します。
8.1 Stream 処理の必須条件:1 サイクル 1 サンプルを止めずに流す
(1) HLS の Top 関数は Pipeline II=1 を基本にする
AXI4-Stream で RFDC からサンプルが 毎クロック 1 個飛んでくる状況では、
HLS モジュールも毎クロック 1 個処理できる必要があります。
この「何クロックおきに新しいデータを受け付けられるか」を表すのが
HLS の Initiation Interval (II) です。
void ddc_top(...) {
#pragma HLS PIPELINE II=1
...
}
としておくと、
- 1 クロックごとに新しい入力を受け取り
- 1 クロックごとに出力を出せる
ストリーム処理に適した構造になります。
もし II=2 以上になると:
- RFDC は毎クロックサンプルを送りたい
- でも HLS は 2 クロックに 1 回しか新しい入力を受け取れない
→ 結果として
- 上流にバックプレッシャ(TREADY=0)がかかる
- 場合によっては RFDC 側でサンプルが捨てられる
という好ましくないことが起こります。
(2) TREADY は「受け取れるときは基本 High のまま」
AXI4-Stream のハンドシェイクは:
- 送信側:
TVALID - 受信側:
TREADY -
TVALID && TREADY == 1のときだけ 1 サンプル転送
というルールです。
RFDC → HLS の流れでは、HLS 側は「受け取れる限り TREADY=1 にしておく」 のが基本です。
- 内部 FIFO がいっぱいになる
- 意図的にサンプルを間引く
など、本当に必要なときだけ TREADY=0 を出すようにしないと、
無意識に
TREADYを 0 にしてしまい、サンプルが欠損する
原因になります。
HLS では、axis ポートを read() し続けるシンプルな実装にしておけば、コンパイラがよしなに TREADY を 1 に張り付きにしてくれます。
(3) 適切な FIFO(#pragma HLS STREAM depth=...)で「ゆらぎ」を吸収する
現実のシステムでは、すべてのモジュールが理想的に 1 サイクル 1 サンプルで動いているわけではありません。
- 一時的に内部計算が重くなる
- 追加の分岐や係数更新が入る
- 下流の DMA が一瞬止まる
こうした 局所的な“ゆらぎ” を吸収するために必要なのが FIFO です。
HLS では、
#pragma HLS STREAM variable=some_stream depth=64
のように書くことで、
-
some_streamの背後に BRAM FIFO を置き - 最大 64 サンプル程度のバーストや詰まりを吸収できる
ようにできます。
目安としては:
- クロック比(RFDC のサンプルクロック vs PL クロック)
- DMA や DDC の処理レイテンシ
を考えながら、数十〜数百サンプル程度の深さを持つ FIFO を適宜配置すると安定しやすくなります。
(4) RFDC のサンプル幅と HLS のデータ型を合わせる
RFDC の AXI4-Stream 出力は、
- サンプル幅:16bit / 24bit / 32bit など
- I/Q ペアかどうか(複素か実数か)
- 複数チャネルを 1 ワードにパックしている場合もある
など、コンフィグに応じてフォーマットが変わります。
HLS 側の struct / 型定義がこれとズレていると、
- ビットシフトをミスる
- 符号付き/符号なしを間違える
- 上位ビットが全て 0 / 1 になってしまう
など、原因が分かりにくいバグになります。
例:RFDC が 16bit 符号付きの I/Q を 1 ワードに詰めている場合
#include <ap_int.h>
struct axis_iq_t {
ap_int<16> i;
ap_int<16> q;
};
void ddc_top(hls::stream<axis_iq_t>& adc_in, ...) {
#pragma HLS INTERFACE axis port=adc_in
...
}
のように、HLS 側の struct も “i と q の幅・順番” を RFDC に揃えるのが鉄則です。
8.2 HLS モジュールのインタフェース設計指針
RFDC と HLS IP をつなぐときに、
「このポートは AXIS の Master か? Slave か?」「AXI-Lite はどう生やすか?」 は設計段階で決めておくとスムーズです。
(1) ADC を受け取る場合:AXI4-Stream Slave(S_AXIS)
RFDC からの ADC データを 受け取る側の HLS IP は、
AXI4-Stream 的には Slave になります。
HLS コードでは:
void ddc_top(hls::stream<axis_iq_t>& adc_in, ...) {
#pragma HLS INTERFACE axis port=adc_in
...
}
のように axis を付けるだけですが、
- RFDC 視点では “M_AXIS”
- HLS 視点では “S_AXIS”
という関係になっていることを意識しておくと、Block Design での接続が理解しやすくなります。
(2) DAC に送る場合:AXI4-Stream Master(M_AXIS)
逆に、HLS で生成した DDS や DUC の出力を DAC に送りたい場合は、
HLS IP が AXI4-Stream Master になります。
void dds_top(hls::stream<axis_iq_t>& dac_out, ...) {
#pragma HLS INTERFACE axis port=dac_out
...
}
この dac_out は、
- HLS IP 視点では “M_AXIS”
- RFDC の DAC IP 視点では “S_AXIS”
として接続されます。
どちらが “マスタ”かは、「誰が TDATA を出して、誰が受け取るか」で決まる
という感覚を持っておくと混乱しにくくなります。
(3) パラメータ設定(周波数レジスタなど)は AXI-Lite(s_axilite)
NCO の周波数、ゲイン、ON/OFF、フィルタ係数のバンク切り替えなど、
「PS から書き換えたい設定」 は AXI-Lite で出しておくのが定石です。
HLS では:
void ddc_top(
hls::stream<axis_iq_t>& adc_in,
hls::stream<axis_iq_t>& ddc_out,
ap_uint<32> freq_word,
bool enable
) {
#pragma HLS INTERFACE axis port=adc_in
#pragma HLS INTERFACE axis port=ddc_out
#pragma HLS INTERFACE s_axilite port=freq_word bundle=CTRL
#pragma HLS INTERFACE s_axilite port=enable bundle=CTRL
#pragma HLS INTERFACE s_axilite port=return bundle=CTRL
#pragma HLS INTERFACE ap_ctrl_none port=return
...
}
ポイント:
-
s_axiliteを付けると AXI-Lite レジスタとしてマップされる -
bundle=CTRLで同一バスにまとめる(s_axi_CTRLなど) -
ap_ctrl_noneとすることで、外部からスタート停止をしない常時動作 IP にできる
(RFDC とストリーム接続する DSP ブロックでは、この形が扱いやすい)
こうしておくと、PS(Linux/PYNQ)側からは:
ddc = ol.ddc_top_0
ddc.register_map.freq_word = 0x12345678
ddc.register_map.enable = 1
のように レジスタ名ベースで制御できるようになり、
実験が非常に楽になります。
9. まとめ:RFDC を使いこなす鍵は「AXI を“線”ではなく“仕組み”として理解すること」
RFSoC の RFDC は、
- 数 GHz 帯域の ADC / DAC
- 豊富なミキサ・デシメータ
- それらを PL に AXI4-Stream で接続
という、とても強力な IP です。
しかし、その真価を発揮できるかどうかは結局のところ:
「AXI の基本設計思想をどれだけ腹落ちさせているか」
で大きく変わります。
9.1 なぜ AXI の歴史や設計思想を振り返るのか?
単に「AXI4 はこういう信号線があって…」と覚えるだけでは、
- なぜ
VALIDとREADYを分けているのか? - なぜ AXI4-Lite と AXI4-Stream を分けたのか?
- なぜバースト転送や ID が入っているのか?
といった “設計上の理由” が見えません。
歴史的な背景(AMBA → AXI に至る流れ)や設計思想をざっくり押さえると:
- 「CPU と周辺回路を大規模 SoC でつなぐには、これくらいの柔軟性が必要だった」
- 「だから制御用の AXI-Lite と、データ用の AXI4 / AXI-Streamに分かれている」
という 必然性が理解できるようになります。
9.2 RFDC は「AXI-Stream の端子」である、と理解する
RFDC のブロックを抽象化すると:
- 制御プレーン:AXI4-Lite でレジスタ設定
- データプレーン:AXI4-Stream でサンプルを送り出す/受け取る
という、非常にシンプルな構造になっています。
つまり、
- RFDC の「中身のアナログ魔法」は IP に任せる
- 我々は「AXI-Stream でどうサンプルを受け取り、どう処理し、どうメモリに流すか」に集中する
という意識で設計すればよい、ということです。
9.3 HLS モジュール設計は「AXI との境界」を意識すると失敗が減る
この記事で見てきたように、
- AXI4-Stream のハンドシェイクを意識した Pipeline 設計
- AXI-Lite でパラメータを出しておく設計
- RFDC のデータ幅に合わせた struct 設計
- DMA を前提にしたストリーム → メモリの流れ
などを押さえて HLS を書くと、
「シミュレーションでは動くのに、実機で RFDC とつないだら破綻する」
といった事故が劇的に減ります。
9.4 最後に:RFDC + HLS + PYNQ の“正しい三角関係”
- RFDC:高速アナログ I/O と AXI-Stream の「入り口・出口」
- HLS:RFDC のストリームを受けて、NCO / DDC / フィルタなどを記述する「信号処理エンジン」
- PYNQ:AXI-Lite / DMA / Overlay を抽象化し、Python からそれらを制御する「実験用フロントエンド」
この三者がうまく噛み合うと、
RFSoC 上で「RFDC → HLS DDC → DMA → NumPy FFT」までを、1 人の研究者が Python 中心のワークフローで回せる
という、とても強力な環境が手に入ります。
そのための土台として、
- AXI の役割分担(AXI4 / AXI-Lite / AXI-Stream)
- ストリーミング処理の条件(II=1・TREADY・FIFO)
- HLS のインタフェース設計
を一度きちんと押さえておく価値は大きいと思います。
- 参考サイト
- 関連記事

