PYNQ で遊ぶ : 5. BRAM を使う (3)

# これは何か

PYNQ-Z1 を使って遊んでみます。

今回は、下記ができるようになる事を目指します。

- BRAM を使ってみる
    - （1）[AXI BRAM Controller を使って python から読み書きしてみる](https://qiita.com/nishimuraatsushi/items/4d31d397c0704ab09fdb)
    - （2）[自作 IP を接続して、FPGA 内部で生成したデータを python から読み出してみる](https://qiita.com/nishimuraatsushi/items/ffb001501e91434629ae)
    - (本記事) BRAM を ROM として使ってみる


##### 関連記事：

- [PYNQ で遊ぶ : 1. LED を点滅させる](https://qiita.com/nishimuraatsushi/items/3e5f161eca4b2a0f06c8)
- [PYNQ で遊ぶ : 2. Verilog HDL を使ってみる](https://qiita.com/nishimuraatsushi/items/e05b7185de463c0b006a)
- [PYNQ で遊ぶ : 3. BRAM を使う (1)](https://qiita.com/nishimuraatsushi/items/4d31d397c0704ab09fdb)
- [PYNQ で遊ぶ : 4. BRAM を使う (2)](https://qiita.com/nishimuraatsushi/items/ffb001501e91434629ae)


# 5-1. プロジェクトの作成
- PYNQ や Vivado のセットアップがまだな場合
    - [手順 1-2. 開発の準備](https://qiita.com/nishimuraatsushi/items/3e5f161eca4b2a0f06c8#1-2-%E9%96%8B%E7%99%BA%E3%81%AE%E6%BA%96%E5%82%99) を参考に済ませておきます

- [手順 1-3. プロジェクトの作成](https://qiita.com/nishimuraatsushi/items/3e5f161eca4b2a0f06c8#1-3-%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90) を参考に、新規プロジェクトを作成します
    - Project name : asobu05
    - Project location : ~/vivado/asobu
    - にしました。

- [手順 1-4-1. ZYNQ の IP を配置する](https://qiita.com/nishimuraatsushi/items/3e5f161eca4b2a0f06c8#1-4-1-zynq-%E3%81%AE-ip-%E3%82%92%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B) を参考に、Block Design を作ります
    - 名前は design_1 のままにしました
    - ZYNQ も配置しておきます


# 5-2. 自作モジュールの作成
今回は、次の構成をゴールにします。

- BRAM を ROM として設置
- 自作モジュールで、
    - GPIO から、要求するアドレスを受け取り、
    - BRAM-ROM から要求アドレスのデータを取り出し、
    - GPIO へ返す

## 5-2-1. 自作モジュールを編集する

- [手順 2-2-1. Verilog HDL のファイルを追加します](https://qiita.com/nishimuraatsushi/items/e05b7185de463c0b006a#2-2-1-verilog-hdl-%E3%81%AE%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%97%E3%81%BE%E3%81%99) を参考に verilog HDL ファイルを追加します。
    - ファイル名は、bram_loader.v にしました。

- [手順 2-2-2. Verilog HDL のファイルを編集します](https://qiita.com/nishimuraatsushi/items/e05b7185de463c0b006a#2-2-2-verilog-hdl-%E3%81%AE%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E7%B7%A8%E9%9B%86%E3%81%97%E3%81%BE%E3%81%99) を参考に、ファイルを編集します

```verilog:bram_loader.v
module bram_loader
  (
    input wire CLK,
    
    input wire [31:0] req_addr,
    output reg [31:0] data,
    
    output wire bram_clk,
    output reg [31:0] bram_addr,
    input wire [31:0] bram_data
  );
  
  assign bram_clk = CLK;
  
  always @(posedge CLK)
    begin
      bram_addr <= req_addr;      
      // 今回は、アドレス指示値に、末尾の 2'b00 を付けません。
      // 今回は、Block Memory Generator で、Stand Alone を選択し、
      // かつ、Generate address interface with 32 bits のチェックボックスを OFF にしました。
      // この場合、アドレスは連続する整数で指定します。

      data <= bram_data;
      // 実際は、データは 1 CLK 遅れてやってきます。
      // 連続してデータを取り続ける必要がある場合などは、気をつけてください。
    end
endmodule
```

# 5-3. IP を配置し配線する
## 5-3-1. IP を配置する
- IP Integrator で IP を配置します。
    - [2-3-1. 自作モジュールを配置する](https://qiita.com/nishimuraatsushi/items/e05b7185de463c0b006a#2-3-1-%E8%87%AA%E4%BD%9C%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%82%92%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B) の要領で、bram_loader.v を配置します。
    - [3-2-1. Block Memory Generator IP を配置する](https://qiita.com/nishimuraatsushi/items/4d31d397c0704ab09fdb#3-2-1-block-memory-generator-ip-%E3%82%92%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B) の要領で、Block Memory Generator を配置します。
    - [1-4-2. AXI GPIO の IP を配置する](https://qiita.com/nishimuraatsushi/items/3e5f161eca4b2a0f06c8#1-4-2-axi-gpio-%E3%81%AE-ip-%E3%82%92%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B) の要領で、AXI GPIO を配置します。

<img width="350" alt="スクリーンショット 2020-11-03 11.47.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/e160708f-4bf8-74d9-3015-7ca901a39636.png">


## 5-3-2. Block Memory Generator を ROM で設定する

- Block Memory Generator をダブルクリックして設定を開き、
    - Mode を、Stand Alone に
    - Memory Type を、Single Port ROM にします。

<img width="350" alt="スクリーンショット 2020-11-03 11.17.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/59b22fd3-8fce-7437-915e-efe6d0958159.png">

- メモリを設定します
    - データ個数 = 8 、
    - Always Enabled : en ピンが不要になります
    - Registor を使用しない : レイテンシが短くなります
    - Reset を使わない : rst ピンが不要になります

<img width="350" alt="スクリーンショット 2020-11-03 11.34.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/560bdb80-e4bf-2a87-f356-4196a04b14b6.png">

- ROM データを設定します
    - ROM データは外部ファイルで管理されます
    - 今回は、読み込めるものがないので、新規作成します

<img width="350" alt="スクリーンショット 2020-11-03 11.28.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/24bd2f0e-541c-0bf1-05ec-efd109101147.png">

- ファイル名はとりあえず、適当です

<img width="350" alt="スクリーンショット 2020-11-03 11.29.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/432763c0-2171-a2b2-ef3f-290cdfb39129.png">

- 編集します
    - 記述方式は 10 進数にしました
    - Depth を 8 に、メモリを設定しておいたので、データを 8 個書きます。スペース区切りです。

<img width="350" alt="スクリーンショット 2020-11-03 11.31.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/cfba9172-ee82-159d-505e-8123d4b3703c.png">

- BRAM の設定状況を確認しておきましょう
    - レイテンシは 1 CLK
    - アドレス幅は 3 bit です 

<img width="350" alt="スクリーンショット 2020-11-03 11.36.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/63073806-3c82-fb6f-3596-1820e10021ed.png">

- ちなみに、生成された ROM データのファイルは以下のような感じです
    - テキストなので、事前に python などで準備しておくと便利でしょう

```design_1_blk_mem_gen_0_0.coe
memory_initialization_radix=10;
memory_initialization_vector=0 10 20 30 40 50 60 70;
```

- 設定後の BRAM はピンが減っています

<img width="350" alt="スクリーンショット 2020-11-03 11.59.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/c01dc3fe-4585-b92e-4d2d-bb260d304ebd.png">

## 5-3-3. IP を配線する
- 次の配線を行います：
    - axi_gpio_0.gpio_io_i -- bram_loader_0.data
    - axi_gpio_0.gpio_io_o -- bram_loader_0.req_addr
    - blk_mem_gen_0.addra -- bram_loader_0.bram_addr
    - blk_mem_gen_0.clka -- bram_loader_0.bram_clk
    - blk_mem_gen_0.douta -- bram_loader_0.bram_data

<img width="350" alt="スクリーンショット 2020-11-03 12.04.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/fc4cd2c8-ae84-a916-e778-e5129d5d367c.png">

- Run Connection Automation (チェックボックスを全て選択) と、Run Block Automation を実行します。

<img width="500" alt="スクリーンショット 2020-11-03 12.05.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/b0c7e7eb-6e92-90c9-2df2-2e1596fdcc99.png">

こんな感じになりました。

# 5-4. 後仕上げ
## 5-4-1. HDL Wrapper を生成する
[手順 1-5. HDL Wrapper を生成する](https://qiita.com/nishimuraatsushi/items/3e5f161eca4b2a0f06c8#1-5-hdl-wrapper-%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B) と同様の手順です

## 5-4-2. 生成する
[手順 1-7. 生成する](https://qiita.com/nishimuraatsushi/items/3e5f161eca4b2a0f06c8#1-7-%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B) と同様の手順です

こんな実装になりました。

<img width="500" alt="スクリーンショット 2020-11-03 12.12.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/9f4ecb24-bad9-2df4-20cf-8c115a81b035.png">



# 5-5. PYNQ で実行する
## 5-5-1. ファイルのアップロード

- [手順 1-8-1. ファイルのアップロード](https://qiita.com/nishimuraatsushi/items/3e5f161eca4b2a0f06c8#1-8-1-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89) を参考に PYNQ へアップロードします
- 例えばこんなコマンドになります

```sh
scp ~/vivado/asobu/asobu05/asobu05.runs/impl_1/design_1_wrapper.bit xilinx@192.168.2.99:pynq/overlays/asobu05/asobu05.bit
scp ~/vivado/asobu/asobu05/asobu05.srcs/sources_1/bd/design_1/hw_handoff/design_1.hwh xilinx@192.168.2.99:pynq/overlays/asobu05/asobu05.hwh
```

## 5-5-2. jupyter で実行

- [手順 1-8-2. jupyter で実行](https://qiita.com/nishimuraatsushi/items/3e5f161eca4b2a0f06c8#1-8-2-jupyter-%E3%81%A7%E5%AE%9F%E8%A1%8C) を参考に実行します


<img width="450" alt="スクリーンショット 2020-11-03 12.12.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/fee389b9-da95-7b0f-3a45-9bbb809edb2e.png">


```python:asobu05.ipynb
import pynq
fpga = pynq.Overlay('asobu05.bit')

gpio = pynq.MMIO(fpga.ip_dict['axi_gpio_0']['phys_addr'], length=0x1000)

# 読み込んでみます。
gpio.read()
# >> 0
# 初期値は 0 でした。

# req_addr に 1 を入れてみます。
gpio.write(offset=0, data=1)
gpio.read()
# >> 10
# アドレス 1 番に格納した 10 が取り出せました。

# req_addr に 5 を入れてみます。
gpio.write(offset=0, data=5)
gpio.read()
# >> 50
# アドレス 5 番に格納した 50 が取り出せました。
```