はじめに
次の記事で UltraZed 向け Debian GNU/Linux (v2018.2版) の構築をしました。
- 「UltraZed 向け Debian GNU/Linux (v2018.2版) の構築(イントロ編)」@Qiita
- 「UltraZed 向け Debian GNU/Linux (v2018.2版) の構築(Boot Loader編)」@Qiita
- 「UltraZed 向け Debian GNU/Linux (v2018.2版) の構築(Linux Kernel編)」@Qiita
- 「UltraZed 向け Debian GNU/Linux (v2018.2版) の構築(Debian9 Root File System編)」@Qiita
また、次の記事で、上の記事で構築したシステムのイメージを紹介しました。
この記事では、上の記事で構築したシステムを使って、Vivado-HLS で合成した回路を Linux から動かす例を示します。この記事で紹介する FPGA のデザインやプログラムは次の URL に公開しています。
Sample FPGA Design
今回動かす回路は 次のように C で記述したプログラムを Vivado-HLS で合成します。見ての通り、in から 32bit integer を読んで、符号反転して out 書き込むだけの簡単な回路です。
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
このデザインでは、Vivado-HLS で合成した回路の AXI Master Interface を ZynqMP の HP0 ポートに接続しています。HP0 は キャッシュのコヒーレンシを行わないので Linux で動くアプリケーション側でキャッシュ制御する必要があります。
UltraZed 向け Debian GNU/Linux (v2018.2版) で FPGA にロードするためには、Bitstream File を Binary File に変換する必要があります。Binary File への変換は、次のような bif file を用意して、Vivado-SDK v2018.2 の bootgen コマンドを使います。
all:
{
[destination_device = pl] negative.bit
}
vivado% bootgen -image negative.bif -arch zynqmp -w -o negative.bin
Binary File の作り方の詳細は以下の URL を参照してください。
準備
UltraZed の準備
次の記事を参考に UltraZed に Debian GNU/Linux をインストールしてください。
fpga でログイン
Linux が起動したら ユーザー名 fpga でログインします。パスワードは fpga になっています。
debian-fpga login: fpga
Password:
fpga@debian-fpga:~$
リポジトリのダウンロード
サンプルとなる FPGA デザインやプログラムを下記の URL よりダウンロードします。
ここでは examples/negative としてダウンロードして、v2018.2 をチェックアウトします。
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-UltraZed negative
fpga@debian-fpga:~/examples$ cd negative
fpga@debian-fpga:~/examples/negative$ git checkout v2018.2
FPGA のコンフィギュレーション
Binary File を /lib/firmware へコピー
FPGA Region では /lib/firmware にコンフィギュレーションするファイルを置いておく必要があります。
fpga@debian-fpga:~/examples/negative$ sudo cp negative.bin /lib/firmware
Device Tree Overlay による FPGA のコンフィギュレーション
FPGA Region では Device Tree を使って FPGA をコンフィギュレーションします。そこで次のような Device Tree Overlay 用のソースファイルを用意します。
/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 のコンフィギュレーションを行います。
- Device Tree Overlay 用ソースファイル(ここでは fpga-load.dts)を dtc(Device Tree Compiler) を使って dtb (ここでは fpga-load.dtb) に変換します。
- /config/device-tree/overlays 下に Device Tree Overlay 用のディレクトリ(ここでは fpga) を作ります。
- 2.で作ったディレクトリ下の dtbo に 1. で作った dtb を書き込みます。
これで次のようなカーネルメッセージが出れば、FPGA のコンフィギュレーションは成功です。
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
[ 56.218477] fpga_manager fpga0: writing negative.bin to Xilinx ZynqMP FPGA Manager
FPGA のクロックの設定
FPGA をコンフィギュレーションするだけでは FPGA 側の回路は動きません。なぜなら FPGA へ供給するクロックの設定が終わっていないからです。
ここでは fclkcfg を使ってクロックを設定します。fclkcfg は筆者が作ったデバイスドライバで、次の URL に公開しています。
「UltraZed 向け Debian GNU/Linux ブートイメージの提供」をインストールしてあれば、すでに fclkcfg が組み込まれています。
クロックを設定するには、次のような Device Tree Overlay 用のソースファイルを用意します。
/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 のクロックを出力するように設定しています。
なお、「UltraZed 向け Debian GNU/Linux (v2017.3版) で Vivado-HLS を使って合成した回路を動かす」 で説明した fclk0-zynqmp.dts とは clocks の記述が違うことに注意してください。v2017.3版では <&clkc 0x47>
でしたが、v2018.3版では <&clk 0x47>
となっています。
次の手順で Device Tree Overlay を使ってクロックを設定します。
- Device Tree Overlay 用ソースファイル(ここでは fclk0-zynqmp.dts)を dtc(Device Tree Compiler) を使って dtb (ここでは fclk0-zynqmp.dtb) に変換します。
- /config/device-tree/overlays 下に Device Tree Overlay 用のディレクトリ(ここでは fclk0) を作ります。
- 2.で作ったディレクトリ下の dtbo に 1. で作った dtb を書き込みます。
これで次のようなカーネルメッセージが出れば、FPGA クロックの設定は成功です。
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
[ 111.238976] fclkcfg amba:fclk0: driver installed.
[ 111.243617] fclkcfg amba:fclk0: device name : fclk0
[ 111.248737] fclkcfg amba:fclk0: clock name : pl0
[ 111.253678] fclkcfg amba:fclk0: clock rate : 99999999
[ 111.259085] fclkcfg amba:fclk0: clock enabled : 1
[ 111.263833] fclkcfg amba:fclk0: remove rate : 1000000
[ 111.269125] fclkcfg amba:fclk0: remove enable : 0
Uio と Udmabuf の準備
FPGA にコンフィギュレーションした回路にアクセスするには uio を使います。また、データのやりとりは udmabuf を使います。そのために次のような Device Tree Overlay 用のソースファイルを用意します。
/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 0x80010000 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 の レジスタのアドレスは 0x80010000、サイズは 0x10000 です。これは Vivado でデザインしたときに割り当てたアドレスを指定します。
割り込み番号は < 0 89 4>
を指定します。これは Vivado でデザインしたときに、割り込み信号を ZynqMP の割り込みポート(PL_PS_IRQ0)に接続してますが、その割り込みポートを示します。『Zynq UltraScale+ Device Technical Reference Manual UG1085 (v1.7) December 22, 2017』 の「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 を設定します。
- Device Tree Overlay 用ソースファイル(ここでは negative.dts)を dtc(Device Tree Compiler) を使って dtb (ここでは negative.dtb) に変換します。
- /config/device-tree/overlays 下に Device Tree Overlay 用のディレクトリ(ここでは negative) を作ります。
- 2.で作ったディレクトリ下の dtbo に 1. で作った dtb を書き込みます。
これで次のように /dev/uio1、/dev/udmabuf4、/dev/udmabuf5 が出来れば成功です。
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
[ 164.123998] udmabuf amba_pl@0:negative-udmabuf4: driver probe start.
[ 164.131871] udmabuf udmabuf4: driver installed
[ 164.136254] udmabuf udmabuf4: major number = 244
[ 164.141021] udmabuf udmabuf4: minor number = 0
[ 164.145619] udmabuf udmabuf4: phys address = 0x0000000070400000
[ 164.151689] udmabuf udmabuf4: buffer size = 1048576
[ 164.156811] udmabuf udmabuf4: dma coherent = 0
[ 164.161412] udmabuf amba_pl@0:negative-udmabuf4: driver installed.
[ 164.167998] udmabuf amba_pl@0:negative-udmabuf5: driver probe start.
[ 164.175758] udmabuf udmabuf5: driver installed
[ 164.180142] udmabuf udmabuf5: major number = 244
[ 164.184917] udmabuf udmabuf5: minor number = 1
[ 164.189505] udmabuf udmabuf5: phys address = 0x0000000070500000
[ 164.195576] udmabuf udmabuf5: buffer size = 1048576
[ 164.200698] udmabuf udmabuf5: dma coherent = 0
[ 164.205298] udmabuf amba_pl@0:negative-udmabuf5: driver installed.
fpga@debian-fpga:~/examples/negative$ ls -la /dev/uio*
crw------- 1 root root 245, 0 Jan 8 18:07 /dev/uio0
crw------- 1 root root 245, 0 Jan 8 18:07 /dev/uio1
fpga@debian-fpga:~/examples/negative$ ls -la /dev/udmabuf*
crw------- 1 root root 244, 0 Jan 8 18:07 /dev/udmabuf4
crw------- 1 root root 244, 1 Jan 8 18:07 /dev/udmabuf5
実際に走らせてみよう
negative.py
FPGA にロードした回路を動かすために次のようなサンプルプログラムを用意しています。
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 を制御」で紹介したものです。
udmabuf.py
negative.py で import している 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
- 「Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ」 @Qiita
- 「Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ(キャッシュのフラッシュと無効化を追加)」@Qiita
実行結果
うまくいくと次のような結果が得られます。
fpga@debian-fpga:~/examples/negative$ sudo python3 negative.py
total:1.514[msec] setup:0.777[msec] xfer:0.197[msec] cleanup:0.540[msec]
total:1.299[msec] setup:0.610[msec] xfer:0.183[msec] cleanup:0.506[msec]
total:1.254[msec] setup:0.606[msec] xfer:0.184[msec] cleanup:0.464[msec]
total:1.251[msec] setup:0.603[msec] xfer:0.182[msec] cleanup:0.466[msec]
total:1.263[msec] setup:0.604[msec] xfer:0.197[msec] cleanup:0.462[msec]
total:1.081[msec] setup:0.604[msec] xfer:0.020[msec] cleanup:0.457[msec]
total:8.963[msec] setup:0.601[msec] xfer:7.903[msec] cleanup:0.459[msec]
total:1.075[msec] setup:0.598[msec] xfer:0.020[msec] cleanup:0.457[msec]
total:1.084[msec] setup:0.607[msec] xfer:0.020[msec] cleanup:0.458[msec]
average_setup_time :0.623[msec]
average_cleanup_time:0.474[msec]
average_xfer_time :0.990[msec]
throughput :264.900[MByte/sec]
np.negative(udmabuf4) == udmabuf5 : OK
時々、実行時間がやたらに遅かったりすることがありますが、これに関してはもう少し調査する予定です。
また、キャッシュのフラッシュの時間(setup time)や無効化の時間(cleanup time)がけっこうバカにならないくらいかかっていることが判ります。キャッシュのコヒーレンシをハードウェアで行わずにソフトウェアで行う場合は、この点に注意する必要があります。
後始末
遊び終わったら、Device Tree Overlay で追加したデバイスツリーを、後から追加した順に削除します。
fpga@debian-fpga:~/examples/negative$ sudo rmdir /config/device-tree/overlays/netagive
[ 749.266776] udmabuf udmabuf5: driver uninstalled
[ 749.271568] udmabuf amba_pl@0:negative-udmabuf5: driver unloaded
[ 749.277680] udmabuf udmabuf4: driver uninstalled
[ 749.282536] udmabuf amba_pl@0:negative-udmabuf4: driver unloaded
fpga@debian-fpga:~/examples/negative$ sudo rmdir /config/device-tree/overlays/fclk0
[ 760.491074] fclkcfg amba:fclk0: change rate : 992064
[ 760.496344] fclkcfg amba:fclk0: change enable : 0
[ 760.501388] fclkcfg amba:fclk0: driver unloaded
fpga@debian-fpga:~/examples/negative$ sudo rmdir /config/device-tree/overlays/fpga
参考
- 「UltraZed 向け Debian GNU/Linux (v2018.2版) の構築(イントロ編)」@Qiita
- 「UltraZed 向け Debian GNU/Linux (v2018.2版) の構築(Boot Loader編)」@Qiita
- 「UltraZed 向け Debian GNU/Linux (v2017.3版) の構築(Sample FPGA Design編)」@Qiita
- 「UltraZed 向け Debian GNU/Linux (v2018.2版) の構築(Linux Kernel編)」@Qiita
- 「UltraZed 向け Debian GNU/Linux (v2018.2版) の構築(Debian9 Root File System編)」@Qiita
- 「UltraZed 向け Debian GNU/Linux (v2018.2版) ブートイメージの提供」@Qiita
- 「UltraZed 向け Debian GNU/Linux (v2018.2版) で FPGA をコンフィギュレーション」@Qiita
- 「Python と Numpy で UIO を制御」@Qiita
- 「Zynq UltraScale+ Device Technical Reference Manual UG1085 (v1.7) December 22, 2017」
- https://github.com/ikwzm/ZynqMP-FPGA-Linux-Example-2-UltraZed
- http://www.wiki.xilinx.com/Solution+ZynqMP+PL+Programming
- https://github.com/ikwzm/fclkcfg
- https://github.com/ikwzm/udmabuf