Ultra96 向け Debian GNU/Linux (v2018.2版) で Vivado-HLS を使って合成した回路を動かす

はじめに
-------------------------------------------------------------

次の記事で Ultra96 向け Debian GNU/Linux (v2018.2版) の構築をしました。

 * [「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(イントロ編)」@Qiita](https://qiita.com/ikwzm/items/5ae07792143d8018bfea)
 * [「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Boot Loader編)」@Qiita](https://qiita.com/ikwzm/items/9c78e83298906447fe5d)
 * [「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Linux Kernel編)」@Qiita](https://qiita.com/ikwzm/items/02af5ac78a558c910e84)
 * [「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Debian9 Root File System編)」@Qiita](https://qiita.com/ikwzm/items/be06b07e26cbf05fec2b)

また、次の記事で、上の記事で構築したシステムのイメージを紹介しました。

 * [「Ultra96 向け Debian GNU/Linux (v2018.2版) ブートイメージの提供」@Qiita](https://qiita.com/ikwzm/items/975ab6997905700dd2e0)


この記事では、上の記事で構築したシステムを使って、Vivado-HLS で合成した回路を Linux から動かす例を示します。この記事で紹介する FPGA のデザインやプログラムは次の URL に公開しています。

 * [https://github.com/ikwzm/ZynqMP-FPGA-Linux-Example-2-Ultra96](https://github.com/ikwzm/ZynqMP-FPGA-Linux-Example-2-Ultra96)


Sample FPGA Design
-------------------------------------------------------------

今回動かす回路は 次のように C で記述したプログラムを Vivado-HLS で合成します。見ての通り、in から 32bit integer を読んで、符号反転して out 書き込むだけの簡単な回路です。

```C:negative.c
int negative(volatile int *in, volatile int *out, int size){
#pragma HLS INTERFACE m_axi depth=10 port=out offset=slave
#pragma HLS INTERFACE m_axi depth=10 port=in  offset=slave
#pragma HLS INTERFACE s_axilite port=size
#pragma HLS INTERFACE s_axilite port=return
    int i;

    for (i = 0; i < size; i++){
        out[i] = -in[i];
    }

    return(0);
}
```

in と out は AXI Master Interface を経由して外部のメモリにアクセスします。また、この回路の制御は AXI-Lite Slave Interface を経由して外部プロセッサからレジスタアクセスすることによって行います。

デザインのブロック図は次のようになっています。

![Fig.1 ZynqMP Sample Design](https://qiita-image-store.s3.amazonaws.com/0/24981/82f96053-63b6-33f1-faba-5c2a10bdaf08.png "Fig.1 ZynqMP Sample Design")

Fig.1 ZynqMP Sample Design

このデザインは、[「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Sample FPGA Design編)」](https://qiita.com/ikwzm/items/0489b02962c6f0455783)で説明したサンプルを元に Vivado-HLS で合成した回路を追加しています。

特に注意する点は、Vivado-HLS で合成した回路の AXI Slave Interface (s_sxi_AXILiteS) を ZynqMP の M_AXI_HPM0_FPD に接続することです(AXI Interconnect 等を通じても可)。
なぜなら、Ultra96 向け Debian GNU/Linux (v2018.2版) で提供している boot.bin は、Sample FPGA Design から FSBL をビルドしていて、この FSBL は M_AXI_HPM0_FPD をアクティブにします。ZynqMP の他の AXI Master Interface (M_AXI_HPM1_FPDとM_AXI_HPM0_LPD) はアクティブにしません。試しに Vivado-HLS で合成した回路の AXI Slave Interface (s_sxi_AXILiteS) を ZynqMP の M_AXI_HPM0_LPD に接続しても動きませんでした。

このデザインでは、Vivado-HLS で合成した回路の AXI Master Interface を ZynqMP の HP0 ポートに接続しています。HP0 は キャッシュのコヒーレンシを行わないので Linux で動くアプリケーション側でキャッシュ制御する必要があります。

Ultra96 向け Debian GNU/Linux (v2018.2版) で FPGA にロードするためには、Bitstream File を Binary File に変換する必要があります。Binary File への変換は、次のような bif file を用意して、Vivado-SDK v2018.2 の bootgen コマンドを使います。

```text:negative.bif
all:
{
	[destination_device = pl] negative.bit
}
```

```console
vivado% bootgen -image negative.bif -arch zynqmp -w -o negative.bin
```

Binary File の作り方の詳細は以下の URL を参照してください。

 * http://www.wiki.xilinx.com/Solution+ZynqMP+PL+Programming


準備
-------------------------------------------------------------

### Ultra96 の準備

次の記事を参考に Ultra96 に Debian GNU/Linux をインストールしてください。

 * [「Ultra96 向け Debian GNU/Linux (v2018.2版) ブートイメージの提供」@Qiita](https://qiita.com/ikwzm/items/975ab6997905700dd2e0)

### fpga でログイン

Linux が起動したら ユーザー名 fpga でログインします。パスワードは fpga になっています。

```console
debian-fpga login: fpga
Password:
fpga@debian-fpga:~$
```

### リポジトリのダウンロード

サンプルとなる FPGA デザインやプログラムを下記の URL よりダウンロードします。

 * [https://github.com/ikwzm/ZynqMP-FPGA-Linux-Example-2-Ultra96](https://github.com/ikwzm/ZynqMP-FPGA-Linux-Example-2-Ultra96)

ここでは examples/negative としてダウンロードして、v2018.2.1 をチェックアウトします。

```console
fpga@debian-fpga:~$ mkdir examples
fpga@debian-fpga:~$ cd examples
fpga@debian-fpga:~/examples$ git clone https://github.com/ikwzm/ZynqMP-FPGA-Linux-Example-2-Ultra96 negative
fpga@debian-fpga:~/examples$ cd negative
fpga@debian-fpga:~/examples/negative$ git checkout v2018.2.1
```

FPGA のコンフィギュレーション
-------------------------------------------------------------

### Binary File を /lib/firmware へコピー

FPGA Region では /lib/firmware にコンフィギュレーションするファイルを置いておく必要があります。

```console
fpga@debian-fpga:~/examples/negative$ sudo cp negative.bin /lib/firmware
```

### Device Tree Overlay による FPGA のコンフィギュレーション

FPGA Region では Device Tree を使って FPGA をコンフィギュレーションします。そこで次のような Device Tree Overlay 用のソースファイルを用意します。

```dts:fpga-load.dts
/dts-v1/;
/ {
	fragment@0 {
		target-path = "/fpga-full";
		__overlay__ {
			firmware-name = "negative.bin";
		};
	};
};
```

target-path が "/fpga-full" であることに注意してください。これは Linux 起動時に読み込んだ Device Tree で指定した fpga-region のシンボル名です。

次の手順で Device Tree Overlay による FPGA のコンフィギュレーションを行います。

 1. Device Tree Overlay 用ソースファイル(ここでは fpga-load.dts)を dtc(Device Tree Compiler) を使って dtb (ここでは fpga-load.dtb) に変換します。
 2. /config/device-tree/overlays 下に Device Tree Overlay 用のディレクトリ(ここでは fpga) を作ります。
 3. 2.で作ったディレクトリ下の dtbo に 1. で作った dtb を書き込みます。

これで次のようなカーネルメッセージが出れば、FPGA のコンフィギュレーションは成功です。
 
```console
fpga@debian-fpga:~/examples/negative$ dtc -I dts -O dtb -o fpga-load.dtb fpga-load.dts
fpga@debian-fpga:~/examples/negative$ sudo mkdir /config/device-tree/overlays/fpga
fpga@debian-fpga:~/examples/negative$ sudo cp fpga-load.dtb /config/device-tree/overlays/fpga/dtbo
[  127.413510] fpga_manager fpga0: writing negative.bin to Xilinx ZynqMP FPGA Manager
```

FPGA のクロックの設定
-------------------------------------------------------------

FPGA をコンフィギュレーションするだけでは FPGA 側の回路は動きません。なぜなら FPGA へ供給するクロックの設定が終わっていないからです。

ここでは fclkcfg を使ってクロックを設定します。fclkcfg は筆者が作ったデバイスドライバで、次の URL に公開しています。

 * [https://github.com/ikwzm/fclkcfg](https://github.com/ikwzm/fclkcfg)

[「Ultra96 向け Debian GNU/Linux (v2018.2版) ブートイメージの提供」@Qiita](https://qiita.com/ikwzm/items/975ab6997905700dd2e0) をインストールしてあれば、すでに fclkcfg が組み込まれています。

クロックを設定するには、次のような Device Tree Overlay 用のソースファイルを用意します。

```dts:fclk0-zynqmp.dts
/dts-v1/;/plugin/;
/ {
	fragment@0 {
		target-path = "/amba";
		__overlay__ {
			fclk0 {
				compatible    = "ikwzm,fclkcfg-0.10.a";
				clocks        = <&clk 0x47>;
				insert-rate   = "100000000";
				insert-enable = <1>;
				remove-rate   = "1000000";
				remove-enable = <0>;
			};
		};
	};
};
```

ここでは clocks で ZynqMP の PL CLOCK[0] を指定しています。```&clkc``` はクロック制御ドライバのシンボル名、0x47 は PL CLOCK[0] のインデックス番号です。また、この Device Tree が挿入されたとき PL CLOCK[0] は 周波数 100MHz のクロックを出力するように設定しています。

次の手順で Device Tree Overlay を使ってクロックを設定します。

 1. Device Tree Overlay 用ソースファイル(ここでは fclk0-zynqmp.dts)を dtc(Device Tree Compiler) を使って dtb (ここでは fclk0-zynqmp.dtb) に変換します。
 2. /config/device-tree/overlays 下に Device Tree Overlay 用のディレクトリ(ここでは fclk0) を作ります。
 3. 2.で作ったディレクトリ下の dtbo に 1. で作った dtb を書き込みます。

これで次のようなカーネルメッセージが出れば、FPGA クロックの設定は成功です。

```console
fpga@debian-fpga:~/examples/negative$ dtc -I dts -O dtb -o fclk0-zynqmp.dtb fclk0-zynqmp.dts
fpga@debian-fpga:~/examples/negative$ sudo mkdir /config/device-tree/overlays/fclk0
fpga@debian-fpga:~/examples/negative$ sudo cp fclk0-zynqmp.dtb /config/device-tree/overlays/fclk0/dtbo
fpga@debian-fpga:~/examples/negative$ [  254.415287] fclkcfg: loading out-of-tree module taints kernel.
[  254.424175] fclkcfg amba:fclk0: driver installed.
[  254.428822] fclkcfg amba:fclk0: device name    : fclk0
[  254.433929] fclkcfg amba:fclk0: clock  name    : pl0_ref
[  254.439222] fclkcfg amba:fclk0: clock  rate    : 99999999
[  254.444617] fclkcfg amba:fclk0: clock  enabled : 1
[  254.449371] fclkcfg amba:fclk0: remove rate    : 1000000
[  254.454665] fclkcfg amba:fclk0: remove enable  : 0
```

Uio と Udmabuf の準備
-------------------------------------------------------------

FPGA にコンフィギュレーションした回路にアクセスするには uio を使います。また、データのやりとりは udmabuf を使います。そのために次のような Device Tree Overlay 用のソースファイルを用意します。

```dts:negative.dts
/dts-v1/;/plugin/;
/ {
	fragment@0 {
		target-path = "/amba_pl@0";
		#address-cells = <2>;
		#size-cells = <2>;

		__overlay__ {
			#address-cells = <2>;
			#size-cells = <2>;

			negative-uio {
				compatible = "generic-uio";
				reg = <0x0 0xA0010000 0x0 0x10000>;
				interrupt-parent = <&gic>;
				interrupts = <0 89 4>;
			};

			negative-udmabuf4 {
				compatible  = "ikwzm,udmabuf-0.10.a";
				device-name = "udmabuf4";
				size = <0x00100000>;
			};

			negative-udmabuf5 {
				compatible = "ikwzm,udmabuf-0.10.a";
				device-name = "udmabuf5";
				size = <0x00100000>;
			};
                };
	} ;
} ;
```

uio の レジスタのアドレスは 0xA0010000、サイズは 0x10000 です。これは Vivado でデザインしたときに割り当てたアドレスを指定します。

割り込み番号は ```< 0 89 4>``` を指定します。これは Vivado でデザインしたときに、割り込み信号を ZynqMP の割り込みポート(PL_PS_IRQ0)に接続してますが、その割り込みポートを示します。[『Zynq UltraScale+ Device Technical Reference Manual UG1085 (v1.7) December 22, 2017』](https://www.xilinx.com/support/documentation/user_guides/ug1085-zynq-ultrascale-trm.pdf) の「Table 13-1 System Interrupts」によれば、PL_PS_IRQ0 は割り込み番号(IRQ Number) は 121 です。Device Tree の interrupts プロパティの第2引数 には、この割り込み番号 121 から 32 を引いた値(=89)を指定するようです。（ちなみに、Zynq の場合も Device Tree の interrupts プロパティの第二引数に割り込み番号から32を引いた値を指定しています。ARM の 割り込みコントローラー(GIC)のデバイスドライバの規則のようなものかもしれません。）

次の手順で Device Tree Overlay を使って uio と udmabuf を設定します。

 1. Device Tree Overlay 用ソースファイル(ここでは negative.dts)を dtc(Device Tree Compiler) を使って dtb (ここでは negative.dtb) に変換します。
 2. /config/device-tree/overlays 下に Device Tree Overlay 用のディレクトリ(ここでは negative) を作ります。
 3. 2.で作ったディレクトリ下の dtbo に 1. で作った dtb を書き込みます。

これで次のように /dev/uio1、/dev/udmabuf4、/dev/udmabuf5 が出来れば成功です。

```console
fpga@debian-fpga:~/examples/negative$ dtc -I dts -O dtb -o negative.dtb negative.dts
fpga@debian-fpga:~/examples/negative$ sudo mkdir /config/device-tree/overlays/negative
fpga@debian-fpga:~/examples/negative$ sudo cp negative.dtb /config/device-tree/overlays/negative/dtbo
[  372.919428] udmabuf udmabuf4: driver version = 1.3.2
[  372.924332] udmabuf udmabuf4: major number   = 242
[  372.929113] udmabuf udmabuf4: minor number   = 0
[  372.933691] udmabuf udmabuf4: phys address   = 0x0000000070100000
[  372.939768] udmabuf udmabuf4: buffer size    = 1048576
[  372.944885] udmabuf udmabuf4: dma coherent   = 0
[  372.949489] udmabuf amba_pl@0:negative-udmabuf4: driver installed.
[  372.957472] udmabuf udmabuf5: driver version = 1.3.2
[  372.962364] udmabuf udmabuf5: major number   = 242
[  372.967142] udmabuf udmabuf5: minor number   = 1
[  372.971735] udmabuf udmabuf5: phys address   = 0x0000000070200000
[  372.977812] udmabuf udmabuf5: buffer size    = 1048576
[  372.982930] udmabuf udmabuf5: dma coherent   = 0
[  372.987533] udmabuf amba_pl@0:negative-udmabuf5: driver installed.

fpga@debian-fpga:~/examples/negative$ ls -la /dev/uio*
crw------- 1 root root 244, 0 Nov  4  2016 /dev/uio0
crw------- 1 root root 244, 1 Oct 29 14:52 /dev/uio1
fpga@debian-fpga:~/examples/negative$ ls -la /dev/udmabuf*
crw------- 1 root root 242, 0 Oct 29 14:52 /dev/udmabuf4
crw------- 1 root root 242, 1 Oct 29 14:52 /dev/udmabuf5
```

実際に走らせてみよう
-------------------------------------------------------------

### negative.py

FPGA にロードした回路を動かすために次のようなサンプルプログラムを用意しています。

```python:negative.py
from udmabuf import Udmabuf
from uio     import Uio
import numpy as np
import time

if __name__ == '__main__':
    uio1       = Uio('uio1')
    regs       = uio1.regs()
    udmabuf4   = Udmabuf('udmabuf4')
    udmabuf5   = Udmabuf('udmabuf5')
    test_dtype = np.uint32
    test_size  = min(int(udmabuf4.buf_size/(np.dtype(test_dtype).itemsize)),
                     int(udmabuf5.buf_size/(np.dtype(test_dtype).itemsize)))
  
    udmabuf4_array    = udmabuf4.memmap(dtype=test_dtype, shape=(test_size))
    udmabuf4_array[:] = np.random.randint(-21474836478,2147483647,(test_size))
    udmabuf4.set_sync_to_device(0, test_size*(np.dtype(test_dtype).itemsize))

    udmabuf5_array    = udmabuf5.memmap(dtype=test_dtype, shape=(test_size))
    udmabuf5_array[:] = np.random.randint(-21474836478,2147483647,(test_size))
    udmabuf5.set_sync_to_cpu(   0, test_size*(np.dtype(test_dtype).itemsize))

    total_setup_time   = 0
    total_cleanup_time = 0
    total_xfer_time    = 0
    total_xfer_size    = 0
    count              = 0

    for i in range (0,9):

        start_time  = time.time()
        udmabuf4.sync_for_device()
        udmabuf5.sync_for_device()
        regs.write_word(0x18, udmabuf4.phys_addr & 0xFFFFFFFF)
        regs.write_word(0x20, udmabuf5.phys_addr & 0xFFFFFFFF)
        regs.write_word(0x28, test_size)
        regs.write_word(0x04, 0x000000001)
        regs.write_word(0x08, 0x000000001)
        regs.write_word(0x0C, 0x000000001)
        uio1.irq_on()
        phase0_time = time.time()
        regs.write_word(0x00, 0x000000001)
        uio1.wait_irq()

        phase1_time = time.time()
        regs.write_word(0x0C, 0x000000001)
        udmabuf4.sync_for_cpu()
        udmabuf5.sync_for_cpu()

        end_time     = time.time()
        setup_time   = phase0_time - start_time
        xfer_time    = phase1_time - phase0_time
        cleanup_time = end_time    - phase1_time
        total_time   = end_time    - start_time

        total_setup_time   = total_setup_time   + setup_time
        total_cleanup_time = total_cleanup_time + cleanup_time
        total_xfer_time    = total_xfer_time    + xfer_time
        total_xfer_size    = total_xfer_size    + test_size
        count              = count              + 1
        print ("total:{0:.3f}[msec] setup:{1:.3f}[msec] xfer:{2:.3f}[msec] cleanup:{3:.3f}[msec]".format(round(total_time*1000.0,3), round(setup_time*1000.0,3), round(xfer_time*1000.0,3), round(cleanup_time*1000.0,3)))


    print ("average_setup_time  :{0:.3f}".format(round((total_setup_time  /count)*1000.0,3)) + "[msec]")
    print ("average_cleanup_time:{0:.3f}".format(round((total_cleanup_time/count)*1000.0,3)) + "[msec]")
    print ("average_xfer_time   :{0:.3f}".format(round((total_xfer_time   /count)*1000.0,3)) + "[msec]")
    print ("thougput            :{0:.3f}".format(round(((total_xfer_size/total_xfer_time)/(1000*1000)),3)) + "[MByte/sec]")

    udmabuf4_negative_array = np.negative(udmabuf4_array)
    if np.array_equal(udmabuf4_negative_array, udmabuf5_array):
         print("np.negative(udmabuf4) == udmabuf5 : OK")
    else:
         print("np.negative(udmabuf4) == udmabuf5 : NG")
         count = 0
         for i in range(test_size):
             if udmabuf4_negative_array[i] != udmabuf5_array[i] :
                 count = count + 1
                 if count < 16:
                     print("udmabuf4_negative_array[0x{0:08X}] = 0x{1:08X} udmabuf5_array[0x{0:08X}] = 0x{2:08X}".format(i, udmabuf4_negative_array[i], udmabuf5_array[i]))
         print("NG Count:{0}".format(count))
         
    
```

単純にバッファの大きさ分の 32bit 整数を np.random.randint() で生成して udmabuf4 に用意しておき、回路を起動して、終わるのを割り込みで待ちます。終わったら udmabuf5 に符号変換された結果が格納されているはずなので、negative.py 側で計算しておいた結果と比較して OK / NG を判定しています。

今回は回路の実行時間の他に、キャッシュのフラッシュと無効化にかかった時間も測定しています。

### uio.py

negative.py で import している uio.py は、[「Python と Numpy で UIO を制御」](https://qiita.com/ikwzm/items/d7559858d099c86c350c)で紹介したものです。

### udmabuf.py

negative.py で import している udmabuf.py は次のようなものです。

```python:udmabuf.py
import numpy as np

class Udmabuf:
    """A simple udmabuf class"""

    def __init__(self, name):
        self.name           = name
        self.device_name    = '/dev/%s'               % self.name
        self.class_path     = '/sys/class/udmabuf/%s' % self.name
        self.phys_addr      = self.get_value('phys_addr', 16)
        self.buf_size       = self.get_value('size')
        self.sync_offset    = None
        self.sync_size      = None
        self.sync_direction = None

    def get_value(self, name, radix=10):
        value = None
        for line in open(self.class_path + '/' + name):
            value = int(line, radix)
            break
        return value

    def set_value(self, name, value):
        f = open(self.class_path + '/' + name, 'w')
        f.write(str(value))
        f.close

    def memmap(self, dtype, shape):
        self.item_size = np.dtype(dtype).itemsize
        self.array     = np.memmap(self.device_name, dtype=dtype, mode='r+', shape=shape)
        return self.array

    def set_sync_area(self, direction=None, offset=None, size=None):
        if offset is None:
            self.sync_offset    = self.get_value('sync_offset')
        else:
            self.set_value('sync_offset', offset)
            self.sync_offset    = offset
            
        if size   is None:
            self.sync_size      = self.get_value('sync_size')
        else:
            self.set_value('sync_size', size)
            self.sync_size      = size

        if direction is None:
            self.sync_direction = self.get_value('sync_direction')
        else:
            self.set_value('sync_direction', direction)
            self.sync_direction = direction

    def set_sync_to_device(self, offset=None, size=None):
        self.set_sync_area(1, offset, size)

    def set_sync_to_cpu(self, offset=None, size=None):
        self.set_sync_area(2, offset, size)

    def set_sync_to_bidirectional(self, offset=None, size=None):
        self.set_sync_area(3, offset, size)

    def sync_for_cpu(self):
        self.set_value('sync_for_cpu', 1)

    def sync_for_device(self):
        self.set_value('sync_for_device', 1)

```

基本的には、udmabuf で持っている機能を単純に python のメソッドに割り当てたものです。udmabuf の詳細は以下の URL を参照してください。

 * [https://github.com/ikwzm/udmabuf](https://github.com/ikwzm/udmabuf)
 * [「Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ」 @Qiita](https://qiita.com/ikwzm/items/cc1bb33ff43a491440ea)
 * [「Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ(キャッシュのフラッシュと無効化を追加)」@Qiita](https://qiita.com/ikwzm/items/27bc4f03635cd5c7a22a)

### 実行結果

うまくいくと次のような結果が得られます。

```console
fpga@debian-fpga:~/examples/negative$ sudo python3 negative.py
total:1.352[msec] setup:0.753[msec] xfer:0.031[msec] cleanup:0.568[msec]
total:0.986[msec] setup:0.554[msec] xfer:0.019[msec] cleanup:0.413[msec]
total:0.975[msec] setup:0.545[msec] xfer:0.018[msec] cleanup:0.412[msec]
total:0.978[msec] setup:0.546[msec] xfer:0.019[msec] cleanup:0.413[msec]
total:0.990[msec] setup:0.558[msec] xfer:0.018[msec] cleanup:0.414[msec]
total:0.974[msec] setup:0.543[msec] xfer:0.019[msec] cleanup:0.412[msec]
total:0.976[msec] setup:0.545[msec] xfer:0.018[msec] cleanup:0.412[msec]
total:1.002[msec] setup:0.545[msec] xfer:0.018[msec] cleanup:0.439[msec]
total:8.851[msec] setup:0.547[msec] xfer:7.887[msec] cleanup:0.417[msec]
average_setup_time  :0.571[msec]
average_cleanup_time:0.433[msec]
average_xfer_time   :0.894[msec]
throughput          :293.186[MByte/sec]
np.negative(udmabuf4) == udmabuf5 : OK
```

時々、実行時間がやたらに遅かったりすることがありますが、これに関してはもう少し調査する予定です。

また、キャッシュのフラッシュの時間(setup time)や無効化の時間(cleanup time)がけっこうバカにならないくらいかかっていることが判ります。キャッシュのコヒーレンシをハードウェアで行わずにソフトウェアで行う場合は、この点に注意する必要があります。

後始末
-------------------------------------------------------------

遊び終わったら、Device Tree Overlay で追加したデバイスツリーを、後から追加した順に削除します。

```console
fpga@debian-fpga:~/examples/negative$ sudo rmdir /config/device-tree/overlays/negative/
[  544.713240] udmabuf amba_pl@0:negative-udmabuf5: driver removed.
[  544.719696] udmabuf amba_pl@0:negative-udmabuf4: driver removed.
fpga@debian-fpga:~/examples/negative$ sudo rmdir /config/device-tree/overlays/fclk0/
[  555.221755] fclkcfg amba:fclk0: change rate    : 2777778
[  555.227057] fclkcfg amba:fclk0: change enable  : 0
[  555.231954] fclkcfg amba:fclk0: driver unloaded
fpga@debian-fpga:~/examples/negative$ sudo rmdir /config/device-tree/overlays/fpga/
```

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

 * [「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(イントロ編)」@Qiita](https://qiita.com/ikwzm/items/5ae07792143d8018bfea)
 * [「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Boot Loader編)」@Qiita](https://qiita.com/ikwzm/items/9c78e83298906447fe5d)
 * [「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Sample FPGA Design編)」@Qiita](https://qiita.com/ikwzm/items/0489b02962c6f0455783)
 * [「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Linux Kernel編)」@Qiita](https://qiita.com/ikwzm/items/02af5ac78a558c910e84)
 * [「Ultra96 向け Debian GNU/Linux (v2018.2版) の構築(Debian9 Root File System編)」@Qiita](https://qiita.com/ikwzm/items/be06b07e26cbf05fec2b)
 * [「Ultra96 向け Debian GNU/Linux (v2018.2版) ブートイメージの提供」@Qiita](https://qiita.com/ikwzm/items/975ab6997905700dd2e0)
 * [「Python と Numpy で UIO を制御」@Qiita](https://qiita.com/ikwzm/items/d7559858d099c86c350c)
 * [「Zynq UltraScale+ Device Technical Reference Manual UG1085 (v1.7) December 22, 2017」](https://www.xilinx.com/support/documentation/user_guides/ug1085-zynq-ultrascale-trm.pdf)
 * [https://github.com/ikwzm/ZynqMP-FPGA-Linux-Example-2-UltraZed](https://github.com/ikwzm/ZynqMP-FPGA-Linux-Example-2-UltraZed)
 * [http://www.wiki.xilinx.com/Solution+ZynqMP+PL+Programming](http://www.wiki.xilinx.com/Solution+ZynqMP+PL+Programming)
 * [https://github.com/ikwzm/fclkcfg](https://github.com/ikwzm/fclkcfg)
 * [https://github.com/ikwzm/udmabuf](https://github.com/ikwzm/udmabuf)