Polyphony で Deep Learning に必要そうな AND/OR/NAND/XOR 回路を FPGA で作る

#ゼロから作る Deep Learning を読む
[ゼロから作る Deep Learning](http://amzn.to/2nDvCir) という本が素晴らしい。Python で学ぶディープランニングという副題が示す通り Python でゼロから Deep Learning のアプリ（？アルゴリズム？）を作ることで、理解を深めましょうという本です。

この本では最初に AND, OR, NAND, XOR を作っています。本末転倒の気もしますが、これらを Polyphony でコンパイルしてハードウェア化してみましょう。

なおソースは github の以下の URL からとってこれます。
https://github.com/ryos36/polyphony-tutorial/

簡単に試したい人は polyphony と iverilog をインストールし上記 URL を clone して simu.py で各ソースを実行してみてください。


```txt
> pip3 install polyphony
<適当に iverilog をインストール>
> git clone https://github.com/ryos36/polyphony-tutorial/
> cd polyphony-tutorial/DeepLearning
> ../bin/simu.py and.py 
```
pyvenv を使った環境構築はこちらにまとめました。
http://qiita.com/ryos36/items/7e7fce9078a79f782380

## ２章のパーセプトロンから
2.3.1 の例にある AND ゲートを作ってみます。

```python:and.py
from polyphony import testbench

def AND(x1, x2):
    w1, w2, theta = 5, 5, 7
    tmp = x1*w1 + x2*w2
    if tmp <= theta:
        return 0
    elif tmp > theta:
        return 1

@testbench
def test():
    print(AND(0, 0))
    print(AND(1, 0))
    print(AND(0, 1))
    print(AND(1, 1))

test()
```
本の中では 0.5, 0.5, 0.7 のパラメータになっていますが、Polyphony 用に 5, 5, 7 の整数値に変更します。（今考えたら 2,2,3 の方がよかったかも）

結果は次の通りです。

```txt
[test-0.2.2] Persimmon:polyphony-tutorial> cd DeepLearning/
[test-0.2.2] Persimmon:DeepLearning> ../bin/simu.py and.py
    0:AND_0_in_x1=   x, AND_0_in_x2=   x, AND_0_out_0=   x
  110:AND_0_in_x1=   0, AND_0_in_x2=   0, AND_0_out_0=   x
  160:AND_0_in_x1=   0, AND_0_in_x2=   0, AND_0_out_0=   0
0
  180:AND_0_in_x1=   1, AND_0_in_x2=   0, AND_0_out_0=   0
0
  250:AND_0_in_x1=   0, AND_0_in_x2=   1, AND_0_out_0=   0
0
  320:AND_0_in_x1=   1, AND_0_in_x2=   1, AND_0_out_0=   0
  370:AND_0_in_x1=   1, AND_0_in_x2=   1, AND_0_out_0=   1
1
```

期待通りです。なお、勝手に出力されている謎の情報のコロンの左の数字は時間(クロック数)です。おおよその性能の目安になります。

## Python のリストを使う
こんどは Python のリストを使ってみましょう。

```python:and2.py
from polyphony import testbench

def list_mul(lst_r, lst_a, lst_b):
    for i in range(len(lst_r)):
        lst_r[i] = lst_a[i] * lst_b[i]

def sum(lst):
    tmp = 0
    for i in range(len(lst)):
        tmp = tmp + lst[i]

    return tmp

def AND(x1, x2):
    lst_r = [0, 0]
    lst_a = [x1, x2]
    lst_b = [5, 5]
    b = -7

    list_mul(lst_r, lst_a, lst_b)
    tmp = sum(lst_r) + b

    if tmp <= 0:
        return 0
    else:
        return 1

@testbench
def test():
    print(AND(0, 0))
    print(AND(1, 0))
    print(AND(0, 1))
    print(AND(1, 1))

test()
```

Polyphony にリストの掛算やsumはないので、関数を定義しています。

```txt
[test-0.2.2] Persimmon:DeepLearning> ../bin/simu.py and2.py
    0:AND_0_in_x1=   x, AND_0_in_x2=   x, AND_0_out_0=   x
  110:AND_0_in_x1=   0, AND_0_in_x2=   0, AND_0_out_0=   x
  550:AND_0_in_x1=   0, AND_0_in_x2=   0, AND_0_out_0=   0
0
  570:AND_0_in_x1=   1, AND_0_in_x2=   0, AND_0_out_0=   0
0
 1030:AND_0_in_x1=   0, AND_0_in_x2=   1, AND_0_out_0=   0
0
 1490:AND_0_in_x1=   1, AND_0_in_x2=   1, AND_0_out_0=   0
 1930:AND_0_in_x1=   1, AND_0_in_x2=   1, AND_0_out_0=   1
1
```

リストを使うことで抽象性が増しましたが、演算に for 文を使っているためスピードが遅くなってしまいました。
同様に or.py や nand.py を作ることが出来ます。コピペになっているのがちょっと悲しいソースです。

## XOR をつくってみる
これらを踏まえて XOR を作り実行します。

```python:xor.py
from polyphony import testbench

def list_mul(lst_r, lst_a, lst_b):
    for i in range(len(lst_r)):
        lst_r[i] = lst_a[i] * lst_b[i]

def sum(lst):
    tmp = 0
    for i in range(len(lst)):
        tmp = tmp + lst[i]

    return tmp

def AND(x1, x2):
    lst_r = [0, 0]
    lst_a = [x1, x2]
    lst_b = [5, 5]
    b = -7

    list_mul(lst_r, lst_a, lst_b)
    tmp = sum(lst_r) + b

    if tmp <= 0:
        return 0
    else:
        return 1

def OR(x1, x2):
    lst_r = [0, 0]
    lst_a = [x1, x2]
    lst_b = [5, 5]
    b = -2

    list_mul(lst_r, lst_a, lst_b)
    tmp = sum(lst_r) + b

    if tmp <= 0:
        return 0
    else:
        return 1

def NAND(x1, x2):
    lst_r = [0, 0]
    lst_a = [x1, x2]
    lst_b = [-5, -5]
    b = 7

    list_mul(lst_r, lst_a, lst_b)
    tmp = sum(lst_r) + b

    if tmp <= 0:
        return 0
    else:
        return 1

def XOR(x1, x2):
    s1 = NAND(x1, x2)
    s2 = OR(x1, x2)
    y = AND(s1, s2)
    return y

@testbench
def test():
    print(XOR(0, 0))
    print(XOR(1, 0))
    print(XOR(0, 1))
    print(XOR(1, 1))

test()
```

コピペプログラムの集大成のようなプログラムになっていますが、動きます。動くことが重要です。次のように結果も正しいようです。

```txt
[test-0.2.2] Persimmon:DeepLearning> ../bin/simu.py xor.py
    0:XOR_0_in_x1=   x, XOR_0_in_x2=   x, XOR_0_out_0=   x
  110:XOR_0_in_x1=   0, XOR_0_in_x2=   0, XOR_0_out_0=   x
 1440:XOR_0_in_x1=   0, XOR_0_in_x2=   0, XOR_0_out_0=   0
0
 1450:XOR_0_in_x1=   1, XOR_0_in_x2=   0, XOR_0_out_0=   0
 2780:XOR_0_in_x1=   1, XOR_0_in_x2=   0, XOR_0_out_0=   1
1
 2790:XOR_0_in_x1=   0, XOR_0_in_x2=   1, XOR_0_out_0=   1
1
 4130:XOR_0_in_x1=   1, XOR_0_in_x2=   1, XOR_0_out_0=   1
 5460:XOR_0_in_x1=   1, XOR_0_in_x2=   1, XOR_0_out_0=   0
0
```

## Python のクラスを使う
コピペを回避するためにクラスを使いましょう。だいぶ見通しがよくなりました。

```python:c_xor.py
from polyphony import testbench

class BitOp:
    def __init__(self, w0, w1, b):
        self.w0 = w0
        self.w1 = w1
        self.b = b

    def eval(self, x0, x1):
        tmp0 = self.w0 * x0
        tmp1 = self.w1 * x1
        tmp = tmp0 + tmp1 + self.b
        if tmp <= 0:
            return 0
        else:
            return 1

def AND(x1, x2):
    op = BitOp(5, 5, -7)
    return op.eval(x1, x2)

def OR(x1, x2):
    op = BitOp(5, 5, -2)
    return op.eval(x1, x2)

def NAND(x1, x2):
    op = BitOp(-5, -5, 7)
    return op.eval(x1, x2)

def XOR(x1, x2):
    AND = BitOp(5, 5, -7)
    OR = BitOp(5, 5, -2)
    NAND = BitOp(-5, -5, 7)
    s1 = NAND.eval(x1, x2)
    s2 = OR.eval(x1, x2)
    y = AND.eval(s1, s2)
    return y

@testbench
def test():
    print(XOR(0, 0))
    print(XOR(1, 0))
    print(XOR(0, 1))
    print(XOR(1, 1))

test()
```

リストを使うのをやめたので、実行時間は早くなりました。

```txt
[test-0.2.2] Persimmon:DeepLearning> ls
and.py  and2.py  c_xor.py  nand.py  or.py  t_and.py  xor.py
[test-0.2.2] Persimmon:DeepLearning> ../bin/simu.py c_xor.py
    0:XOR_0_in_x1=   x, XOR_0_in_x2=   x, XOR_0_out_0=   x
  110:XOR_0_in_x1=   0, XOR_0_in_x2=   0, XOR_0_out_0=   x
  280:XOR_0_in_x1=   0, XOR_0_in_x2=   0, XOR_0_out_0=   0
0
  290:XOR_0_in_x1=   1, XOR_0_in_x2=   0, XOR_0_out_0=   0
  460:XOR_0_in_x1=   1, XOR_0_in_x2=   0, XOR_0_out_0=   1
1
  470:XOR_0_in_x1=   0, XOR_0_in_x2=   1, XOR_0_out_0=   1
1
  650:XOR_0_in_x1=   1, XOR_0_in_x2=   1, XOR_0_out_0=   1
  820:XOR_0_in_x1=   1, XOR_0_in_x2=   1, XOR_0_out_0=   0
0
```

## おまけ: タプル版の and
Polyphony を 0.3.0 にすると(普通に pip3 install すると現時点 2017.3.27 では 0.2.2 がインストールされる) タプル版も動くよ。

```python:t_and.py
from polyphony import testbench

def t_mul2(t_a, t_b):
    a0, a1 = t_a
    b0, b1 = t_b
    return (a0 * b0, a1 * b0)

def t_sum2(t_a):
    a0, a1 = t_a
    return a0 + a1

def AND(x1, x2):
    para = (5, 5)
    b = -7

    t_r = t_mul2((x1, x2), para)
    tmp = t_sum2(t_r) + b

    if tmp <= 0:
        return 0
    else:
        return 1

@testbench
def test():
    print(AND(0, 0))
    print(AND(1, 0))
    print(AND(0, 1))
    print(AND(1, 1))

test()
```

### XOR は出来たけど
XOR 出来たけど、、、そもそも FPGA でできるの当たり前では？
Deep Learning マスターへの道はまだまだ遠い