zFIFO - an AXI DMA driver for Zynq and ZynqMP

zFIFO むけの PL design を作る

ここでは、自分で作った FPGA デザインを PL にのせて、zFIFO 経由で Linux からアクセスできるようにするための基本的な手順を解説します。基本的には、

Vivadoのブロックデザインを用意する

zFIFO のソースコードと一緒に、Ultra96 (無印およびv2)、ZYBO (無印および Z7-20) 用のブロックデザインを生成するための Tcl スクリプトを配布しています。現在配布されているものは Vivado 2019.2 用です。

Ultra96v2 を使う場合は Avnet の BDF repository (https://github.com/Avnet/bdf) からボード定義をダウンロードし、セットアップしておく必要があります。それ以外の場合は Vivado で Tools->Download Latest Boards を実行して、Xilinx Board Storeからダウンロードしたボード定義ファイルがあればOKです。

スクリプトは examples/fifo/ 以下の、各ボードに対応したフォルダ以下にあります。各フォルダには design_ps.tcl というブロックデザインのファイルがありますが、それではなく u96.tcl や zybo.tcl のような、ボードの名前のついたファイルを使います。

手順は簡単で、

  1. Vivado で空のプロジェクトを作成 (デバイスの設定とかは適当でもかまいません)
  2. Vivado の Tools -> Run Tcl Script で、上記の Tcl script (たとえば examples/fifo/u96v2/u96v2.tcl) を実行

とするだけです。こうすると、以下のようにAXI DMA を AXI Stream Data FIFO でループバックするだけのブロックデザインを含むプロジェクトが生成されますので、そのままビットストリームを生成すれば DMA loopback の例題として動かすことが可能です。また、AXI Stream Data FIFO を高位合成やRTLで設計した自前のロジックに置き換えれば、自分のアプリケーションを載せることもできます。

ps_design.png

Vivado HLSでのコード例

Vivado HLSでコードを書くときのポイントは、

  1. 入出力ともAXI Streamなので、hls::streamクラスを使うのが簡単です
    • TLAST信号をつけるには正式にはap_intクラスを使う方法がサポートされていますが、これだと型が整数に限られますし、なにかと面倒です。以下の例のようにboolがひとつついた構造体を使うとうまく合成できます
  2. 出力 (FPGA -> Linux) については、Linux 側で長さが事前にわかっていることが必要 の2点です。

1次元の整数配列の総和をもとめるVivado HLS用コードの例を以下に挙げます。

  1. #include "hls_stream.h"
  2.  
  3. struct int_s{
  4.   int data;
  5.   bool last;
  6. };
  7.  
  8. void vec_accum (hls::stream<int_s>& a,
  9.                 hls::stream<int_s>& b){
  10. #pragma HLS INTERFACE axis port=a
  11. #pragma HLS INTERFACE axis port=b
  12.  
  13.   int_s aa, bb;
  14.   int sum=0, i=0;
  15.  
  16.   do {
  17.     aa = a.read();
  18.     int x = aa.data;
  19.     sum += x;
  20.     i ++;
  21.   } while(aa.last == 0);
  22.  
  23.   bb.data=i;   bb.last=0;  b.write(bb);
  24.   bb.data=sum; bb.last=1;  b.write(bb);
  25. }

これを普通にVivado HLSで合成すると、ブロックレベルのハンドシェイク信号と、a, b ふたつの AXI Stream インタフェイスが生成されます。ブロックレベルハンドシェイク信号は ap_start を常時1になるようにconstantブロックへ接続しておくだけでOKです。AXI Streamインタフェイスは、DMA loopback の例題に含まれている AXI Stream Data FIFO を削除して、その代わりに AXI DMA controller へ接続します。最終的に、以下のようなブロックデザインができます。

bd3.png

Vivadoで生成したビットストリームは、/boot/pl.bin に置くなどすれば、再起動時に読み込まれます。開発中は JTAG 経由で直接 PL を書き換えてしまっても (その時に PS からアクセスしていなければ、ドライバを rmmod しなくても) 大丈夫です。

Linux (PS) 側のコード

上記のHLSコードを動かすための、PS側のzfifoなコード (vec-accum-ps.c) は以下のようになります。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/types.h>
  5. #include <sys/stat.h>
  6. #include <fcntl.h>
  7. #include <sys/ioctl.h>
  8.  
  9. #include "zfifo.h"
  10.  
  11. int main(){ 
  12.   int fd = open("/dev/zfifo0", O_RDWR | O_SYNC);
  13.   if (fd<0) {
  14.     printf("Can't open /dev/zfifo0!\n");
  15.     return -1;
  16.   }
  17.  
  18.   unsigned size = 10;
  19.   int send[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  20.   int recv[2] = { 0 };
  21.  
  22.   zf_send(fd, (char*)send, sizeof(int)*size);
  23.   zf_recv(fd, (char*)recv, sizeof(int)*2);
  24.  
  25.   printf("Length: %d, Sum: %d\n", recv[0], recv[1]);
  26.  
  27.   close(fd);
  28.   return 0;
  29. }

配列に書いておいたデータを zf_send() で送信、zf_recv() で PL からのデータを受信します。このとき、PL から実際に DMA で送られてくるデータ長と zf_recv() に指定するデータ長が一致しないと、バッファがあふれたり、ドライバがデータを待ち続けて zf_recv() が終了しないなどの問題が発生しますので、注意が必要です。

ダウンロード時点で用意されている DMA loopback の例題と同じく、

$ sudo sh load_driver

でドライバをロードしたあと、

$ gcc libzfifo.c vec-accum-ps.c -o vec-accum
$ ./vec-accum
Length: 10, Sum: 55

のようにして実行し、動作確認ができます。

制限事項など

zfifo によるデータ転送には以下のような制約があります。


Front page   New Page list Search Recent changes   Help   RSS of recent changes