読者です 読者をやめる 読者になる 読者になる

Polyphony で 64bit Fibonacci (本編スピンアウト 64bit Fibonacci the movie)

あらすじ ~ TV とは違う Movie ならではの展開 ~

32bit で気を良くした Polyphony サポートチームは 64bit の Fibonacci にチャレンジした。64bit は 32 を2つにしたというだけではない。次から次へと襲う難題。チームは問題を解決できるのか?本編からスピンアウトした 64bit 対応。最後に笑うのは誰だ?そして翔太は?

まずは Polyphony 0.3.0 の 64bit 化

env.py にある default_int_width=32 を 64 に変更。なお、64bit 対応は 0.3.0 から。master の 0.2.2 は未対応。

 default_int_width = 64

test bench を 64bit 化

from polyphony import testbench

def fib(n):
    if n <= 0: return 0
    if n == 1: return 1
    r0 = 0
    r1 = 1
    for i in range(n-1):
        prev_r1 = r1
        r1 = r0 + r1
        r0 = prev_r1
    return r1

@testbench
def test():
    expect = [0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610]
    for i in range(len(expect)):
        result = fib(i)
        assert expect[i] == result
        print(i, "=>", result)

    expect2 = [ 0, 55, 6765, 832040, 102334155, 12586269025, 1548008755920, 190392490709135, 23416728348467685, 2880067194370816120, 354224848179261915075, 573147844013817084101, 927372692193078999176, 1500520536206896083277, 2427893228399975082453 ]
    j = 0
    for i in range(105):
        if i % 10 != 0 and i < 100 :
            continue
        result = fib(i)
        #print("{i} => {result}({rhex}) vs {expect2}({ehex})".format(i = i, result = result, rhex = hex(result), expect2 = expect2[j], ehex = hex(expect2[j])))
        print(i, "=>", result, "(", expect2[j], ")")
        #print(i, "=>", hex(result), "(", expect2[j], ")")
        assert expect2[j] == result
        j += 1

test()

そして、Python3 で動くことを確かめる。

> python3 fib.py
0 => 0
ざっくり中略
90 => 2880067194370816120 ( 2880067194370816120 )
100 => 354224848179261915075 ( 354224848179261915075 )
101 => 573147844013817084101 ( 573147844013817084101 )
102 => 927372692193078999176 ( 927372692193078999176 )
103 => 1500520536206896083277 ( 1500520536206896083277 )
104 => 2427893228399975082453 ( 2427893228399975082453 )

64bit の int では 93 まで。(uint なら 94)。上の結果はなぜか 104 まで

msgpack-rpc vhdl を 64bit 化

たぶん create_project で generate する際のパラメタを渡せると思うだが、力及ばず、/src/test/vhdl/test_bench.vhd を直接編集。

> git diff ../../../src/test/vhdl/test_bench.vhd
diff --git a/examples/fibonacci/src/test/vhdl/test_bench.vhd b/examples/fibonacci/src/test/vhdl/test_bench.vhd
index 11f6861..7eabfbf 100644
--- a/examples/fibonacci/src/test/vhdl/test_bench.vhd
+++ b/examples/fibonacci/src/test/vhdl/test_bench.vhd
@@ -66,13 +66,13 @@ architecture MODEL of TEST_BENCH is
     constant CLOCK_PERIOD    : time    := 10 ns;
     constant DELAY           : time    :=  1 ns;
     constant MATCH_PHASE     : integer :=  8;
-    constant I_BYTES         : integer :=  4;
+    constant I_BYTES         : integer :=  8;
     constant I_WIDTH         : AXI4_STREAM_SIGNAL_WIDTH_TYPE := (
                                  ID    => 4,
                                  USER  => 4,
                                  DEST  => 4,
                                  DATA  => 8*I_BYTES);
-    constant O_BYTES         : integer :=  4;
+    constant O_BYTES         : integer :=  8;
     constant O_WIDTH         : AXI4_STREAM_SIGNAL_WIDTH_TYPE := (
                                  ID    => 4,
                                  USER  => 4,

これは polyphony では入力も 64 ビットになるから。Synthesijer では入力 32ビット、出力 64bit みたいなことができる。
あとcreate_project.tcl 内のシナリオも test_42.snr から test_92.snr に変えておく

Vivado でシミュレーション

f:id:ryos36:20170322184239p:plain
結果は 7540113804746346429 でウェブで検索したらあたったので、OK とします。

そのころ翔太は

Ikalog をとることに没頭していた。

PYNQ で fibonacci (完結編)

前回までのあらすじ

Polyphony で高位合成をして fib.py を verilog に落とし、ついでにインタフェース情報も JSON で吐き出したものを yml に(python で)書き直し、それを手で書き直したものをつかい vivado のシミュレーションに成功。さらに IPXACT 化して、PYNQ の FPGA のビットストリームを作ることに成功した。このバイナリは本当に動くのか?チームの命運は如何に?そして、翔太の恋愛のゆくえは?

PYNQ の環境を git clone する (lfs に注意!!)

https://github.com/ikwzm/PYNQ-Festival から clone する。ReadMe に書いてある通りにやればいいので、この ReadMe を最初によく読む(べきだった、、、)
特にここに含まれる大きめのファイルは git lfs をつかっているので、clone しただけだとただの ascii file です。

  • boot/boot.bin
  • boot/design_1_wrapper.bit
  • boot/u-boot.img
  • boot/uImage-4.8.17-armv7-fpga
  • debian8-rootfs-4.8.17.tgz

私の環境では git lfs が使えなかったので、githubgui からこつこつクリックしてとってきました。あとパーティションを切るようになっているので、SD カードの 2nd Partition を適切に設定し、debian8-rootfs-4.8.17.tgz を展開しました。これも ReadMe の通り

うまくたちあがると login を見ることが出来るので fpga/fpga でログイン。examples/fibonacci に移動して fibonacci_server_install.sh をroot 権限で実行。これで準備は整いました。

jupyter からつかう

折角 PYNQ なので、web browser から使います。samba も動いているので pynq という hostname で繋がります。うまく、動きました。
f:id:ryos36:20170320164153p:plain

jupyter 便利ですね。これの便利さは結局 history の永続性と視覚化の2点ですね。

ISSUE: time out

なんか時々動きが、、、そういう細かいところにこだわっていてはお重荷になれないので、大物になれないので、ざっくりとオミット。
はいいろぞうさん、いるといいなぁ~

追記

「timeout は、socat を起動するときに "socat -d -d tcp-listen:54321,fork /dev/zptty0,raw,nonblock,echo=0 &" のように echo=0 をつけると出なくなりました。」

とのこと。試してみよう。

そのころ翔太は、、、

ひとり家の中でスプラトゥーンに興じていた。

polyphony + msgpack-rpc を試す(なんかシミュレーションはうまくいきましたよ変)

自動生成の json とか yml とかに間違いが

いろいろ自動生成に間違いがありインタフェース名がまずかったようで、そこをなおしたら動いた。
f:id:ryos36:20170320001621p:plain

以前より性能がよくなっているじゃん。

def fib じゃなくて def fibonacci にすべきだったんだね。
とりあえずは手で yml 等を修正

PYNQ 用のビットストリームを生成

ここまでいけば PYNQ でいけそうじゃありませんか?回路図はうまくいきましたね。(一部 tcl のファイル名を書き直しましたが)
f:id:ryos36:20170320002539p:plain

理由はわかりませんが build_fsbl.tcl だけ失敗。まぁいいでしょう。うまくビットストリームを作るところまではいきました。

Polyphony + msgpack-rpc をつかってみる(未達成編)

Python base の高位合成と msgpack-rpc をつかって処理をやってみようという話
元記事は 
フィボナッチを求める回路をPolyphonyとMessagePack-RPCでFPGAに実装してみた(シミュレーション編) - Qiita
あと、PYNQ 祭りの資料
Pynq祭り資料

msgpack-rpc を使うモチベーション

Python から簡単に FPGA を使いたいからというのが理由。msgpack-rpc を使えばほぼ Python だけを知っていれば、FPGA が使える環境が構築できそう(<=まだ、出来ていない。複雑な手順を追わないといけない)
まずは自分のメモ代わりにその複雑な手順を追ってみる。

参考までに書くと msgpack というのは json 風のシリアライゼーションの仕組み。仕様としてはその形式だけを定義しているので RPC の機能はない。msgpack-rpc というのがそれとは別にあって、こちらは RPC 。(という私の理解)

準備

まずは polyphony の準備。0.3.0 の branch を使っている。
GitHub - ktok07b6/polyphony: Polyphony is Python based High-Level Synthesis compiler.
そして、msgpack-vhdl
GitHub - ikwzm/msgpack-vhdl-examples: Example for msgpack-vhdl
msgpack-vhdl は git のサブモジュールを使っているのでそれらも clone する。

> git clone https://github.com/ikwzm/msgpack-vhdl-examples
> cd msgpack-vhdl-examples
> git submodule init
> git submodule update

polyphony にはパッチを当てる.これで json を標準出力に出力する。

diff --git a/polyphony/compiler/__main__.py b/polyphony/compiler/__main__.py
index e19b58e..65fb09b 100644
--- a/polyphony/compiler/__main__.py
+++ b/polyphony/compiler/__main__.py
@@ -1,5 +1,10 @@
 ・ソimport os
 import sys
+
+import json
+from .hdlinterface import port2ahdl
+
+from collections import OrderedDict
 from optparse import OptionParser
 from .builtin import builtin_names
 from .driver import Driver
@@ -230,6 +235,33 @@ def reducestate(driver, scope):
     StateReducer().process(scope)
 
 
+def print_json(driver, scope):
+    #print(scope.module_info) 
+    #print(scope.module_info.name) 
+    if not scope.is_module() and not scope.is_function_module() :
+        return
+    mod_if_dict = OrderedDict()
+    mod_if_dict["name"] = scope.module_info.get_name()
+    infs = []
+    for inf in scope.module_info.get_interfaces().values():
+        an_inf = OrderedDict()
+        an_inf["interface"] = inf.__class__.__name__
+        an_inf["name"] = inf.get_if_name()
+        ports = []
+        port= OrderedDict()
+        for p in inf.get_ports().all():
+            #print(port2ahdl(inf, p[0]))
+            port["name"] = p[0]
+            port["width"] = p[1]
+            port["dir"] = p[2]
+            port["signed"] = p[3]
+            ports.append(port)
+        an_inf["ports"]=ports
+        infs.append(an_inf)
+    mod_if_dict["interfaces"] = infs
+    print(json.dumps(mod_if_dict))
+    #print(scope.module_info.get_parameters())
+
 def transformio(driver, scope):
     IOTransformer().process(scope)
 
@@ -396,6 +428,9 @@ def compile_plan():
         transformio,
         dbg(dumpmodule),
         reducestate,
+
+        print_json,
+
         dbg(dumpmodule),
         genhdl,
         dbg(dumphdl),

msgpack-vhdl-examples/examples/fibonacci/src/main/polyphony で作業をする。
すでに *.v や *.yml があるので(それを自動生成する過程を追いましょうというの今回の話なので)
それらを削除。

PYTHONPATH を patch のあたった polyphony をつかってまずは fib.v を作る。
あれ~ Makefile とファイル名が違うので Makefile を修正する。

SOURCE_VERILOG_FILE = fib.v
INTERFACE_VHDL_FILE = fib_interface.vhd
SERVER_VHDL_FILE    = fib_server.vhd
> make fib.v > fib.json
> json2yaml.py fib.json
> make
../../../../../msgpack-vhdl/tools/msgpack-rpc-ifgen -v fib.yml
msgpack-rpc-ifgen 0.2.5 : read file : fib.yml
msgpack-rpc-ifgen 0.2.5 : generate interface file : fib_interface.vhd
msgpack-rpc-ifgen 0.2.5 : generate server file : fib_server.vhd
> ls
create_vivado_project.tcl*  fib.v              fib_server.vhd   test.v
fib.json                    fib.yml            Makefile
fib.py*                     fib_interface.vhd  polyphony_out.v

json2yaml は今回作った python のやっつけソース

#!/usr/bin/env python3

import os
import sys
import json
from collections import OrderedDict
from functools import partial
import yaml

def main():
    if len(sys.argv) <= 1:
        sys.exit(0)
    src_file = sys.argv[-1]
    if not os.path.isfile(src_file):
        print(src_file + ' is not valid file name')
        sys.exit(0)
    
    (dst_file,_) = os.path.splitext(src_file)
    dst_file += ".yml"

    with open(src_file, "r+") as f:
        data = json.load(f) 

    yml_data = OrderedDict()

    name = data["name"]
    yml_data["name"] = data["name"]
    yml_data["generate"] = {"interface": { "name": name + "_Interface", "file": name + "_interface.vhd"}, "server": { "name" : name + "_Server", "file" : name + "_server.vhd"}}
    yml_data["port"] = {'reset': 'rst', 'clear': None, 'clock': 'clk'}

    meth = OrderedDict()
    meth["name"] = data["name"]
    meth["interface"] = {"type": "polyphony"}
    args = []
    returns = []
    infs = data["interfaces"]
    for i in infs:
        iname = i["interface"]
        if not iname == "SingleWriteInterface" and not iname == "SingleReadInterface" :
            continue
        pi = i["ports"][0]
        a = OrderedDict()
        a["name"] = i["name"]
        a["type"] = "Integer"
        ai = OrderedDict()
        ai["name"] = "Signal"
        ait = OrderedDict()
        if pi["signed"] == True :
            ait["name"] = "Signed"
        else :
            ait["name"] = "Unsigned"
        ait["width"] = pi["width"]
        ai["type"] = ait
        aip = {}
        aip["data"] = data["name"] + "_" + i["name"]
        ai["port"] = aip
        a["interface"] = ai
        if pi['dir'] == "in" : 
            args.append(a)
        else:
            returns.append(a)
    meth["arguments"] = args
    meth["returns"] = returns

    yml_data["methods"] = [meth]


    def represent_odict(dumper, instance):
         return dumper.represent_mapping(u'tag:yaml.org,2002:map', instance.items())
    yaml.add_representer(OrderedDict, represent_odict)

    with open(dst_file, "w") as f:
        f.write(yaml.dump(yml_data, default_flow_style=False))

main()

これで polyphony がつくった verilog のファイルと ruby で作った VHDL のソースができあがる。これでいいはずなのよ。

Vivado を立ち上げて合成してみる

create_vivado_project.tcl があるのでたぶんこれを実行するのでしょう。ということで Vivado の console から実行。
あーファイル名を変えていたので tcl 内のファイル名も変更。
どうやら合成はできるようです。ただ、ここだとシミュレーションもできないようなのでここはここまで。

f:id:ryos36:20170319230532p:plain

Vivado でシミュレーション

ディレクトリを examples/fibonacci/sim/vivado/polyphony に変えてシミュレーションにいどむ。ここでも tcl を書き直し。
f:id:ryos36:20170319231319p:plain

どうもシミュレーションは失敗した模様。
これは道は険しい。

オリジナルに戻ってチェック

git reset --hard
でオリジナルに戻ってチェック。こっちはさすがにちゃんと動く。本日は、ここまでか、、、手順がわかったのでよしとする。
f:id:ryos36:20170319231607p:plain

Python の Parser

訳があって Python の Parser をしらべていたら、Python のコードがかなり整理されていることに気が付いた。文法もどうやら、adsl という形でちゃんと定義されている。ソース内(https://github.com/python/cpython)には Grammar/Grammar というファイルと Parser/Parser.asdl という形で整理されている。

Grammar はまさに Python の文法。これは BNF じゃなさそうだけど、素直に読める形式だ。Parser.asdl はなんだろう?IR だろうか?これ AST の Visitor で現れるキーワードだと思う。

よく整理されているので Grammar を適当に変えると、自分の言語が簡単に作れるのではないかと思った。公式ドキュメントもあるし、このいばらの道を突き進んだ強者もいるようだ。

24. Changing CPython’s Grammar — Python Developer's Guide
qiita.com
pf-siedler.hatenablog.com
pf-siedler.hatenablog.com

cygwin を更新したら、、、vim の動きが変わりましたよ

毎度のことだが、cygwin を更新したら、デフォルトの vim の動きが変わってしまった。
/etc/vimrc を読むらしい。
勝手に以前に編集した(直前に編集した)場所に戻るようになってしまった。
これが原因らしいよ。

if has("autocmd")
  augroup fedora
  autocmd!
  " In text files, always limit the width of text to 78 characters
  " autocmd BufRead *.txt set tw=78
  " When editing a file, always jump to the last cursor position
  autocmd BufReadPost *
  \ if line("'\"") > 0 && line ("'\"") <= line("$") |
  \   exe "normal! g'\"" |
  \ endif
  " don't write swapfile on most commonly used directories for NFS mounts or USB sticks
  autocmd BufNewFile,BufReadPre /media/*,/run/media/*,/mnt/* set directory=~/tmp,/var/tmp,/tmp
  " start with spec file template
  autocmd BufNewFile *.spec 0r /usr/share/vim/vimfiles/template.spec
  augroup END
endif

非常に鬱陶しい。ちゃんと先頭から表示してくれ(というか動きを変えるなよ)。ということで、.vimrc に

if has("autocmd")
  augroup fedora
  autocmd!
endif

と書いて抑制。

Polyphony の開発の位置づけ

とりあえず master が clone されることが多いので stable 版になります。いまは 2.1.0。
最新版は branch を切って開発。現時点で 0.3.0 です。
pip3 でインストールされるのは現時点で 2.2.0 です。

整理は必要ですが、現時点ではこんな感じ。あー自分のメモだな。こりゃ。