GoでFPGAしてみる(Reconfigure.io)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/43622/83b14ea5-71d4-0e01-a835-411b20c5d808.png)

[Reconfigure.io](https://reconfigure.io)というサービスがパブリックベータになりました。
このサービスは、Goのコードを、AWS F1インスタンス上で[SDAccel](https://japan.xilinx.com/products/design-tools/software-zone/sdaccel.html)という技術を用いて動かすサービスです。

SDAccelについて詳しくは、以下の記事を参考にしてください。
[【ザイリンクス】プログラマのためのFPGA開発環境が登場 C/C++を駆使してCPU／GPUの「壁」を突破 - 日経テクノロジーオンラインSPECIAL ](http://special.nikkeibp.co.jp/atcl/TS/T/TEC_t_TAT04266/)

[内部](http://docs.reconfigure.io/overview.html#system-architecture)では、
[Goのサブセット言語](http://docs.reconfigure.io/go_support.html#gosupport)->[Teak](http://apt.cs.manchester.ac.uk/projects/teak/)->Verilog->Bitstream
のような順で変換されているそうです。

今回は、このReconfigure.ioを用いて簡単なサンプルプログラムをAWS F1インスタンスへデプロイするところまでやってみたいと思います。

# 登録
[Signup For Free](https://reconfigure.io/sign-up)を開き、ユーザ情報を入力した後、Submitを押します。
まず初めに、登録したメールアドレスに[Reconfigure.io Community](https://community.reconfigure.io/)への招待URLが届きます。
これは、Reconfigure.ioに関するニュースや、サポートを受けることのできる掲示板であり、実はこの時点ではまだReconfigure.ioのパブリックベータへの登録は完了していません。

**登録から2週間ほど経ってから**、実際のReconfigure.ioへの招待URLが届きますので、URLをクリックしログインをすれば登録完了です。

招待URLは二回届きます。初めはCommunity、次がサービス本体です。

# インストール
登録が完了し、[ダッシュボード](https://app.reconfigure.io/dashboard)へアクセスできるようになったら、次はrecoコマンドのインストールを行います。

[ここ](http://docs.reconfigure.io/support_troubleshooting.html#install-update-our-command-line-tool)へアクセスし、お使いのOSに合わせたインストールコマンドを実行します。

その後、[ダッシュボード](https://app.reconfigure.io/dashboard)へアクセスをし、API Keyをコピーしておきます。
以下のコマンドを入力し、API Keyを入力すればインストール完了です。

```bash
$ reco auth
Copy the token after authentication at http://app.reconfigure.io/dashboard
Token: blablablablabla0123456789
```

# なんか書いてみる
では早速何か書いてみましょう。
公式でいくつかのサンプルプログラムが[Github上に用意されている](https://github.com/ReconfigureIO)ので、これを参考にして何か書きましょう。

## プロジェクトの作成
recoは`./.reco`ディレクトリにあるデータを使って、プロジェクトを認識します。gitみたいな感じですね。
まずはこの`.reco`ディレクトリを作成する必要があります。以下のコマンドを、main.goを配置するディレクトリで実行してください:

```bash
$ reco project create sample_proj
```

これでプロジェクトの作成が完了しました。`./.reco`ディレクトリが作成されていることが確認できます。

```bash
~/Projects/sample_proj » ls -lA
合計 8
drwxr-xr-x 2 mjhd users 4096 12月  7 21:53 .reco
-rw-r--r-- 1 mjhd users  511 12月  7 21:50 main.go

```

## ディレクトリ構造
Reconfigure.ioでは、プログラムはkernel(FPGA)とcommand(CPU, Host)に分かれます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/43622/2fbc0fe2-92e9-f897-f205-ffd77ba55941.png)

今後、kernel(FPGA)プログラムは**カーネル**、command(CPU, Host)プログラムは、**ホスト**と呼びます。

ホストは`./cmd/`以下にディレクトリを作成し、main.goとしてmainパッケージで配置してください。
カーネルは、`./main.go`として作成してください。
以下のようなディレクトリ構造になります。

```bash
~/Projects/sample_proj » tree -a
.
├── .reco
│   └── project.json
├── cmd
│   └── test-sample
│       └── main.go
└── main.go

3 directories, 3 files
```

## プログラムを書く
ホストプログラムの役割は以下です:

* データを作成、管理し、カーネルに送信する
* カーネルを起動する
* 処理結果を共有メモリから受け取り、出力処理を行う

カーネルプログラムの役割は以下です:

* 共有メモリからデータを受け取る
* 処理する
* 共有メモリへデータを書き込む

今回は、シンプルにランダムな値の入った配列を、それぞれ2倍していくプログラムを書いてみます。

```go:./main.go
// カーネルプログラム
package main

import (
	_ "sdaccel"

	aximemory "axi/memory"
	axiprotocol "axi/protocol"
)

func Top(
	// ホストで指定したパラメータが渡される
	inputs uintptr,
	outputs uintptr,
	length uint32,

	memReadAddr chan<- axiprotocol.Addr,
	memReadData <-chan axiprotocol.ReadData,

	memWriteAddr chan<- axiprotocol.Addr,
	memWriteData chan<- axiprotocol.WriteData,
	memWriteResp <-chan axiprotocol.WriteResp) {

	// 全てのinputsをinputChanへ流す
	inputChan := make(chan uint32)
	go aximemory.ReadBurstUInt32(
		memReadAddr, memReadData, true, inputs, length, inputChan)

	// データを2倍し、outputChanへ吐き出す
	outputChan := make(chan uint32)
	go func() {
		// ループは停止しなくても良い
		for {
			outputChan <- (<-inputChan) * 2
		}
	}()

	// outputChanからoutputsへ流す
	aximemory.WriteBurstUInt32(
		memWriteAddr, memWriteData, memWriteResp, true, outputs, length, outputChan)

}
```

```go:./cmd/test-sample/main.go
// ホストプログラム
package main

import (
	"encoding/binary"
	"xcl"
)

func main() {
	// カーネルとのやり取り用のWorldを作成する
	world := xcl.NewWorld()
	defer world.Release()

	// カーネルをインポートする
	// 二つの識別子は固定
	krnl := world.Import("kernel_test").GetKernel("reconfigure_io_sdaccel_builder_stub_0_1")
	defer krnl.Release()

	// 入力する配列を作成する
	inputs := make([]uint32, 30)
	for i := range inputs {
		inputs[i] = uint32(i)
	}

	// カーネルとのデータの入力用に共用メモリを確保する
	inputBuff := world.Malloc(xcl.ReadOnly, uint(binary.Size(inputs))
	defer inputBuff.Free()

	// 出力用の配列を作成する
	outputs := make([]uint32, len(inputs))

	// カーネルとのデータの出力用に共用メモリを確保する
	outputBuff := world.Malloc(xcl.WriteOnly, uint(binary.Size(outputs))
	defer outputBuff.Free()

	// ここで、データを作成し、上で割り当てた共用メモリを用いてカーネルにデータを渡す
	binary.Write(inputBuff.Writer(), binary.LittleEndian, &inputs)
	binary.Write(outputBuff.Writer(), binary.LittleEndian, &outputs) // 初期化だけする

	// カーネルへ渡すパラメータを指定する
	// 第一引数(配列へのポインタ)
	krnl.SetMemoryArg(0, inputsBuff)
	// 第二引数(配列へのポインタ)
	krnl.SetMemoryArg(1, outputBuff)
	// 第三引数(配列の長さ)
	krnl.SetArg(2, uint32(len(inputs)))

	// カーネルを実行する
	krnl.Run(1, 1, 1)

	// 共用メモリに出力されたデータを、表示する
	binary.Read(outputBuff.Reader(), binary.LittleEndian, &outputs)

	for i := range inputs {
		fmt.Printf("%d => %d\n", inputs[i], outputs[i])
	}
}
```

そして、プログラムが正しいかどうか、型チェックを行います。

```bash
» reco check
./sample_proj/main.go checked successfully
```

無事通ったら、実際にReconfigure.io上のハードウェアで実行してみます。

```bash
» reco test run test-sample

2017-12-07 23:49:32| preparing simulation .. 
2017-12-07 23:49:34| done
2017-12-07 23:49:34| archiving
2017-12-07 23:49:34| done
2017-12-07 23:49:34| uploading ... 
2017-12-07 23:49:34| done
2017-12-07 23:49:34| running simulation
2017-12-07 23:49:34| 
2017-12-07 23:49:34| you can run "reco simulation log aaaaaaaaa-bbbbbbbb-ccccccc-dddddddd" to manually stream logs
2017-12-07 23:49:34| getting simulation details
2017-12-07 23:49:34| status: queued  mkdir -p ""/mnt/.reco-work/sdaccel/dist""
cd "/mnt/.reco-work/sdaccel/dist" && XCL_EMULATION_MODE=hw_emu emconfigutil --xdevice xilinx:aws-vu9p-f1:4ddr-xpr-2pr:4.0 --nd 1
****** configutil v2017.1_sdx (64-bit)
  **** SW Build 1933108 on Fri Jul 14 11:54:19 MDT 2017
    ** Copyright 1986-2017 Xilinx, Inc. All Rights Reserved.
INFO: [ConfigUtil 60-895]    Target platform: /opt/Xilinx/SDx/2017.1.op/platforms/xilinx_aws-vu9p-f1_4ddr-xpr-2pr_4_0/xilinx_aws-vu9p-f1_4ddr-xpr-2pr_4_0.xpfm
  emulation configuration file `emconfig.json` is created in ./ directory
...(中略)...
0 => 0
1 => 2
2 => 4
3 => 6
4 => 8
5 => 10
6 => 12
7 => 14
8 => 16
9 => 18
10 => 20
11 => 22
12 => 24
13 => 26
14 => 28
15 => 30
16 => 32
17 => 34
18 => 36
19 => 38
20 => 40
21 => 42
22 => 44
23 => 46
24 => 48
25 => 50
26 => 52
27 => 54
28 => 56
29 => 58
verilog,1.60,46512
xo,66.00,1579708
xclbin,73.91,1120504
```

実行結果がでましたね。きちんと二倍されています。

# デプロイ
正常に動くことが確認できたら、AWS F1インスタンスへデプロイをしましょう。

まずは、Reconfigure.io上にアップロードし、ビルドをします。

```bash
» reco build run
2017-12-07 23:57:16| preparing build .. 
2017-12-07 23:57:17| done. Build id: aaaaaaaa-bbbbbbb-cccccc-dddddddddd
2017-12-07 23:57:17| archiving
2017-12-07 23:57:17| done
2017-12-07 23:57:17| uploading ... 
2017-12-07 23:57:18| done
2017-12-07 23:57:18| 
2017-12-07 23:57:18| you can run "reco build log aaaaaaaa-bbbbbbb-cccccc-dddddddddd" to manually stream logs
2017-12-07 23:57:18| getting build details
2017-12-07 23:57:18| status: queued  mkdir -p ""/mnt/.reco-work/sdaccel/dist""
...(省略)
```

ビルドが完了すると、以下のコマンドで今までのビルド一覧を確認できます。

```bash
» reco build list

                BUILD ID                     STATUS           STARTED          DURATION         
  aaaaaaaaa-bbbbbbb-ccccccc-dddddddddd      completed      19 hours ago        3h58m51s 
```
4時間もかかりました…一度startedになったビルドはクラウド上で行われるため、Ctrl-Cでコマンドを終了してもビルドは続くようです。
停止したい場合は、`reco build stop aaaaaaaaa-bbbbbbb-ccccccc-dddddddddd`としてください。

このBuildIDを用いて、デプロイをおこないます。

```bash
» reco deployment run aaaaaaaaa-bbbbbbb-ccccccc-dddddddddd test-sample

2017-12-08 20:07:45| creating deployment .. 
2017-12-08 20:07:46| done. Deployment id: eeeeeeee-fffffff-gggggggg-hhhhhhhhh
2017-12-08 20:07:46| 
2017-12-08 20:07:46| you can run "reco deployment log eeeeeeee-fffffff-gggggggg-hhhhhhhhh" to manually stream logs
2017-12-08 20:07:46| getting deployment details
2017-12-08 20:07:46| status: unstarted ... Device/Slot[0] (/dev/xdma0, 0:0:1d.0)
xclProbe found 1 FPGA slots with XDMA driver running
0 => 0
1 => 2
2 => 4
3 => 6
4 => 8
5 => 10
6 => 12
7 => 14
8 => 16
9 => 18
10 => 20
11 => 22
12 => 24
13 => 26
14 => 28
15 => 30
16 => 32
17 => 34
18 => 36
19 => 38
20 => 40
21 => 42
22 => 44
23 => 46
24 => 48
25 => 50
26 => 52
27 => 54
28 => 56
29 => 58
eeeeeeee-fffffff-gggggggg-hhhhhhhhh
```

動きましたね。お疲れさまです。

# 関連
Reconfigure.ioのGithubには、BNN(Binarized Neural Network)のAPIもあります。[ReconfigureIO/brain](https://github.com/ReconfigureIO/brain)
ニューラルネットワークや、画像処理、動画処理など、大きなデータを処理する場面で活用できそうです。

他にもWebインターフェース越しにデータのmd5をとるサンプルなどがあります。
https://github.com/ReconfigureIO/web-md5

時間があったらReconfigure.ioでBNNを動かしてみたいです。