新千葉 ガーベージ・コレクション

FPGA マガジンやインターフェースで書けなかったこと等をちょぼちょぼ書いてます。@ryos36

Python をさわってみて

Python 実によくできた言語だと思います。C や Perl でどんな風に書いたら可読性が”落ちるのか”を研究していて、そういう書き方が”できない”ようにしているようです。(あくまで個人的な感想)

インデントで整形というのがその典型です。これは構文解析が楽になるという効果もあるようです。

代入したらローカル変数、参照”だけ”はグローバル

面白いと思ったのはこのスコープの扱い。フリー変数の扱いと言い換えてもいいかもしれません。

def func(a):
    b = 3
    return a + b

この b のように”代入したら”ローカル変数です。

def func(a):
    return a + b

ここの b は、参照”だけ”しているのでグローバル変数。この時点で、かの有名な方の持論である PythonLisp の亜種説は崩れます。スコープが特殊なんです。これ、Lisp とは大きく方向性が違う。いいか悪いかはともかくとして、おそらく、このようなスコープは Python 独特で、なかのコンパイラインタプリタの構造を大きく変える(はずです)。事実 HyLang は let を捨てたようです。

じゃ、こんなのはどう?

def func(a):
     c = b
     b = b + 3
     return c + b + a

"UnboundLocalError: local variable 'b' referenced before assignment" と実行時にエラーになります。

if の中で現れたものは?

>>> def iffunc(a):
...     if a :
...         b = 3
...     else :
...         print(a)
...     print(b)
...
>>> iffunc(3==3)
3
>>> iffunc(3!=3)
False
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in iffunc
UnboundLocalError: local variable 'b' referenced before assignment

実行時にかなり頑張っています。

++ とかはない

>>> a++
  File "<stdin>", line 1
    a++
      ^
SyntaxError: invalid syntax

こういう構文シュガーがありません。どっかの言語みたいに構文シュガーの塊のようなことはないんです。載せればいいわけじゃない。

単項演算子の ++ とか -- とかあったほうが便利そうじゃないですか。でも入れると全体のバランスが崩れるんです。折角、参照はグローバルと規定したのに、++ を入れることで、参照だが代入だかわからなくなる。そういう事態を避けるようになっています。この規則のおかげでグローバル変数はたいていの場合"read onlyなデータ"です。関数の中からは書き換えることが出来ません。

こういった、コンパイラの中身や(インタプリタか、、、)書き方の細部にわたって、設計思想がかなりはっきりした言語であり、私にとっては知れば知るほど好感が持てる言語として評価が上がっています。コンパイラインタプリタを作る人はその思想はどうあるべきかを考えるうえで研究対象の筆頭に上がってよい言語だと思います。

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