FPGA(Pynq/ZYNQ)でプログラム(C/C++)を動作させるまでの手順

# 0. 概要
C/C++で作成したプログラムをFPGAで動作させるチュートリアル。

登場人物と彼らの役割は以下。

 * VivadoHLS
 高位合成用のソフトウェアで、C/C++で作成したプログラムをFPGA用の言語であるVerilog/HDLに変換してくれる。

 * Vivado
Verilog/HDLをBitstreamというFPGA言語にコンパイルしてくれる。
一般的なCPUアーキテクチャでいうとアセンブラがVerilog/HDL（RTL: レジスタ転送レベル）に対応する。マシン語がBitsteramに対応。

 * Zynq
FPGAとCPUが1枚のボードに実装されている。このような形式をSoCといってARMのCPUからFPGAへアクセスできるのが利点である。これにより、CPU上でLinuxを動作させ、デバイスドライバを用いてFPGAへアクセスすることができる。
また、FPGAのメモリ空間をCPU(メモリ)にマッピングすることができるため、CPUの使用感でFPGAが使える。


C/C++で作成したプログラムをFPGAで動作させる全体の流れは以下。

 * C/C++をVerilog/VHDL化(Vivado HLSを使用)
 * Verilog/VHDLをBitstream化(Vivadoを使用)
 * BitstremをFPGAに書き込む(Zynq上)
 * FPGAをCPU(SoC)から使う(implementation in c on Zynq)

環境は以下。

* FPGA: Pynq for Zynq
* 母艦PC: Windows 7 64bit


# 1. C/C++→Verilog/VHDL化
まず、何らかのC/C++コードがあるものとする。今回はCNNとする。これを最終的にはFPGAで動作させる。

## 最上位関数及びインタフェースの設定
CNNのプログラムの仕様としては画像(input)を受け取り、その画像が何であるかを識別した結果(output)を返すものとする。

このため、FPGAへ搭載する最上位の関数は以下になる(私の場合)。

```

void top_function(T *_input, T *_output) {
  // CNNの処理
}

```

因みに、上記のようにポインタでargsを渡すと大きさが未知なので、こんな感じのエラーが出る。

```

unsupported memory access on variable '_output' which is (or contains) an array with unknown size at compile time.

```

そのため、以下のように大きさを指定する。
この時、大きさを指定することによって、そのサイズ分を確保していそうであるものの、
Cと同様で配列はメモリコピーにならず、ポインタ渡しになる。
すなわち配列を超える大きさでも最初のオフセットからずらして参照すればアクセスできる(お勧めしない)。


```

void top_function(T _input[INPUT_NUM], T _output[OUTPUT_NUM])

```

次に、この``input``と``output``をCPU及びFPGAで参照できるようにするため、
以下のようにインタフェースを設定する。

```
#pragma HLS INTERFACE s_axilite port=_input bundle=AXI4LS
#pragma HLS INTERFACE s_axilite port=_output bundle=AXI4LS
#pragma HLS INTERFACE s_axilite port=return bundle=AXI4LS
```
コーディングはこれでおしまい。次から高位合成を行う。

## 高位合成の準備
まずはターゲット(FPGA)の設定が適切かを見る。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/a05310d3-3b17-b70f-c701-3e3511a79582.png)
Solution > Solution Settingsを選択。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/d74ed66a-152b-ab72-888f-58a2f06338b4.png)
Solution Settings > Synthesisを選択。
次に、この画面上でpartの横にある...を選択。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/923901c9-a90e-3a5a-9866-d7d73dae6864.png)
このFPGAの一覧から作成したプログラムを動作させたいFPGAを選択。
選択後はOKを選択し、画面を閉じる。

次に最上位関数の設定
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/6229b28c-c51f-b0df-ba0a-e9d7f492de91.png)
Project > Project Settings

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/58ab028a-57e3-46a8-5224-924719ec4c8a.png)
Project Settings > Synthesis > Top function > Browseから最上位関数を選択
私の場合はtop_function.c


## 高位合成

次に高位合成をする

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/5314f389-a582-7aa0-1803-237dbe3714d7.png)
赤矢印の再生ボタンをクリック

```Starting C synthesis ...```とConsoleに表示されて、コンパイルが始まる。

なお、Cシミュレータと異なり、高位合成時のコンパイラではmalloc、New、Vectorや各種STLが使えないこと、ポインタ渡しにも制約があるため、コンパイルに失敗することが多いだろう。

一先ず、コンパイル時にメモリの大きさが明確（だますことはできる）になっているように気を付けておけばよい。


コンパイルが成功すると以下のようなログとともに、Synthesis Reportが表示される。

```
INFO: [HLS 200-111] Finished generating all RTL models Time (s): cpu = 00:00:53 ; elapsed = 00:01:54 . Memory (MB): peak = 1176.785 ; gain = 1118.508
INFO: [SYSC 207-301] Generating SystemC RTL for top_function.
INFO: [VHDL 208-304] Generating VHDL RTL for top_function.
INFO: [VLOG 209-307] Generating Verilog RTL for top_function.
INFO: [HLS 200-112] Total elapsed time: 114.699 seconds; peak allocated memory: 1.034 GB.
Finished C synthesis.
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/e45bd5f2-e59d-bffa-556f-4b2c005e6927.png)

まずは赤矢印の表を見て、自身の実装したプログラムをFPGAに搭載すると、どれくらいのロジックとメモリを使うかを見てみよう。これが100%を超えていれば、もちろんそのFPGAには乗らないので、またプログラムを変更しつつ、コンパイルして調整しよう。

## Verilogを書き出す
では次に、コンパイルした結果を外部から読み取れるようにする。
そのために、```Export RTL```を選択する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/a4c92d8a-1dd1-ca41-d008-9d877c39a294.png)
ポチっと押すとExport RTL画面が表示される。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/0eb11cbe-7162-0d65-4eb9-d983c1486840.png)
デフォルト設定のまま、OKを押す。

そうすると、```Starting export RTL ...```と表示され、Exportが始まる。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/ae5fa6d5-f538-e967-82b1-6081effb0f79.png)

終わるとConsoleに```Finished export RTL ...```と表示される。
また、左のExplorerにimpl > ipのフォルダが追加され、ここにコンパイル結果が格納される。
その中にxilinx_com_hls_xxxxxx_xxx.zipもある。

なお、CPUからFPGAを叩くために使うDriverもこの時生成されており、
```impl > ip > drivers```の中に入っている。このフォルダの中のsrcフォルダを全てコピーしておけばよいだろう。

# 2. Verilog/VHDLをBitstream化(Vivadoを使用)
FPGAへ焼くために書き出されたVerilogをBitstream化する。
まずはVivadoを起動。

## Projectの作成
Quick Start > Create Projectを選択
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/405689e0-c4a3-d2d6-0da4-5df367680cdb.png)

Next

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/d696cc46-8c3a-0baf-1b89-f838c5465816.png)

テキトーなProject nameとProject locationを設定 > Next
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/665032f8-2dab-8346-3c68-d5f1dd308dc2.png)

HLSでRTLをエクスポートしたので、RTLプロジェクトを選択 > Next

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/1dd4a4cc-df4e-2eb7-b843-00ecba297541.png)
何も設定せずNext

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/d9c4536e-1de2-d365-e8a5-246d76835c3c.png)
制約も何も設定せずNext

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/90e4f1a8-ca20-c0ba-346d-7ac24eb06a23.png)
HLSと同じターゲットに設定 > Next

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/5ba21623-e390-7408-6011-19ca60dccbb7.png)
Finish

## IPの追加
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/87927197-04b4-6c70-ecac-777bfd451e24.png)
PROJECT MANAGER > IP Catalogを選択

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/1f9afe28-0d3b-c2c8-d254-b61dc0adab32.png)
IP Catalog上で右クリック  > IP settings

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/3c218730-0c47-f415-d046-e8ad7d1d426e.png)
IP > Repositoryを選択


+ボタンで、先ほどHLSから書き出されたIPフォルダを追加
```<YOUR_VIVADO_HLS_PATH>\solution1\impl\ip```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/d9b36f20-3ebe-3919-4c0a-5c012a99da7b.png)
こんな感じのファイルがあるフォルダ。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/bd1013d7-eb1c-45d7-b225-b512cd130bcb.png)
追加したらOK > OKを選択して閉じる。

## 回路の作成
PROJECT MANAGER > IP INTEGRATOR > Create Block Designを選択

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/2b40e0a5-68f4-701c-1105-99dd652210c6.png)
こんな画面が表示されるので、そのままOK
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/69583836-eafb-295e-eec6-17fbab03620a.png)
ADD IPを選択
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/5c5ab1c4-36f4-1b74-5c5d-fdedfc0c2633.png)
Zynqと打ってエンター

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/9fd9e8fd-dbd0-7043-74a9-920e881162dd.png)
Zynqが追加されたのをみつつ、Run Block Automation
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/b3420784-84bc-7900-cb8e-7ddea76e8215.png)
デフォルトの設定でOK

次に、またADD IPから、自身の最上位関数を登録。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/f6a13080-4962-a433-d100-1c60f8e3d996.png)

私の場合はTop_function
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/d55c2191-7047-b708-f1bf-8c5c491afc2c.png)
追加されたのを確認しつつ、Run Connection Automationを選択し、自動配線。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/3cacaa74-05ba-a169-8f82-6feca9d38417.png)
デフォルト設定でOK

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/ec22d1cc-5cc7-3838-941a-a544013c31f4.png)
自動配線された。

後は気になるブロックなどをダブルクリックで選択して、詳細を確認すればよい。
次に、このブロック線図が正しいかValidationを行う。

ValidationはF6キーで行える。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/db050af9-a0aa-04e5-f7cf-67988f35a64e.png)
こんな画面が表示されれば検証終了

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/2dd47aa3-6b1e-3527-02e1-5bf3b66c0892.png)
Sources > 右クリック > Create HDL Wrapperを選択

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/a2e13d1b-eee0-2723-b964-b67d026ad12f.png)
デフォルトでOK

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/bc4125e0-cae5-ca81-b805-e7b60ee1f07c.png)
これでラッパーが作成される。

## Bitstream化する
ではVerilogから回路図が出来たので、いよいよBitstream化する。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/0a8e180f-67e1-638e-6f4e-3492bcc273f1.png)
Generate Bitstreamを選択

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/85acd735-3dfb-9d2e-f2b9-9037b14debff.png)
YES

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/12ffacd3-c360-9c85-721d-6b955a4dcc1d.png)
OKでコンパイル開始。

なおNumber of jobsでスレッド数を変えられる。
CPU数 - 1くらいの数にしておこう。CPU全て使うと、たまにVivadoが落ちたりする。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/c960e9b3-fef5-2799-8519-3f8fa7f72618.png)
終わるとこんな感じの画面が出るので、キャンセル

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/d66ad2f7-0517-15d9-2a6a-3e5dc0447dcb.png)
次にProject Summaryを押す

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/1e335213-e897-6496-705c-65b38991e9b3.png)
こんな具合に、SynthesisとImplementationがCompleteになっていれば成功

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/ceb50970-5675-8be6-c0d6-2c9c6aafb0aa.png)
ではいよいよBitstreamを書き出す。
File > Export > Export Hardware

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/7df36ee5-0aa2-3c7f-ef28-bb1d48aaed4a.png)
Include bitstreamにチェックを入れてOK

こんな感じのファイルパスのところにbitstreamが書き出される。
```<YOUR_PATH>\testProject\testProject.runs\impl_1```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/8bbc0274-bcc2-1a61-6b36-ee677e374d49.png)
こんな感じのファイル群
```.bit```という拡張子がBitstreamのファイルになる。

これで必要なファイル達は殆ど揃った。

# 3. BitstreamをFPGAに書き込む(Zynq上)
では、いよいよFPGAに書き込む。まずFPGAが乗っているボードにSCPないしは、何らかの方法で先ほど生成したbitstreamを送る。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/1688028e-a8bb-f79c-76dd-38c440abc530.png)
こんな感じのファイル構造になっていればよい。

main.pyは以下のように書く。

```python3
#coding:utf-8
import os.path


BITSTREAM_FILE = "./design_1_wrapper.bit"
PARTIAL_BITSTREAM = "/sys/devices/soc0/amba/f8007000.devcfg/is_partial_bitstream"
FPGA_DEVICE = "/dev/xdevcfg"

if __name__ == '__main__':

       # Compose bitfile name, open bitfile
        with open(BITSTREAM_FILE, 'rb') as f:
            buf = f.read()

        # Set is_partial_bitfile device attribute to 0
        with open(PARTIAL_BITSTREAM, 'w') as fd:
            fd.write('0')

        # Write bitfile to xdevcfg device
        with open(FPGA_DEVICE, 'wb') as f:
            f.write(buf)
```

以下で実行

```
sudo python3.x main.py
```

そうするとBitstreamがCPUからFPGAへに書き込まれる。


```
/dev/xdevcfg
```
このxdevcfgがFPGAとマッピングされており、
こちらにBitstreamを書き込めば、オーバレイできる仕組みとなっている。

そのため、こんなのでもよい。

```
$sudo bitstream > /dev/xdevcfg
```

# 4. FPGAをCPU(SoC)から使う(implementation in c on Zynq)
まず、CPUからFPGAを叩くために、デバイスドライバなどをIncludeしたC言語で実装しなければならない。
そのため、必要なファイルを揃える。

## ビルドに必要なファイルを揃える
まずVIVADO HLSで生成したIPフォルダのDriverを開く。パスは以下。
```<YOUR_PROJECT_PATH>\solution1\impl\ip\drivers\<YOUR_FUNCTION>\src\```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/c6a43859-8f40-e04a-366a-e16bb7cae8d2.png)
ファイルは最上位関数の名前によって変わる。
では、Makefile以外をコピー。

次に私でいうところの、xtop_function.hを見てみると、以下のファイルがヘッダとして読み込まれていることが分かる。

```
#include "xil_types.h"
#include "xil_assert.h"
#include "xstatus.h"
#include "xil_io.h"
```

これらのファイルがありそうなところから、ごっそりインクルードファイルとして持ってくる。

```
C:\Xilinx\SDK\2017.3\data\embeddedsw\lib\bsp\standalone_v6_4\src\common
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/0d0fcbfd-364e-aded-c198-f0e9710c07eb.png)

これらのファイルをコピー。


![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/c5632d0c-0659-3889-c02d-163c5c6a3deb.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/10438086-054e-7193-3f24-51dcc2ef4725.png)
最終的にこんな構造になっていればよい。
testBench.cについてはまだないと思われるが、次項で説明する。

## ビルド、そして実行
testBench.cは実際にFPGAをCPUから叩く際のアプリケーションとなる。
以下のような感じで書けばよい。

```C
#include <stdio.h>
#include "xtop_function.h"

int main(){
	XTop_function XMluti_ap;
	XTop_function_Initialize(&XMluti_ap, "fabric") ;
        float input[] = {...};
        float output[] = {...} ;

	XTop_function_Write_p_input_Bytes(&XMluti_ap, 0, (char *)input, (28 * 28 * 4));
	while(!XTop_function_IsIdle(&XMluti_ap)) ;
	XTop_function_Start(&XMluti_ap);
	while(!XTop_function_IsDone(&XMluti_ap)) ;
	for(int i=0; i<10; ++i){
	  XTop_function_Read_p_output_Bytes(&XMluti_ap, i * 4, (char *)&output[i], 4) ;
	  printf("%d : %f\n", i, output[i]) ;			 
	}

	return(0);
}
```

各関数は微妙に関数名が異なっていることもあるので、xtop_function.hを参考にすること。

因みに、これ

```C
	XTop_function_Initialize(&XMluti_ap, "fabric") ;
```

この`fabric`全てのFPGAボードで共通というわけではない。ものすごい落とし穴である。
これはCPUからFPGAにアクセスする時のアドレスの識別子なのである。

これがデバドラ`XTop_function_Initialize`の中身である。

```C
int XTop_function_Initialize(XTop_function *InstancePtr, const char* InstanceName) {
        XTop_function_uio_info *InfoPtr = &uio_info;
        struct dirent **namelist;
    int i, n;
    char* s;
    char file[ MAX_UIO_PATH_SIZE ];
    char name[ MAX_UIO_NAME_SIZE ];
    int flag = 0;

    assert(InstancePtr != NULL);

    n = scandir("/sys/class/uio", &namelist, 0, alphasort);
    if (n < 0)  return XST_DEVICE_NOT_FOUND;
    for (i = 0;  i < n; i++) {
        strcpy(file, "/sys/class/uio/");
        strcat(file, namelist[i]->d_name);
        strcat(file, "/name");
        if ((line_from_file(file, name) == 0) && (strcmp(name, InstanceName) == 0)) {
            flag = 1;
            s = namelist[i]->d_name;
            s += 3; // "uio"
            InfoPtr->uio_num = atoi(s);
            break;
        }
    }
    if (flag == 0)  return XST_DEVICE_NOT_FOUND;

    uio_info_read_name(InfoPtr);
    uio_info_read_version(InfoPtr);
    for (n = 0; n < MAX_UIO_MAPS; ++n) {
        uio_info_read_map_addr(InfoPtr, n);
        uio_info_read_map_size(InfoPtr, n);
    }

    sprintf(file, "/dev/uio%d", InfoPtr->uio_num);
    if ((InfoPtr->uio_fd = open(file, O_RDWR)) < 0) {
        return XST_OPEN_DEVICE_FAILED;
    }

    // NOTE: slave interface 'Axi4ls' should be mapped to uioX/map0
    InstancePtr->Axi4ls_BaseAddress = (u32)mmap(NULL, InfoPtr->maps[0].size, PROT_READ|PROT_WRITE, MAP_SHARED, InfoPtr->uio_fd, 0 * getpagesize());
    assert(InstancePtr->Axi4ls_BaseAddress);

    InstancePtr->IsReady = XIL_COMPONENT_IS_READY;

    return XST_SUCCESS;
}
```

ようは、ここのパスにある者以外はじかれるということですね。

```
        strcpy(file, "/sys/class/uio/");
        strcat(file, namelist[i]->d_name);
        strcat(file, "/name");
        if ((line_from_file(file, name) == 0) && (strcmp(name, InstanceName) == 0)) {
```

なら以下から名前を持って来ればよし！ということです。

```
$ cd /sys/class/uio/uio0
$ cat name
```

因みに他のやり方としては、Socの中身を見る方法もある。

```
$ cd /sys/devices/soc0/amba@0
$ ls -a

```

これで表示された`40000_xxxx`みたいな名前から始まるやつの`xxxx`がだいたい識別子である。(zynqの場合)

```
.                             f8000000.ps7-slcr     f8f00200.ps7-globaltimer
..                            f8001000.ps7-ttc      f8f00600.ps7-scutimer
43c40000.lap_filter_axim_hls  f8003000.ps7-dma      f8f00620.ps7-scuwdt
driver_override               f8006000.ps7-ddrc     f8f01000.ps7-scugic
e0001000.serial               f8007000.ps7-dev-cfg  f8f02000.ps7-pl310
e0002000.ps7-usb              f8007100.ps7-xadc     fc000000.ps7-qspi-linear
e000a000.ps7-gpio             f8008000.ps7-afi      modalias
e000b000.ps7-ethernet         f8009000.ps7-afi      power
e000d000.ps7-qspi             f800a000.ps7-afi      subsystem
e0100000.ps7-sdio             f800b000.ps7-afi      uevent
e0200000.ps7-iop-bus-config   f800c000.ps7-ocmc
```

これでインタフェースの識別子/名前が分かったので、あとはインスタンスを作るだけ。その後は各関数を使って目的の機能を利用する。

関数の簡単な説明は以下。

```
XTop_function_Write_p_input_Bytes
```
バイト単位でFPGAの設定したインタフェースに値を送る。
この場合だとinputというインタフェースに値を送ることになっている。

```
XTop_function_IsIdle
```
アイドル状態かを確認

```
XTop_function_Start
```
FPGAを動作させる


```
!XTop_function_IsDone
```
処理が終了したか

```
XTop_function_Read_p_output_Bytes
```
バイト単位でFPGAの設定したインタフェースから値を読み込む

では既にBitstreamはFPGAに書き込まれているので以下でコンパイル。

```
g++ -Wall -I ./include/ -g *.c
```

以下で実行

```
sudo ./a.out
```

実行結果
![image.png](https://qiita-image-store.s3.amazonaws.com/0/95636/5d70e0d2-6ac1-d19c-0350-c28efedd4aef.png)
入力画像に対して、何のクラスであるかの確率が出力された。