第７回PYNQのHDMI出力overlayを作る

## はじめに
base overlayでもHDMIスルーやキャプチャはできたのですが、ちょっと物足りない。せっかくなのでなにか画面を出力する回路を作りたい。小林優著「[FPGAプログラミング大全 Xilinx編](https://www.shuwasystem.co.jp/book/9784798047539.html)」を頼りにして、VGAのテスト信号をHDMIに出力する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)

## 方針

### 道具立て
とりあえず、小林さんの書籍を基にして640x480のVGAのテスト信号を作成する回路をつくり、Digilentさんからvivado用ライブラリとして提供されている「RGB信号をHDMIに出力する[rgb2DVI IPコア](https://github.com/Digilent/vivado-library/tree/master/ip/rgb2dvi)」を使うことにします。

### 構成

(phase 1)テスト信号の出力を目指す
とりあえず、ZynqのPSから制御はいらないのですが、あとあと信号を変えたり、フォーマットを変えたりしたくなるので、AXI GPIOで接続する周辺IPとします。
(phase 2)PYNQから自由に作画できるVRAMをつくる
小林さんの本のとおり。

## テスト信号作成回路をつくる
### プロジェクトをつくる
1. Zynq (ZC7020)の新規プロジェクトをつくる。
2. Settingから開発言語を **VHDL** にかえる

### 回路をつくる

小林さんの書籍の「2-3 PC用ディスプレイにパターンを表示」を参考ににして回路を作り、シミュレーションで動作を確認します。

1. 信号フォーマットを確認する。<br>VGA信号は、ピクセルクロックが25MHz、有効画素640x480ピクセル、その周りに水平・垂直同期期間があるので、信号のフレーム周期は800x525/25MHz=1/59.52です。
2. クロックジェネレータ回路(pckgen)を作成
3. 同期信号とピクセル座標を作成する回路(syncgen)を作成
4. ピクセル座標に対応する信号を作成する回路(pattern)を作成
5. テストベンチ回路を作成
6. チェック

細かな内容は、書籍を参照していただくとして、VHDLで書く時のポイント、書籍とちょっと変えたところなど以下に示します。

### pclgen (VGAクロック作成)
クロックジェネレータで150MHzから25MHzのピクセルクロックを作る。クロックイングウィザードを使わず、直接プリミティブにパラメータを入れてインスタンス化している。
将来、VGA以外のサイズに対応させるときに、周波数を変えねばならないため、分周比などのパラメータはGenericで指定する形が良いかと。

### syncgen (同期信号作成)
verilogからVHDLに変更するために、以下の書き換えを行う。

- 信号フォーマットのパラメータをpackageファイルにまとめる。
- VHDLではverilogのように型変換を自動でしてくれないため、変換を多段階に分解し、各段階に応じた信号を定義する。

### pattern
下記のとおり（課題のgraduationも同様）。

- 書籍のクロックは125MHzだが、AXIクロック100MHzに対応するため、`pclgen`でパラメタライズした値をGenericポートに追加する
- vga2DVIでは、pixcel clock、EN(有効画面)が必要なのでこれらの信号を出力ポートに追加
- 注意点はCLKとPCK(ピクセルクロック）を間違えないようにすること。
- ピクセル座標をもとにピクセルデータを計算するのだが、ラッチするため１クロック遅延が発生する

## overlayをつくる
### プロジェクトをつくる
1. Zynq (ZC7020)の新規プロジェクトをつくる。
2. Settingから開発言語をVHDLにかえる
3. ブロックデザインを作る

### カスタムIP作成

1. メニューバーのTools->Create and Package IP...を選択するとwizardが出てくる
2. Create a new AXI4 peripheralを選択
3. (中略)
4. Edit IPを選択してFinish -->カスタムIPのプロジェクトができる
1. 既に作成した信号作成のファイルを追加する。
2. RGB信号、同期信号、EN、ピクセルクロックのポートを追加して配線する

### rgb2dvi IPの修正と接続
配布されているIPをそのまま結線するとMMCMの実装エラーがでました。VCOの周波数が低すぎるとのこと。marseeさんのFPGAの部屋 [AXI VDMAのMM2Sを使用してビデオ出力6（インプリメントしたがエラー）](http://marsee101.blog.fc2.com/blog-entry-3459.html)を参考にして解決しました。

- エラーはピクセルクロックからピクセルクロックとその５倍のクロックを作成する(ClockGen.vhd)のMMCMで起こっている。
- ピクセルクロック(25MHz)が低すぎてVCOの下限周波数を下回る。
- MMCMのフィードバック係数の値（分周比）を大きくする修正を加えることで、VCOの周波数を高くする。
- VCOの周波数はrgb2dviの設定メニューで変更することができるのだが、配布されたパッケージでは分周比は5(=1x5)もしくは10(=2x5)にしか設定できない。そこで、以下のようにIPコアを修正し25(=5x5)を選択できるようにする。

(IPコアの修正)
1. rgb2dviを右クリックしてIP packagerで開く
2. **Customization Parameters** をクリックし
3. kClkRange の Value Validation List に **25MHz (5)**を追加する。この項を選択すると、(5)を5倍した値(5 x 5 =25)が分周比となるので、25MHzx25=625MHzがVCOの周波数になる。
4. **Re-package**し、IP packagerを終了する

(IPコア修正をプロジェクトに反映)
1. Reportメニューから[Report IP Status]を選び
2. 下段のペインのIP Status タグを選び
3. rgb2dviを選択し　[Upgrade Selected]ボタンをクリック

(rgb2dviへの接続)
R,G,Bの信号をcatで結合し、24bitの信号pDataをつくる。ここで、MSBからR,B,Gの順序で結合すること。
`pData<-R_DATA&B_DATA&G_DATA`

参考　[rgb2dviのドキュメント](https://github.com/Digilent/vivado-library/blob/master/ip/rgb2dvi/docs/rgb2dvi.pdf)
参考2 tom01hさんの[雑多な趣味の記録帳](https://tom01h.exblog.jp/tags/HDMI%20OUT/)


(bitファイルを作成しPYNQ-Z1にコピー)
（略)

### テスト
HDMI TXとディスプレーを接続しておき、Jupyter notebook にて新たに作成したbitファイルを読み込めば、信号が出力される。

## VRAM化
VRAMのメモリアドレスをちゃんと確保してやらないといけません。
第３回 [pythonとPYNQ PL資源へのアクセス](2019-08-15_FPGA_PYNQ_Python_zynq_2fbcf08ba9d29b2452be.md)を参考にしてCMAメモリを確保します。

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

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

```python:uint16の480x640配列バッファを確保
buffer = get_pynq_buffer(shape=(480,640), dtype=np.uint16)
```

```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)
```

あとは、buffer(y,x)にピクセルデータをかくだけ。

## まとめ
質問あれば、更新します。

こちらも参考になりそうです。@iwatake2222 さんの記事[ZYBO (Zynq) でHDMI出力をする](https://qiita.com/iwatake2222/items/b323e129f96426031f9f)