FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(SDSoC対応編)

## はじめに

次の記事で Linux Kernel 4.10 から新たにサポートされた FPGA Region を紹介しました。

* [「Linux Kernel 4.10 での FPGA のサポート事情」 @Qiita](https://qiita.com/ikwzm/items/2ff7d5429da8ace7c0bd)

また、次の記事で ZYBO、ZYBO-Z7、PYNQ で動作するブートイメージを提供している事を紹介しました。

* [「FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(ブートイメージの提供)」 @Qiita](https://qiita.com/ikwzm/items/7e90f0ca2165dbb9a577)
* [https://github.com/ikwzm/FPGA-SoC-Linux](https://github.com/ikwzm/FPGA-SoC-Linux)


この記事では、上記の環境の上で SDSoC で作った実行ファイルと FPGA のビットストリームを動かす方法について説明します。

## 背景

実は筆者は SDSoC に関しては詳しくありません。ディスク容量の関係で PC にインストールすらしていません。『FPGAの部屋』の @marsee101 さんが twitter で次のようにつぶやいたことから、ちょっと試してみた結果です。ちなみに SDSoC のサンプルも @marsee101 さんの提供です。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/ikwzm?ref_src=twsrc%5Etfw">@ikwzm</a> さんのZybo用Linux環境は、デバイスツリーもFPGAのビットファイルもクロックも動作中に再定義できるし、SDSoCを動作させる環境としては最適だと思う。</p>&mdash; marsee101 (@marsee101) <a href="https://twitter.com/marsee101/status/978391176444170240?ref_src=twsrc%5Etfw">2018年3月26日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

SDSoC に関しては @Vengineer さんの資料がとても参考になりました。あわせてご覧ください。

* [「Xilinx SDSoC(2016.3)解体新書ソフトウェア編」 @Vengineer](https://www.slideshare.net/ssuser479fa3/xilinx-sdsoc20163)

## お断り

今回はあくまでも、ちょっとやってみました的なトライアルの記事です。本格的に動かしたらボロが出そうな感じがします。情報が追加されしだい、追記または削除します。ご了承ください。


Xilinx APF Accelerator driver
-----------------------------------------------------------

SDSoC で作った実行ファイルを動かすには Linux のデバイスドライバである Xilinx APF Accelerator driver(/dev/xlnk) が必要です。このデバイスドライバは、メインラインの Linux Kernel には含まれず、Xilinx が提供している https://github/xilinx/linux-xlnk に含まれています。まずは、このデバイスドライバを https://github.com/ikwzm/FPGA-SoC-Linux で動かすことにしましょう。幸いな事に Xilinx APF Accelerator driver は GNU General Public License version 2 で公開されているので、別の Linux Kernel で動かすことは問題ないはずです。

## Xilinx APF Accelerator driver をビルドする際の基本的なフロー

ここでは Xilinx APF Accelerator driver をビルドする際の基本的なやり方について説明します。

### ソースコードを得る

Xilinx APF Accelerator driver のソースコードは linux-xlnk の drivers/staging/apf にあります。linux-xlnk はいろいろなブランチやタグがあってどこからとってきてよいか判りませんが、とりあえず最新っぽい ebb848efc1cf6a6d63565e09888e56d9928965f6 を checkout しました。

```console
shell$ mkdir xlnk-kernel-module
shell$ mkdir xlnk-kernel-module/build
shell$ git clone git://github.com/xilinx/linux-xlnk
shell$ cd linux-xlnk
shell$ git checkout ebb848efc1cf6a6d63565e09888e56d9928965f6
shell$ cp drivers/staging/apf/*  ../xlnk-kernel-module/build/
shell$ cd ../xlnk-kernel-module/build
```

### arch_setup_dma_ops.c を追加する

前節で得たソースコードをコンパイルしようとすると、arch_setup_dma_ops() が未定義でコンパイルに失敗します。この関数はカーネル内で定義されている関数ですが、EXPORT_SYMBOL されていないため、カーネルモジュールからは呼び出せません。幸い、この関数の機能制限版である of_dma_configure() が EXPORT_SYMBOL されているのでこれで代用します。具体的には次のような arch_setup_dma_ops.c を一緒にコンパイルするようにします。

```C:arch_setup_dma_ops.c
#include <linux/device.h>
#include <linux/types.h>
#include <linux/of.h>
#include <linux/of_device.h>

void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
                        const struct iommu_ops *iommu, bool coherent)
{
        of_dma_configure(dev, NULL);
}
```

### Makefile を差し替える

カーネルモジュールをコンパイルするように Makefile を次の用に書き換えます。

```Makefile:Makefile
HOST_ARCH       ?= $(shell uname -m | sed -e s/arm.*/arm/ -e s/aarch64.*/arm64/)
ARCH            ?= $(shell uname -m | sed -e s/arm.*/arm/ -e s/aarch64.*/arm64/)
KERNEL_SRC_DIR  ?= /lib/modules/$(shell uname -r)/build

ifeq ($(ARCH), arm)
 ifneq ($(HOST_ARCH), arm)
   CROSS_COMPILE  ?= arm-linux-gnueabihf-
 endif
endif
ifeq ($(ARCH), arm64)
 ifneq ($(HOST_ARCH), arm64)
   CROSS_COMPILE  ?= aarch64-linux-gnu-
 endif
endif

obj-m := xlnk-apf.o xlnk-eng.o xlnk-dma.o
xlnk-apf-objs = xlnk.o xlnk-config.o 
xlnk-dma-objs = xilinx-dma-apf.o arch_setup_dma_ops.o

ccflags-y += -DCONFIG_XILINX_APF=m
ccflags-y += -DCONFIG_XILINX_DMA_APF=m

all:
	make -C $(KERNEL_SRC_DIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) modules

clean:
	make -C $(KERNEL_SRC_DIR) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) M=$(PWD) clean

```

この Makefile を使って、ARCH変数 や KERNEL_SRC_DIR変数を適切に設定すればコンパイルできました。

## https://github.com/ikwzm/xlnk-kernel-module

前節で説明したやり方をいちいち実行するのは面倒なので、次の URL にリポジトリを用意しました。このリポジトリには、Xilinx APF Accelerator driver のソースコード一式とビルドとインストールするための Makefile 、Debian パッケージを作るためのスクリプト、linux kernel 4.14.21 用にコンパイル済みの Debian パッケージが含まれています。

* https://github.com/ikwzm/xlnk-kernel-module

### ダウンロード

```console
shell$ git clone git://github.com/ikwzm/xlnk-kernel-module
shell$ cd xlnk-kernel-module
```

### Checkout 4.14.21-armv7-fpga

```console
shell$ git checkout 4.14.21-armv7-fpga
```
### ターゲット上でのセルフビルド

ターゲット上でのセルフビルドするためには、ターゲットとなるシステムに Linux Kernel のカーネルモジュール/デバイスドライバをビルドできる環境が必要です。具体的には、コンパイラや Linux Kernel のヘッダーファイルがインストールされている必要があります。Linux Kernel のヘッダーファイルのインストールに関しては、次の記事も参考にしてください。

* [「クロスコンパイルした Debian GNU/Linux でデバイスドライバをセルフコンパイルする際の注意点」@Qiita](https://qiita.com/ikwzm/items/e31ecb099d0d50cb027c)

#### ビルド

```console
shell$ make
cd ./build ; make ARCH=arm KERNEL_SRC_DIR=/lib/modules/4.14.21-armv7-fpga/build all   ; cd /root/work/xlnk-kernel-module
make[1]: Entering directory '/root/work/xlnk-kernel-module/build'
make -C /lib/modules/4.14.21-armv7-fpga/build ARCH=arm CROSS_COMPILE= M=/root/work/xlnk-kernel-module/build modules
make[2]: Entering directory '/usr/src/linux-headers-4.14.21-armv7-fpga'
  CC [M]  /root/work/xlnk-kernel-module/build/xlnk.o
  CC [M]  /root/work/xlnk-kernel-module/build/xlnk-config.o
  LD [M]  /root/work/xlnk-kernel-module/build/xlnk-apf.o
  CC [M]  /root/work/xlnk-kernel-module/build/xlnk-eng.o
  CC [M]  /root/work/xlnk-kernel-module/build/xilinx-dma-apf.o
  CC [M]  /root/work/xlnk-kernel-module/build/arch_setup_dma_ops.o
  LD [M]  /root/work/xlnk-kernel-module/build/xlnk-dma.o
  Building modules, stage 2.
  MODPOST 3 modules
  CC      /root/work/xlnk-kernel-module/build/xlnk-apf.mod.o
  LD [M]  /root/work/xlnk-kernel-module/build/xlnk-apf.ko
  CC      /root/work/xlnk-kernel-module/build/xlnk-dma.mod.o
  LD [M]  /root/work/xlnk-kernel-module/build/xlnk-dma.ko
  CC      /root/work/xlnk-kernel-module/build/xlnk-eng.mod.o
  LD [M]  /root/work/xlnk-kernel-module/build/xlnk-eng.ko
make[2]: Leaving directory '/usr/src/linux-headers-4.14.21-armv7-fpga'
make[1]: Leaving directory '/root/work/xlnk-kernel-module/build'
```

#### インストール

```console
shell$ sudo make install
cd ./build ; make ARCH=arm KERNEL_SRC_DIR=/lib/modules/4.14.21-armv7-fpga/build all   ; cd /root/work/xlnk-kernel-module
make[1]: Entering directory '/root/work/xlnk-kernel-module/build'
make -C /lib/modules/4.14.21-armv7-fpga/build ARCH=arm CROSS_COMPILE= M=/root/work/xlnk-kernel-module/build modules
make[2]: Entering directory '/usr/src/linux-headers-4.14.21-armv7-fpga'
  Building modules, stage 2.
  MODPOST 3 modules
make[2]: Leaving directory '/usr/src/linux-headers-4.14.21-armv7-fpga'
make[1]: Leaving directory '/root/work/xlnk-kernel-module/build'
install -d /lib/modules/4.14.21-armv7-fpga/xilinx
for ko in build/xlnk-apf.ko build/xlnk-dma.ko build/xlnk-eng.ko; do install -m 0644 $ko /lib/modules/4.14.21-armv7-fpga/xilinx ; done
install -d /etc/systemd/system
install -m 0644 systemd/xilinx-apf-driver.service /etc/systemd/system/
```

```console
shell$ sudo depmod
shell$ sudo systemctl start  xilinx-apf-driver.service
shell$ sudo systemctl enable xilinx-apf-driver.service
Created symlink /etc/systemd/system/multi-user.target.wants/xilinx-apf-driver.service → /etc/systemd/system/xilinx-apf-driver.service.
```

### dpkg によるインストール/アンインストール

checkout した [https://github.com/ikwzm/xlnk-kernel-module](https://github.com/ikwzm/xlnk-kernel-module) の 4.14.21-armv7-fpgaには、linux kernel 4.14.21 用にコンパイル済みの Debian パッケージがあります。このパッケージを使ってインストールする場合は、コンパイラや Linux Kernel のヘッダーファイルは必要ありません。

#### dpkg によるインストール

```console
shell$ dpkg -i xlnk-kernel-module-4.14.21-armv7-fpga_0.0.1-1_arm.deb
(Reading database ... 94083 files and directories currently installed.)
Preparing to unpack xlnk-kernel-module-4.14.21-armv7-fpga_0.0.1-1_armhf.deb ...
Unpacking xlnk-kernel-module-4.14.21-armv7-fpga (0.0.1-1) over (0.0.1-1) ...
Setting up xlnk-kernel-module-4.14.21-armv7-fpga (0.0.1-1) ...
Created symlink /etc/systemd/system/multi-user.target.wants/xilinx-apf-driver.service → /etc/systemd/system/xilinx-apf-driver.service.

shell$ lsmod
Module                  Size  Used by
xlnk_apf              135168  0
xlnk_dma               20480  1 xlnk_apf
xlnk_eng               16384  0

shell$ systemctl status xilinx-apf-driver.service
● xilinx-apf-driver.service - Xilinx APF Driver Service.
   Loaded: loaded (/etc/systemd/system/xilinx-apf-driver.service; enabled; vendo
   Active: active (exited) since Thu 2018-03-29 01:11:17 JST; 45s ago
  Process: 4212 ExecStart=/sbin/modprobe xlnk-apf (code=exited, status=0/SUCCESS
  Process: 4208 ExecStart=/sbin/modprobe xlnk-dma (code=exited, status=0/SUCCESS
  Process: 4204 ExecStart=/sbin/modprobe xlnk-eng (code=exited, status=0/SUCCESS
 Main PID: 4212 (code=exited, status=0/SUCCESS)

Mar 29 01:11:17 debian-fpga systemd[1]: Starting Xilinx APF Driver Service....
Mar 29 01:11:17 debian-fpga systemd[1]: Started Xilinx APF Driver Service..
```

#### dpkg によるアンインストール

アンインストールする際は dpkg -r を使います。

```console
shell$ dpkg -r xlnk-kernel-module-4.14.21-armv7-fpga
(Reading database ... 94083 files and directories currently installed.)
Removing xlnk-kernel-module-4.14.21-armv7-fpga (0.0.1-1) ...
Removed /etc/systemd/system/multi-user.target.wants/xilinx-apf-driver.service.
```

SDSoC のサンプルを動かしてみる
-----------------------------------------------------------

## 必要なファイル

SDSoC でビルドすると、Debug/sd_card ディレクトリに次のようなファイルが出来ます。

* Debug/sd_card/
    + ```BOOT.BIN```
    + ```README.txt```
    + ```_sds/```
        + ```_p0_.bin```
    + ```image.ub```
    + ```application.elf```

```BOOT.BIN``` は ```fsbl.elf``` と ```u-boot.elf``` が入っています。```image.ub``` は Linux Kernel と Device Tree と RootFS が入っています。今回はこの ```BOOT.BIN``` と ```image.ub``` は使いません。

```_sds/_p0_.bin``` が FPGA のビットストリームファイル、```application.elf```(application には作ったアプリケーションの名前が入る) が実行ファイルです。今回必要なのはこの二つのファイルです。

## PL(Programmable Logic)部のコンフィギュレーション

```_sds/_p0.bin``` が FPGA のビットストリームファイルです。これを Device Tree Overlay と FPGA Region を使って PL に書き込みます。

具体的には、まず次のような Device Tree ファイルを用意します。

```dts:_p0_.dts
/dts-v1/;
/ {
        fragment@0 {
                target-path = "/amba/fpga-region0";
                #address-cells = <0x1>;
                #size-cells = <0x1>;

                __overlay__ {
                        #address-cells = <0x1>;
                        #size-cells = <0x1>;

                        firmware-name = "_p0_.bin";

                };
        } ;
} ;
```

次に ```_sds/_p0_.bin``` を ```/lib/firmware``` にコピーします。

```console
shell$ sudo cp _sds/_p0_.bin /lib/firmware
```

最後に dtbocfg.rb を使って ```_p0_.dts``` を Device Tree に追加(Overlay)します。

```console
shell$ sudo dtbocfg.rb --install _p0_ --dts _p0_.dts
/config/device-tree/overlays/_p0_/dtbo: Warning (unit_address_vs_reg): Node /fragment@0 has a unit name, but no reg property
[ 6040.868556] fpga_manager fpga0: writing _p0_.bin to Xilinx Zynq FPGA Manager
```

fpga_manager が writing _p0_.bin to Xilinx Zynq FPGA Manager というメッセージを出して書き込んだことを示します。

## Xilinx APF Accelerator driver を有効にする

カーネルモジュール(xlnk-apf.ko、xlnk-dma.ko、xlnk-eng.ko)をインストールしただけでは Xilinx APF Accelerator driver は有効になりません。有効にするためには次のような Device Tree ファイルを用意して Device Tree に追加(Overlay)します。

```dts:xlnk_dts
/dts-v1/; /plugin/;
/ {
        fragment@0 {
                target-path = "/";
                __overlay__ {
                        xlnk {
                                compatible = "xlnx,xlnk-1.0";
                                clock-names = "xclk0", "xclk1", "xclk2", "xclk3";
                                clocks = <&clkc 0xf &clkc 0x10 &clkc 0x11 &clkc 0x12>;
                        };
                };
        };
};
```

```console
shell$ sudo dtbocfg.rb --install xlnk --dts xlnk.dts
/config/device-tree/overlays/xlnk/dtbo: Warning (unit_address_vs_reg): Node /fragment@0 has a unit name, but no reg property
/config/device-tree/overlays/xlnk/dtbo: Warning (clocks_property): Property 'clocks', cell 1 is not a phandle reference in /fragment@0/__overlay__/xlnk
/config/device-tree/overlays/xlnk/dtbo: Warning (clocks_property): Could not get phandle node for /fragment@0/__overlay__/xlnk:clocks(cell 1)
[ 6052.415866] xlnk xlnk: Major 242
[ 6052.422045] xlnk xlnk: xlnk driver loaded
[ 6052.426222] xlnk xlnk: xlnk_pdev is not null
```

ちょっとクロックまわりで警告が出ていますが、とりあえず無視します。と言うのも、ソースコードを見る限りデバイスドライバが clocks プロパティを参照しているとは思えないからです。

## アプリケーションを実行してみる(1)

今回は @marsee101 さんの提供による multi_apuinit.elf を実行した例を示します。もともとのソースコードは次のようなものでした。

```cpp:multi_apuint.cpp
// multi_apuint.cpp

#include <ap_int.h>

void multi_apuint(ap_uint<8> multi_in0, ap_uint<8> multi_in1,
		ap_uint<16> *multi_out){
	*multi_out = multi_in0 * multi_in1;
}
```

```cpp:multi_apuint_tb.cpp
// multi_apuint_tb.cpp

#include <stdio.h>
#include <string.h>
#include <ap_int.h>

int multi_apuint(ap_uint<8> multi_in0, ap_uint<8> multi_in1, ap_uint<16> *multi_out);

int main(){
	using namespace std;

	ap_uint<8> multi_in0;
	ap_uint<8> multi_in1;
	ap_uint<16> multi_out;

	multi_in0 = 10;
	multi_in1 = 10;
	multi_apuint(multi_in0, multi_in1, &multi_out);
	cout << "multi_out = " << multi_out << endl;

	if (multi_out == (multi_in0*multi_in1))
		return(0);
	else
		return(1);
}
```

これを SDSoC でビルドして次のようなファイル達を作ります。

* Debug/sd_card/
    + ```BOOT.BIN```
    + ```README.txt```
    + ```_sds/```
        + ```_p0_.bin```
    + ```image.ub```
    + ```multi_apuinit.elf```


前節で示したように ```_sds/_p0_.bin``` を PL 部にコンフィギュレーションして、Xilinx APF Accelerator driver を有功にしてから、実行ファイルを起動してみました。

```console
shell$ cd Debug/sd_card/
shell$ sudo ./multi_apuint.elf
multi_out = 100
```

なんか動いているっぽいです。ホントかな～。~~なんか AXI Master 使うようなやつだと失敗しそうな予感。。。~~

## アプリケーションを実行してみる(2)

前節で「AXI Master 使うような奴だと失敗しそうな予感」と書いたところ、@marsee101 さんが DMA を使うアプリケーションを提供してくださいました。

```cpp:DMA_pow.cpp
#pragma SDS data zero_copy(in[0:10])
#pragma SDS data zero_copy(out[0:10])

int DMA_pow2(int *in, int *out){

    int i;

    for (i=0; i<10; i++){
        int temp = *in++;
        *out++ =temp * temp;
    }

    return(0);
}
```

```cpp:DMA_pow_tb.cpp
#include <stdio.h>
#include <stdlib.h>
#include "sds_lib.h"

int DMA_pow2(int *in, int *out);

int main(){
    int *data;
    int *result;
    int i;

    if((data=(int *)sds_alloc(sizeof(int)*10)) == NULL){
        fprintf(stderr, "Can't allocate data[10]\n");
        exit(1);
    }

    if((result=(int *)sds_alloc(sizeof(int)*10)) == NULL){
        fprintf(stderr, "Can't allocate result[10]\n");
        exit(1);
    }

    for(int i=0; i<10; i++){
        data[i] = i;
    }

    DMA_pow2(data, result);

    for(i=0; i<10; i++){
        printf("data[%d] = %d, result[%d] = %d\n", i, data[i], i, result[i]);
    }
}
```

```console
shell$ cd DMA_Pow/Debug/sd_card/
shell$ sudo ./DMA_Pow.elf
[  328.444034] xlnk_eng_probe ...
[  328.447026] uio name xilinx-xlnk-eng.0
[  328.450775] xilinx-xlnk-eng xilinx-xlnk-eng.0: physical base : 0x83c00000
[  328.457580] xilinx-xlnk-eng xilinx-xlnk-eng.0: register range : 0x10000
[  328.464171] xilinx-xlnk-eng xilinx-xlnk-eng.0: base remapped to: 0xf0e40000
[  328.471404] xilinx-xlnk-eng xilinx-xlnk-eng.0: xilinx-xlnk-eng uio registered
[  328.481808] xilinx-xlnk-eng xilinx-xlnk-eng.0: xilinx-xlnk-eng uio unregistered
data[0] = 0, result[0] = 0
data[1] = 1, result[1] = 1
data[2] = 2, result[2] = 4
data[3] = 3, result[3] = 9
data[4] = 4, result[4] = 16
data[5] = 5, result[5] = 25
data[6] = 6, result[6] = 36
data[7] = 7, result[7] = 49
data[8] = 8, result[8] = 64
data[9] = 9, result[9] = 81
```

なんかマジで動いているっぽいです。 @marsee101 さん、感謝。

参考
-----------------------------

* [「Xilinx SDSoC(2016.3)解体新書ソフトウェア編」 @Vengineer](https://www.slideshare.net/ssuser479fa3/xilinx-sdsoc20163)
* [「FPGA+SoC+Linux+Device Tree Overlay+FPGA Region(ブートイメージの提供)」 @Qiita](https://qiita.com/ikwzm/items/7e90f0ca2165dbb9a577)
* [https://github.com/ikwzm/FPGA-SoC-Linux](https://github.com/ikwzm/FPGA-SoC-Linux)
* [https://github.com/ikwzm/xlnk-kernel-module](https://github.com/ikwzm/xlnk-kernel-module)