第６回 カスタムIPを作ってPYNQ overlayに組み込む

## はじめに
前回PYNQのOverlayまでつくることができました。いろいろなハードウェア回路を作って、ソフトからアクセスするには、これらの回路にAXIインターフェースをつけて、カスタムIPとして登録しなければなりません。
ちょっと敷居がたかそうですが、@kamotsuruさんの [PYNQ-Z1でカスタムIP作成してLEDチカ](https://qiita.com/kamotsuru/items/0a04bd696006880b4e68)をなぞって、試してみました。なお、VHDL派ですので、ちょっとだけ変えましたが。

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

## プロジェクトとブロックデザインの作成
これは、第５回 [PYNQのOverlayを作ってみた](https://qiita.com/0x20FE/private/81098851f8c8010ca2c8)のZynqのピンの接続までは同じです。

### 開発言語をverilogからVHDLに変更
わたしは、VHDLの方がなれているので、デフォルトの開発言語をverilogからVHDLに変えます。

1. flow navigatorのSettingをクリックして、Setting ダイヤログを表示
2. Project SettingのGeneralを選択しGeneralペインを表示
3. Target LanguageをVHDLに変更してOK

### カスタムIP作成
ここからは、@kamotsuruさんの[PYNQ-Z1でカスタムIP作成してLEDチカ](https://qiita.com/kamotsuru/items/0a04bd696006880b4e68)の3以降と、小林優さんの書籍[FPGAプログラミング大全 Xilinx編](https://www.shuwasystem.co.jp/book/9784798047539.html)など参考にしました。

1. メニューバーのTools->Create and Package IP...を選択するとwizardが出てくる
2. Create a new AXI4 peripheralを選択
3. (中略)
4. Edit IPを選択してFinish
カスタムIPのプロジェクトができる

### カスタムIPプロジェクトで回路を作成
AXIインターフェスを記述する回路(IP名_v1_0_S00_AXI.vhd)とそのラッパー回路(IP名_v1_0.vhd)を書き換える。

このIPは基盤上の次のLEDとボタンと接続する。

- 出力はled[3:0],rgbled4[2:0],rgbled5[2:0]
- 入力はbtn[3:0]

３２ビットレジスタへの割りつけは

- レジスタ０（アドレス０）下位４ビット出力にLED
- レジスタ１（アドレス４）下位３ビット出力にLED４
- レジスタ２（アドレス８）下位３ビット出力にLED５
- レジスタ０（アドレス０）19から16ビット入力にbtn

とします。

```vhdl:IP名_v1_0_S00_AXI.vhd
entity IP名_v1_0_S00_AXI is
PORT(
	-- users to add ports here
	btn: in std_logic_vector(3 downto 0);
	led: out std_logic_vector(3 downto 0);
	rgbled4: out std_logic_vector(2 downto 0);
	rgbled5: out std_logic_vector(2 downto 0);
	-- User ports end
	...
	)
end IP名_v1_0_S00_AXI;

architecture arch_imp of xxxx
   ...
begin
   ...
   -- 読み出し
   case loc_addr is
   when b"00" =>
       reg_data_out <=X"000" & btn & X"0000";
   ...

   -- 書き込み
   -- Add user logic here
   led <= slv_reg0( 3 downto 0);
   rgbled4<=slv_reg1( 2 downto 0);
   rgbled5<=slv_reg2( 2 downto 0);
   -- Use logic ends
end arch_imp
```

ラッパーのvhdファイルにもポートを追加して配線する(略)

### カスタムIPのパッケージを整える

- Package IPペインから、Customization Parametersを選択
- 右上ののMerge changes from~をクリック
- Cutomization GUIを選択して端子の追加を確認
- Review and Packageを選択し、Re Package IP

### カスタムIPをブロックデザインに追加し
（略）
### 端子を配線しValidate Design
（略）
### Create HDL Wrapper
Sources->Design Sources->[ブロックデザイン]を右クリックして **Create HDL Wrapper**
（略）
### 制約ファイルを作成
PYNQの制約ファイルをcopyしてきて、btn,led,rgbledを有効化する
なお、RGB LEDの記述を簡単にするためRGBをstd_logic_vector形式に変更する。具体的には`led4_r` を`rgbled4[2]`のように、R,G,Bの信号３本をまとめて、配列にしrgbled4[2],rgbled4[1],rgbled4[0]のようにリネームする。

### ビットストリームを作成
（略）
### tcl,hwh,ビットストリームをコピー
tcl,hwhは`プロジェクト名.src/source_1/bd/デザイン名/hw_handoff/`の中にあります。

## Jupyterからアクセス
基本は@kamotsuruさんの記事を参考に

```python:PLをインポート
from pynq import Overlay
from pynq import PL
OL = Overlay("/home/xilinx/pynq/overlays/適当なフォルダ/design_1.bit")
```

ip_dictでIPの名前情報を確認する

```python:GPIOの名前を確認
PL.ip_dict
```

Memory Mapped IOでアクセスする。

```python:MemoryMapedIOでアクセス
from pynq import MMIO
mmio=MMIO(int(PL.ip_dict["myIP_0"]['phys_addr']),12,True)
mmio.write(0,15)  # LED0から3を点灯
mmio.write(4,6)   # LED4を黄色点灯
mmio.write(8,4)   # LED5を赤色点灯
mmio.read(0)>>16  # btn0からbtn3を読み出す
```

## カスタムIPの再編集
カスタムIPの中身を再編集する場合は、IP Packagerから行います。これには、次の手順を踏む必要があります。

- IPカタログをクリック
- user repositoryからカスタムIPを選択
- 右クリックしてEdit in IP Packager

## まとめ

- Tools->Create and Package IP...からCreate a new AXI4 peripheralでAXI4のカスタムIPプロジェクトを作成する
- portを追加する
- AXIの出力レジスタ、読み出しポートを周辺回路と結線する
- カスタムIPをブロックデザインに追加する
- overlayファイルを作成する
- Jupyterからip_dictでIPの名前情報を確認する
- IPの名前と物理アドレスを用いてMMIOオブジェクトを作成
- MMIOを経由して入出力をおこなう