第３回 pythonとPYNQ PL資源へのアクセス

## はじめに
PYNQが動き、Jupyter notebookの使い方がわかったところで、いよいよロジック資源へのアクセス方法へ。

第１回 [Setup PYNQ-Z1](https://qiita.com/0x20FE/items/9b5e8e7a51ac0d9eabbc)
第２回 [はじめてのJupyter](https://qiita.com/0x20FE/items/7fa2fa59c930c571f197)
第３回 [pythonとPYNQ PL資源へのアクセス](2019-08-15_FPGA_PYNQ_Python_zynq_2fbcf08ba9d29b2452be.md)
第４回 [PYNQ OVERLAY](2019-08-15_FPGA_PYNQ_Python_zynq_36755b7e5b60cfbfec87.md)
第５回 [PYNQのOverlayを作ってみた](https://qiita.com/0x20FE/private/81098851f8c8010ca2c8)
第６回 [カスタムIPを作ってPYNQ overlayに組み込む](2019-08-22_FPGA_PYNQ_zynq_ece9f0cc0340763dde61.md)
第７回 [PYNQのHDMI出力overlayを作る](2019-08-24_FPGA_PYNQ_zynq_820e68d300dd978ad21c.md)
番外編　[python ジャンプスタート](https://qiita.com/0x20FE/items/6f8b6d893b9c5c197c5b)

更新 2019/9/22 Numpyのデータをロジックと共有する方法

## Python Environment
今回よむのは[Python Environment](https://pynq.readthedocs.io/en/v2.4/python_environment.html)。最初の例は素数を数えるpythonのコードの例。簡単にコードがかけるというデモ。
次の例が本題。

## *[重要]* Numpyのデータをロジックと共有する方法
Xlnkモジュールを用いると、ARMプロセッサのメモリ空間にpythonのnumpy パッケージでアクセスできるバッファを作ることができます。そのバッファの物理アドレスのポインタを取り出し、GPIOを経由してロジックに知らせることで、ARMとロジックでデータを共有します。

```python:バッファ取得関数の定義
import numpy as np
import pynq

def get_pynq_buffer(shape, dtype):
    return pynq.Xlnk().cma_array(shape, dtype)
```

この関数でnumpyからもロジック領域からもアクセスできる連続領域を確保。

<!-- CMA:Continuous Memory Allocator -->

この関数を用いて4x4のuint32のメモリを確保して、その物理アドレスをGPIOを経由してロジックの渡すことで、ARMとロジックの間でデータを共有することができるようになります。

```python:uint32の4x4配列バッファを確保
buffer = get_pynq_buffer(shape=(4,4), dtype=np.uint32)
```

```python:物理アドレスを16進数で確認するには
pl_buffer_address = hex(buffer.physical_address)
```

MMIOを用いて、GPIO経由で物理アドレスを渡す。MMIOは第５回 [PYNQのOverlayを作ってみた](https://qiita.com/0x20FE/private/81098851f8c8010ca2c8)を参照。

```python:GPIO_0でbufferの物理アドレスを送る例
from pynq import MMIO
mmio=MMIO(int(PL.ip_dict["axi_gpio_0"]['phys_addr']),4,True)
mmio.write(0, buffer.physical_address)
```

ロジックからは物理アドレスへの参照は、ロジックがマスターとなるAXIバス経由でDDRメモリにアクセスする。

## 非同期IOの統合

### サンプルを読む
サンプルはこんな感じ

```py
import asyncio
import random
import time

# Coroutine
async def wake_up(delay):
    '''数秒sleepして起きる関数
    '''
    start_time = time.time()
    print(f'The time is: {time.strftime("%I:%M:%S")}')

    print(f"Suspending coroutine 'wake_up' at 'await` statement\n")
    await asyncio.sleep(delay)

    print(f"Resuming coroutine 'wake_up' from 'await` statement")
    end_time = time.time()
    sleep_time = end_time - start_time
    print(f"'wake-up' was suspended for precisely: {sleep_time} seconds")
```

時刻を記録し、指定した時間だけスリープし、wakeしたらwake時刻を記録し、その差分から実際にsleepしていた時間を表示するのか。ふむふむ。えっ！コルーチンってなに？
### coroutine
(参考1) @kawmraさん [Kotlin の Coroutine を概観する](https://qiita.com/kawmra/items/ee4acb7db61f70dec9a8)
ふむふむ。非同期呼び出しするサブルーチンのようなものかな。スレッドは、初期化やら起動停止、イベントやセマフォやらなにかと手間がかかる。そのあたりが簡単に。
(参考2) @icoxfog417さん [Pythonにおける非同期処理: asyncio逆引きリファレンス](https://qiita.com/icoxfog417/items/07cbf5110ca82629aca0)

- async付きで定義した関数を呼び出すと、corutineが戻り、実行はされない。theadの宣言みたいなものか。
- corutineの関数の中ではawait でいろいろイベント待ちをする感じ。pthread_wait_condとか、mutexの類かな。
- asyncio.get_event_loop()は、タスクスイッチャみたいなものか。

pthreadと対応づけた解説があればいいのに。このへんにして、またあとで読もう。


```py
delay = random.randint(1,5)
my_event_loop = asyncio.get_event_loop()

try:
    print("Creating task for coroutine 'wake_up'\n")
    wake_up_task = my_event_loop.create_task(wake_up(delay))
    my_event_loop.run_until_complete(wake_up_task)
except RuntimeError as err:
    print (f'{err}' +
        ' - restart the Jupyter kernel to re-run the event loop')
finally:
    my_event_loop.close()
```

あれ？割り込みの話はどこ？

## まとめ
このページ、PYNQのPLとの関係は ```get_pynq_buffer()```だけだった。次にいこう！

<!--
###余談
シングルプロセス、シングルスレッドでうん千セッションさばくなんて、なつかしい。proxy サーバー界の革命児だった、初期のsquidがまさにこれ。うん千個のソケットをノンブロッキングにしてソケットのR/Wをselectで待ち受け、状態遷移マシン＋イベントドリブンでぶん回してた。serverがfork()して頑張る従来のproxyサーバとは雲泥の差だったなぁ。早いのはいいけれど、イベントを追っかけるのが大変で、ソースを読むのはとてもしんどかった。threadの登場でfork()がなくなり、みとおしがよくなり万々歳だったのですが、それも重く感じられる時代なのですね。coroutineでみとおしがよくて軽いなら、そっちに行くでしょう。時代は巡る。
-->