FPGAを使い始めてしばらくすると、「DMA(Direct Memory Access)」という言葉に出会います。特にZynqのようにPL(FPGA部)とPS(CPU部)を組み合わせて使うシステムでは、このDMAが非常に重要な役割を果たします。この記事では、FPGA初心者の方に向けて、DMAの基礎的な概念や使われ方、実際の設計で知っておきたいポイントをやさしく解説します。
なぜDMAが必要になるのか
FPGAでセンサーデータや信号波形を記録したいとき、最初に思いつくのは、PL側でデータを処理して、BRAMに保存し、それをPS側(CPU)から読み出してファイルに書き込む、という方法です。これは非常にシンプルで、少量のデータなら問題なく動作します。
しかし、ある程度の規模になると問題が見えてきます。たとえば、12bitのADCから10万サンプルを記録しようとすると、必要な容量は約150KBになります。BRAMでは不足する可能性があります。
ここで次に思いつくのは、「ある程度たまったらPSが読み出して保存し、その後またPLが上書きしていく」という運用です。しかしこの方法には大きな弱点があります。BRAMが小さいために、PSが頻繁に読み出し処理を行わなければならず、そのたびに処理が割り込まれます。PSがデータを保存している間、PL側では新しいデータを書き込む場所がなくなり、記録が一時停止したり、データが失われたりする可能性が出てきます。
この「PSが何度も介入しないといけない」というのが、システム全体のボトルネックになりえます。介入の回数を減らすには、もっと大きなバッファが必要です。ZynqにはDDRメモリ(数百MB〜)が搭載されており、ここにデータを直接書き込むことができれば、PLは連続してデータを記録でき、PSは「バッファがある程度たまったタイミング」でまとめて読み出してファイルに保存すれば済むようになります。
この構成を可能にするのがDMA(Direct Memory Access)です。DMAを使えば、PLからDDRメモリにデータをCPUを介さずに自動で転送することができます。CPUは転送処理に関与せず、転送完了後にまとめて処理を行えば良いため、測定処理とファイル保存処理を非同期に並列実行できるようになります。
DMAとは何か
DMAとは、CPUを介さずにデータをメモリへ直接転送するためのハードウェア機構です。たとえば、PLで処理したADCデータを、DMAを使ってそのままDDRメモリに書き込むようにすれば、CPUはその間まったく関与せずに済みます。CPUは、DMA転送が完了したタイミングで、たまったデータをまとめて処理すればよいだけです。
Zynqには「AXI DMA」という専用のIPコアが用意されており、Vivadoで構成することで、PL側からAXI-Streamインタフェースでデータを流し、DMAがそれをDDRに書き込む、という構成を簡単に実現できます。
たとえば、12bitのADCデータを16bitにパディングしてストリーム出力し、DMAが受け取ってDDRに順次格納していくといった使い方が可能です。データ転送はハードウェアで完全に自動化されており、CPUが処理に関わるのは、あくまで転送の開始指示と、完了後のバッファ読み出しだけになります。
このように、DMAは「データを自動で運んでくれる装置」として機能し、CPUリソースを有効に使いながら、連続した高速測定や記録を可能にします。特に、測定中にCPUが他の処理を行っていても、データが失われることなく蓄積され続けるという点で、DMAは測定システムにおける非常に重要な基盤技術となります。
承知いたしました。
それでは、次のセクション「具体例でDMAを理解」を、システム全体の流れが把握できるよう、図解的な説明と実際の構成例・コードを交えて、丁寧に記述いたします。
具体例でDMAを理解
ここでは、FPGA(PL)で生成したデータをDMAを使ってDDRメモリに転送し、その後PS(Linux)側でファイルとして保存するという、一連の処理の流れを具体的に見ていきます。
システム全体の流れ
- PL(FPGAロジック)がADCなどからデータを取得する
- データをAXI-Stream形式で出力する
- AXI DMAがストリームを受け取り、DDRメモリに書き込む
- PS(CPU)が、DMA完了後にDDRのデータを読み出し、ファイルに保存する
この構成により、PL側は測定を止めることなく連続的にデータを流し続けることができ、PS側は適切なタイミングで保存処理を実行できます。
PL側の構成(Verilog)
以下は、12bitのADCデータを16bitに拡張し、DMAに渡す例です。
// ADCデータ(12bit)
wire [11:0] adc_data;
wire adc_valid;
reg [15:0] stream_data;
// ストリーム出力(AXI-Stream)
assign m_axis_tdata = {4'b0000, adc_data}; // 16bitにゼロパディング
assign m_axis_tvalid = adc_valid;
assign m_axis_tlast = (sample_count == SAMPLE_NUM - 1); // 最終サンプルを示す
この出力をAXI DMAの S_AXIS_S2MM
ポートに接続します。DMAはデータを受け取り次第、自動的にDDRに書き込んでくれます。
Vivado上での構成
Vivadoのブロックデザインでは、以下のように接続します:
AXI DMA
IPのS_AXIS_S2MM
にストリーム入力を接続AXI DMA
のM_AXI_S2MM
を AXI Interconnect 経由で DDR に接続AXI Lite
インタフェースを PS に接続し、DMA制御用に使用
これにより、PLからDDRへの自動転送経路が構築されます。
PS側の読み出し処理(Linuxユーザー空間)
DMA転送されたデータは、PS側で mmap()
を使って直接メモリから読み出し、ファイルに保存できます。以下はC言語での例です。
#include <fcntl.h>
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#define DMA_BUF_ADDR 0x1F000000 // 予約済みメモリの物理アドレス
#define DMA_BUF_SIZE 0x00100000 // 1MB
int main() {
int fd = open("/dev/mem", O_RDONLY);
void* dma_buf = mmap(NULL, DMA_BUF_SIZE, PROT_READ, MAP_SHARED, fd, DMA_BUF_ADDR);
FILE* fout = fopen("dma_output.bin", "wb");
fwrite(dma_buf, 1, DMA_BUF_SIZE, fout);
fclose(fout);
munmap(dma_buf, DMA_BUF_SIZE);
close(fd);
return 0;
}
このコードは、DMAで転送されたバッファを1回でまとめて保存しています。PS側の介入回数が大幅に減るため、処理のオーバーヘッドが極めて小さく抑えられます。
DMA使用のメリットを具体的に体感できる場面
- 高速ADCの波形を連続記録したい場合
- 高速センサーや画像デバイスの出力をログ保存する場合
- CPUが他の処理を行っていても、測定を止めたくない場合
このような場面では、DMAとDDRバッファを使う構成が、唯一現実的で安定した方法になります。
DMAの限界
DMAは、FPGAとCPUを連携させたシステムで非常に有効な手段ですが、使用する際にはいくつかの制約や弱点も理解しておく必要があります。適切に扱わなければ、「思ったより速くない」「データが欠損する」といった問題に直面することがあります。
バッファサイズと転送タイミングに注意が必要
DMAは高速にデータを転送できますが、「バッファがいっぱいになる前に次の処理へ進めること」が前提となります。たとえば、PLからDMAを通してDDRに書き込んでいく場合、PS側が一定間隔でバッファを読み出しておかないと、次の転送先が確保できず、DMAの転送が止まってしまいます。
特に、連続測定を行っている場合、PS側の処理タイミングが間に合わないと、データが上書きされたり、DMAが一時停止して測定に隙間が生じる可能性があります。
転送サイズや境界整合性の制限
多くのDMA IPコア(たとえばXilinxのAXI DMA)では、1回に転送できるデータサイズに制限があります。例えば、1回の転送で扱える最大サイズが8MBなどに制限されている場合があります。
また、データの境界(4バイトアラインメントや16バイトアラインメントなど)を守る必要があることが多く、非標準的なビット幅のデータ(たとえば12bit、14bitなど)を詰めて保存しようとすると、ソフトウェア側でのデコードが煩雑になります。通常は16bitや32bitに整形して転送するのが一般的です。
ストリームの取りこぼしに注意
AXI DMAは基本的に「受け取れるときにしかデータを受け取らない」仕様になっています。PL側から送出されるデータがDMAの受信タイミングに合っていない場合、一部のデータが捨てられてしまう可能性があります。
このため、PL側ではストリーム出力に対して TREADY
信号を確認し、DMAが受け取り可能なタイミングにだけ送信するよう設計する必要があります。
複数バッファを使った運用が必要になる場合も
安定してデータを連続記録するためには、シングルバッファでは不十分なことが多く、ダブルバッファ(リングバッファ)構成で運用するのが一般的です。
- 一方のバッファでDMA転送中
- もう一方のバッファをPSが処理中
このように交互に切り替えて使うことで、測定と保存を完全に並列化し、データの欠損を防ぐことができます。
まとめ:DMAは強力だが、設計には配慮が必要
課題 | 内容 | 対処法 |
---|---|---|
転送タイミング | PS側の処理が遅いとバッファがあふれる | ダブルバッファ化、転送監視 |
サイズ制限 | 転送長に上限がある | 複数回に分けて転送 |
アラインメント | bit単位の保存が難しい | データを16bit/32bit単位に整形 |
データロス | DMAが受け取れないとロスする | TREADY確認、FIFOを併用 |
DMAは非常に便利で強力な手段ですが、安定運用するにはFPGA側とCPU側の設計を協調させる必要があるということを忘れてはいけません。
まとめ
FPGA(PL)で生成したデータをPS(CPU)側で保存・処理したいとき、最初はBRAMを使ってアドレス経由で読み出す構成を選ぶことが多いと思います。シンプルで直感的にわかりやすい方法ですが、現実のシステムではすぐに限界が見えてきます。
BRAMは容量が小さく、CPU側からの読み出し処理が追いつかないとすぐにあふれてしまいます。そのため、CPUが何度も測定処理に割り込むことになり、連続測定ができなくなってしまいます。これは、データ処理そのものが遅いというよりも、CPUが処理に介入しなければならない回数が多すぎることが問題です。
このボトルネックを解消する手段として登場するのがDMA(Direct Memory Access)です。DMAを使えば、CPUを介さずにPLから直接DDRメモリにデータを転送できます。これにより、CPUが処理に割り込まなくても、PL側は連続してデータを送り続けることができます。CPUは、ある程度データがたまってから一括で保存処理をすればよく、測定と保存を非同期に並行処理できるというメリットがあります。
ただし、DMAは万能ではありません。転送サイズやアラインメントの制限、データの取りこぼしへの対策、バッファの切り替え制御など、設計上の注意点は少なくありません。これらを理解し、対策を講じたうえで使うことで、DMAは非常に強力なツールとなります。
初心者が押さえておきたいポイント
- DMAはCPUの代わりにデータ転送をしてくれる装置
- BRAMだけではバッファ容量が足りず、連続測定が難しい
- DMA+DDRを使えば、測定処理を止めずにデータを保存できる
- 転送処理の制御やバッファ運用には注意が必要
FPGAとCPUが連携するようなシステムを構築するなら、DMAは避けて通れない技術の一つです。最初は難しく感じるかもしれませんが、シンプルな構成から始めて動かしてみることで、徐々に理解が深まります。
本記事が、DMAという仕組みの背景と役割をつかむ助けになれば幸いです。