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

ゼロから作る Deep Learning を読む

ゼロから作る Deep Learning という本が素晴らしい。Python で学ぶディープランニングという副題が示す通り Python でゼロから Deep Learning のアプリ(?アルゴリズム?)を作ることで、理解を深めましょうという本です。

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

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

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

> 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章のパーセプトロンから

2.3.1 の例にある AND ゲートを作ってみます。

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 の方がよかったかも)

結果は次の通りです。

[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 のリストを使ってみましょう。

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はないので、関数を定義しています。

[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 を作り実行します。

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

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

[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 のクラスを使う

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

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

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

[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 がインストールされる) タプル版も動くよ。

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 マスターへの道はまだまだ遠い