PYNQ で遊ぶ : 4. BRAM を使う (2)

# これは何か

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

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

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


##### 関連記事：

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


# 4-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 : asobu04
    - 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 も配置しておきます

# 4-2. BRAM にデータを送り続けるモジュールを作る

- [手順 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_interface.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_interface.v
module bram_interface
  (
    input wire CLK,
    output wire bram_clk,
    output wire [31:0] bram_addr,
    output wire [31:0] bram_data,
    output wire [3:0] bram_we,
    output wire bram_en,
    output wire bram_rst
  );
  
  reg [31:0] count;
  
  assign bram_clk = CLK;
  // BRAM を駆動するクロックを指定します。
  //   今回はこのモジュールと同期させて動かすので、
  //   モジュールに入力されたクロックをそのまま assign しています。

  assign bram_addr = {count[27:24], 2'b00};
  // count のうち、27-24 bit 目をアドレスに使います。
  //   24 bit 目がカウントアップするのは 0.17 秒に 1 度です。
  //   27-24 bit が 0000 --> 1111 まで変化するのに、2.7 秒かかります。
  //   bram_addr は 4 byte 単位で指定しますので、アドレス末尾に 00 の 2 bit を追加します。
  //   bram_addr 000000 から 111100 まで、16個のアドレスを 2.7 秒かけて網羅する事になります。

  assign bram_data = count[31:28];
  // count のうち、28 bit 目以降をデータとします。
  //   アドレスを一周したら、1つ大きな数字になります。
  
  // 以下は、BRAM の制御用の入力です。
  // 今回は、常時、データを書き続けるので、書き込み有効状態を定数で指定します。
  assign bram_we = 4'hF;
  assign bram_en = 1'b1;
  assign bram_rst = 1'b0;
  
  // カウンタを実装します
  always @(posedge CLK)
    begin
      count <= count + 1;   // 100 MHz CLK をカウントします
    end
endmodule
```


# 4-3. IP を配置し配線する
## 4-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_interface.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 IP を配置します。
    - [3-2-2. AXI BRAM Controller IP を配置する](https://qiita.com/nishimuraatsushi/items/4d31d397c0704ab09fdb#3-2-2-axi-bram-controller-ip-%E3%82%92%E9%85%8D%E7%BD%AE%E3%81%99%E3%82%8B) の要領で AXI BRAM Controller を配置し、Number of BRAM Intnerfaces を 1 にしておきます。

<img width="350" alt="スクリーンショット 2020-11-03 10.39.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/1345dd0f-5f4c-f748-6ff1-b2398de06023.png">


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


## 4-3-2. Block Memory Generator を Dual Port に設定する

- 今回は、BRAM を、PL (FPGA 内部) からと、PS (CPU, python) から使うので、Block Memory Generator を Dual Port に設定する必要があります。

<img width="350" alt="スクリーンショット 2020-11-03 9.38.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/bc380efc-2e35-181e-1189-2ddad469c942.png">

<img width="350" alt="スクリーンショット 2020-11-03 9.38.05.png" スクリーンショット 2020-11-03 9.40.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/88f48d85-6cd0-0f32-11b6-dc4d09c848a9.png">



## 4-3-3. IP を配線する
- 次の配線を行います：
    - blk_mem_gen_0.BRAM_PORTA を axi_bram_ctrl_0.BRAM_PORTA に
    - blk_mem_gen_0.BRAM_PORTB を bram_interface_0 に
        - blk_mem_gen_0.clkb -- bram_innterface_0.bram_clk
        - blk_mem_gen_0.addrb -- bram_innterface_0.bram_addr
        - blk_mem_gen_0.dinb -- bram_innterface_0.bram_data
        - blk_mem_gen_0.web -- bram_innterface_0.bram_we
        - blk_mem_gen_0.enb -- bram_innterface_0.bram_en
        - blk_mem_gen_0.rstb -- bram_innterface_0.bram_rst

<img width="350" alt="スクリーンショット 2020-11-03 10.41.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/bcf426d9-dd57-4c37-88ed-f2b4229eecb7.png">


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


<img width="500" alt="スクリーンショット 2020-11-03 10.42.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/309d2201-5822-614a-e887-48e893993dbc.png">


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


# 4-4. 後仕上げ
## 4-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) と同様の手順です

## 4-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 10.45.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/f118fa3d-d7ac-418e-cf7f-011a7c4556f6.png">


# 4-5. PYNQ で実行する
## 4-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/asobu04/asobu04.runs/impl_1/design_1_wrapper.bit xilinx@192.168.2.99:pynq/overlays/asobu04/asobu04.bit
scp ~/vivado/asobu/asobu04/asobu04.srcs/sources_1/bd/design_1/hw_handoff/design_1.hwh xilinx@192.168.2.99:pynq/overlays/asobu04/asobu04.hwh
```


## 4-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 10.23.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107797/7623313e-9b73-4531-95ee-550867e6600c.png">

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

bram = pynq.MMIO(fpga.ip_dict['axi_bram_ctrl_0']['phys_addr'], length=8*1024)

# 読み込んでみます。
[bram.read(i*4) for i in range(16)]
# >> [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1]
# 値が左 (アドレス小) から右に (アドレス大) 順番に増えていきます
```