Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ（NumPy対応）

## はじめに

前回投稿した[「Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ](http://qiita.com/ikwzm/items/cc1bb33ff43a491440ea)で紹介した[udmabuf](https://github.com/ikwzm/udmabuf)を NumPy(Pythonの数値計算ライブラリ)で使えるようにしました。具体的には、udmabuf でカーネル内に確保したバッファ領域を NumPy の memmap でマッピングして、ndarray と同じような操作が出来るようにします。この記事では、その方法について説明します。

## udmabuf の更新

残念ながら udmabuf のバージョンが version 0.5.0(2016/4/24) 以前では、次のようなエラーが出ます。

```
shell# python
Python 2.7.9 (default, Aug 13 2016, 17:56:53)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> m = np.memmap('/dev/udmabuf0', dtype=np.uint8, mode='r+', shape=(100))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/dist-packages/numpy/core/memmap.py", line 217, in __new__
    fid.seek(0, 2)
IOError: [Errno 29] Illegal seek
```

これは udmabuf が lseek に対応していなかったのが原因です。どうやら NumPy の memmap は seek(0,2) を使ってファイルのサイズを得ているようですが、私が udmabuf に lseek() を実装するのを忘れていました。
udmabuf version 0.6.0(2017/1/29) で lseek に対応しましたので、NumPy で udmabuf を使う場合は、umdabuf version 0.6.0 以降を使ってください。

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

## 使用例

### udmabuf のインストール

まずは udmabuf をインストールします。具体的な方法は、[「Linuxでユーザー空間で動作するプログラムとハードウェアがメモリを共有するためのデバイスドライバ](http://qiita.com/ikwzm/items/cc1bb33ff43a491440ea) を参照してください。

次の例では udmabuf.ko を直接 insmod でインストールしています。その際、udmabuf0 として 8MByte のバッファを確保しています。

```
shell# insmod udmabuf.ko udmabuf0=8388608
[34654.590294] alloc_contig_range: [1f100, 1f900) PFNs busy
[34654.596154] alloc_contig_range: [1f200, 1fa00) PFNs busy
[34654.622746] udmabuf udmabuf0: driver installed
[34654.627153] udmabuf udmabuf0: major number   = 237
[34654.631889] udmabuf udmabuf0: minor number   = 0
[34654.636685] udmabuf udmabuf0: phys address   = 0x1f300000
[34654.642002] udmabuf udmabuf0: buffer size    = 8388608
```

### udmabuf_test.py

次に python+NumPy による簡単なテストをするスクリプトを示します。

```python:udmabuf_test.py
import numpy as np
import time

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
        for line in open(self.class_path + '/size'):
            self.buf_size = int(line)
            break

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

def test_1(a):
    for i in range (0,9):
        a *= 0
        a += 0x31

if __name__ == '__main__':
    udmabuf      = Udmabuf('udmabuf0')
    test_dtype   = np.uint8
    test_size    = int(udmabuf.buf_size/(np.dtype(test_dtype).itemsize))
    udmabuf.memmap(dtype=test_dtype, shape=(test_size))
    comparison   = np.zeros(test_size, dtype=test_dtype)
    print ("test_size  : %d" % test_size)
    start        = time.time()
    test_1(udmabuf.mem_map)
    elapsed_time = time.time() - start
    print ("udmabuf0   : elapsed_time:{0}".format(elapsed_time) + "[sec]")
    start        = time.time()
    test_1(comparison)
    elapsed_time = time.time() - start
    print ("comparison : elapsed_time:{0}".format(elapsed_time) + "[sec]")
    if np.array_equal(udmabuf.mem_map, comparison):
        print ("udmabuf0 == comparison : OK")
    else:
        print ("udmabuf0 != comparison : NG")
    
```

上記スクリプトでは、udmabuf でカーネル内に確保したバッファ領域を numpy.memmap で python から使えるようにしています。
numpy.memmap で生成されたオブジェクトは numpy.ndarray と同様の操作ができるようになります。
上記スクリプトでは a *=0 と a += 0x31 を10回繰り返して、実行時間を測定しています。

### 実行結果

前節のスクリプトを実行すると次のような結果になりました。

```
shell# python udmabuf_test.py
test_size  : 8388608
udmabuf0   : elapsed_time:1.53304982185[sec]
comparison : elapsed_time:1.536673069[sec]
udmabuf0 == comparison : OK
```

udmabuf0(カーネル内に確保したバッファ領域)に対する操作と、ndarray で同じ操作を行った場合(comparison)の実行時間はほぼ同じでした。すなわち udmabuf0 も CPU キャッシュが有効に働いていると思われます。

このスクリプトを実行した後で、udmabuf0 の内容を確認しました。

```
shell# dd if=/dev/udmabuf0 of=udmabuf0.bin bs=8388608
1+0 records in
1+0 records out
8388608 bytes (8.4 MB) copied, 0.151531 s, 55.4 MB/s
shell# 
shell# od -t x1 udmabuf0.bin
0000000 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31
*
40000000
```

スクリプト実行後も、実行した結果がバッファに残っていることが確認できました。
念のため、NumPy でも読めることを確認しましょう。

```
shell# python
Python 2.7.9 (default, Aug 13 2016, 17:56:53)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> a = np.memmap('/dev/udmabuf0', dtype=np.uint8, mode='r+', shape=(8388608))
>>> a
memmap([49, 49, 49, ..., 49, 49, 49], dtype=uint8)
>>> a.itemsize
1
>>> a.size
8388608
>>>
```


## まとめ

udmabuf を使ってカーネル内に確保したバッファ領域を python の NumPy で操作できるようになりました。これにより C で書かれたコードやライブラリを使うこと無く、python だけで 直接 FPGA の PL(Programmable Logic)部を扱えるようになります。

FPGA の PL(Programmable Logic)部 が NumPy 形式に対応することが出来れば、今よりももう少し簡単に CPU(Python+NumPy) と FPGA アクセラレータとが連携できるようになるでしょう。