第４回 PYNQのOVERLAYを使ってみた

##はじめに
こんどこそ、ハードウェアリソースへのアクセス。

第１回 [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)


## loading Overlay
こんどこそ。
overlayはbitstreamのことで、ブート時には"base"と呼ばれるオーバレイがPL(Programmable Logic)に読み込まれる。その後新しいオーバレイを読み込むこともできる。オーバーレイはFPGA回路データのbitstream,vivadoのTcl, Python APIを含む。
PYNQのOverlay classはbitstreamファイル名をつかってインスタンスをつくることできる。

```
from pynq import Overlay
overlay = Overlay("base.bit")
```

### base overlay
boot時に読み込まれるオーバレイ。これがデフォルトね。

```
from pynq.overlays.base import BaseOverlay
base_overlay = BaseOverlay("base.bit")
```

読み込みが完了すればpythonからアクセス可能になる。４〜５秒かかる感じ。help(base_overlay)にて、各種機能のhelpが得られる。

```python:こんな感じでhelpを読む
help(base_overlay.leds)
```

```python:LEDへのアクセスはこんな感じ
base_overlay.leds[0].toggle()
```

### partial reconfiguration
部分的な構成変更も可能。この場合ビットストリームとともに「.hwh」ファイルも必要とのこと。とりあえず後回しでいいかな。
[partial reconfiguration](https://pynq.readthedocs.io/en/latest/pynq_overlays/partial_reconfiguration.html)

## PYNQ-Z1のオーバーレイ
### base overlay
サードパーティ製のオーバーレイもあるが、まずbaseをみる。
### ハードウエア対応

- HDMI (Input and Output)  --> {Jupyter dashboard}/base/video
- Microphone in            --> {Jupyter dashboard}/base/audio
- Audio out
- User LEDs, Switches, Pushbuttons -->{Jupyter dashboard}/base/board/board_btns_leds.ipynb
- 2x Pmod PYNQ MicroBlaze
- Arduino PYNQ MicroBlaze
- 3x Trace Analyzer (PMODA, PMODB, ARDUINO)

Audio/videoとLED,SWはサポートするクラスがある。PmodやArduinoインターフェースは、MicroBlase(softマクロ、マイコン)をプログラムしてコントローラにしてアクセスする構成か。

## PYNQ-Z1のvideoのテスト
HDMI入力にMACのディスプレイ出力、出力にHDMIディスプレイをつないでデモをやってみる(http://{IP ADDRESS}:9090/notebooks/base/video/hdmi_introduction.ipynb)

bitストリームのダウンロードは、すこし時間がかかるがすんなり通過。

```
from pynq.overlays.base import BaseOverlay  # BaseOverlayをimport
from pynq.lib.video import *  # video関係もまとめて

base = BaseOverlay("base.bit")  # baseをロード
hdmi_in = base.video.hdmi_in    # hdmi入出力をcreate
hdmi_out = base.video.hdmi_out
```

### パススルーテスト

```
hdmi_in.configure()  # Configure the pipeline to use the specified pixel format.

# configure(self, mode, pixelformat=None)
# Configure the pipeline to use the specified pixel format and size.
# mode : VideoMode
hdmi_out.configure(hdmi_in.mode)

hdmi_in.start()      # Start the pipeline
hdmi_out.start()
```

```
hdmi_in.tie(hdmi_out)  # Mirror the video input on to an output channel
```

ひと仕事おわったら、ちゃんとクローズするようにとのこと。context managerをつかえば、自動でおかたづけできるとのこと。(どうするのかは書いてない）

```
hdmi_out.close()
hdmi_in.close()
```

しかし、これをやったとしても、2例目以降を実行しようとするとJupyterに"kernel restarting"のダイアログがでる。なぜだろう。
### 映像のキャプチャ

```python:オープンしてパススルー
hdmi_in.configure(PIXEL_RGB)  # RGBフォーマット
hdmi_out.configure(hdmi_in.mode, PIXEL_RGB)

hdmi_in.start()
hdmi_out.start()

hdmi_in.tie(hdmi_out)
```

```python:キャプチャ
import PIL.Image  # Pillow : Python Imaging Library

frame = hdmi_in.readframe()  # Read a video frame; See AxiVDMA.S2MMChannel.readframe for details
image = PIL.Image.fromarray(frame)
image
```

Imaging Libraryね。PIL, Pillow, OpenCV, matplotlibの関係はどうなっているのかな。
nkmk(@nkmk_me twitter)さんのnote.nkmk.me [Pythonで画像処理: Pillow, NumPy, OpenCVの違いと使い分け](https://note.nkmk.me/python-image-processing-pillow-numpy-opencv/)に回答があった。
> - Pillow(PIL): 画像処理ライブラリ
> - NumPy: 数値計算ライブラリ
> - OpenCV: コンピュータビジョンライブラリ

`help(hdmi_in.readframe)`するとAxiVDMA.S2MMChannel.readframe()を引けとでた。ヘルプはどこだろう。また見知らぬ略語だ。

- AxiVDMA : AXI Video Direct Memory Access
- S2MM : Stream to Memory Map

なるほど。まるで70年代のアセンブラのラベル。オートコンプリートつかってないのかなぁ。
みつけた [pynq.lib.video Module](https://pynq.readthedocs.io/en/v2.4/pynq_package/pynq.lib/pynq.lib.video.html)

## Audio
同様にサンプルをやってみる。マイクは内蔵なので、スピーカをつなぐ。ちゃんと音もでるし、録音したものをnotebookで再生できる。
レベルは調整したほうがいいかな。
MICはPulse Density Modulation(PDM)で符号化された出力なので、PDM->PCM変換が必要。ΔΣ変調がかかった状態なので、LPFをかければもとの信号を取り出せる。
なお、`help(base.audio.record()`によれば、ワードのサンプリングレートは192000Hz(192kHz)。１ワード16bitなので192x16ksample/sec(1bit)。

PDM MICについてはFreeplanets[PDMマイクからの信号よみとり(IP)](http://freeplanets.ship.jp/FPGA/Tutor/009_IP001_PDM_Mic/PDM_Mic.html)の解説が詳しい

```python:PDM->PCM
from pynq.overlays.base import BaseOverlay
base = BaseOverlay("base.bit")
pAudio = base.audio
pAudio.record(3)
#pAudio.load("Recording_1.pdm")

# ここから変換
import numpy as np

# まずバッファを32bitから16bitに変換(astype(np.int16))し
# 1bit x 16 sample を 8 bit x 16 sampleに変換
af_uint8 = np.unpackbits(pAudio.buffer.astype(np.int16)
                         .byteswap(True).view(np.uint8))

# PDM->PCM
from scipy import signal  # downsampleのためにscipyモジュールをインポート
# signal.decimateは８次チェビシェフ type I のIIRフィルタを用いる。
af_dec = signal.decimate(af_uint8,8,zero_phase=True)  # 1/8
af_dec = signal.decimate(af_dec,6,zero_phase=True)  # 1/6 (1/48)
af_dec = signal.decimate(af_dec,2,zero_phase=True)  # 1/2 (1/96)
# 16 x 192000 [sample/sec] /96 -> 32 [ksample/sec]
af_dec = (af_dec[10:-10]-af_dec[10:-10].mean())  # DC offsetを削除する

del af_uint8

from IPython.display import Audio as IPAudio
IPAudio(af_dec, rate=32000)
```

## LEDs, Switches, Pushbuttons
{Jupyter dashboard}/base/board/board_btns_leds.ipynb をやってみる。これは簡単。

```
from time import sleep
from pynq.overlays.base import BaseOverlay

base = BaseOverlay("base.bit")

for led in base.leds:
    led.off()
sleep(0.5)
base.leds[0].on()
while (base.buttons[3].read()==0):
	if (base.buttons[0].read()==1):
		base.leds[1].toggle();
		sleep(0.1);
for led in base.leds:
    led.off()
```

せっかくなのでもう少し低いレベルまでほってみる。
PYNQ libraries [AxiGPIO](https://pynq.readthedocs.io/en/v2.4/pynq_libraries/axigpio.html)

ふむふむGPIOは、32pinを２チャンネルもてて、LED,Switch,Button,RGBLEDクラスはこれを拡張してカスタマイズしたもの`[led|switch|button|rgbleds]_gpio`。

```python:AxiGPIOの資料より
from pynq import Overlay
from pynq.lib import AxiGPIO
ol = Overlay("base.bit")

led_ip = ol.ip_dict['gpio_leds']
switches_ip = ol.ip_dict['gpio_switches']
leds = AxiGPIO(led_ip).channel1
switches = AxiGPIO(switches_ip).channel1
```

## まとめ
手順は、baseをダウンロードし、しかるべきオブジェクトを介して操作する。

- audioはPDMフォーマットであるから、PCMとの変換が必要
- ledやSwitchは単純にread/writeするだけ
- pmodやシールドはMicroBleaseで制御する
- 割り込みもあるが、とりあえずおいておく