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

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

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 です。

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

Polyphony で tests suite を試す。

Polyphony で tests suite を試す方法を記します。

Linux とか cygwin とか

まず Python 3 の環境 + pip3 の環境をいれてください。ubuntu なら apt-get でインストールできます。

> sudo apt-get install python3-pip

その後 pip3 でインストール

> sudo pip3 install polyphony
Successfully installed polyphony-0.2.2
> ls -l /usr/local/bin/polyphony
-rwxr-xr-x 1 root root 71  3月  1 16:16 /usr/local/bin/polyphony*

現時点でインストールされるのは 0.2.2 です。IO アクセスの機能が入っていないことに注意してください。(注意してもらえると助かります。)

無事インストールされれば /usr/local/bin に polyphony を見ることが出来るようになります。本来であればチュートリアル的なソースがあればよいのでしょうが、(すいません)現時点ではありません。github には polyphony の開発者用の tests suite があるのでそれを実行することが可能です。iverilog が必要になります。これも apt-get などでインストールしておいてください。

> git clone https://github.com/ktok07b6/polyphony
> cd polyphony
> ./suite.py

.tmp に verilog のファイルが生成されるはずです。

個々のファイルを実行する

個々のテストを実行することも可能です。

> ./simu.py tests/if/if01.py
    0:if01_0_in_x=   0, if01_0_in_y=   0, if01_0_out_0=   x
  110:if01_0_in_x=   0, if01_0_in_y=   1, if01_0_out_0=   x
  140:if01_0_in_x=   0, if01_0_in_y=   1, if01_0_out_0= 721
  150:if01_0_in_x=   1, if01_0_in_y=   1, if01_0_out_0= 721
  180:if01_0_in_x=   1, if01_0_in_y=   1, if01_0_out_0=   2
  190:if01_0_in_x=   0, if01_0_in_y=   2, if01_0_out_0=   2
  220:if01_0_in_x=   0, if01_0_in_y=   2, if01_0_out_0=1442

0.3.0 の新機能を使う

polyphony の 0.3.0 では IO が使えるようになりました。現時点ではテスト的に入れている機能もあるので注意が必要です。

> git clone -b 0.3.0 https://github.com/ktok07b6/polyphony polyphony-0.3.0
> cd polyphony-0.3.0
> export PYTHONPATH=`pwd`
> ./simu.py tests/io/port01.py
    0:p01_in0=   0, p01_in_valid=0, p01_out0=   x, p01_out_valid=x, p01_start=x
   10:p01_in0=   0, p01_in_valid=0, p01_out0=   0, p01_out_valid=0, p01_start=0
  110:p01_in0=   0, p01_in_valid=0, p01_out0=   0, p01_out_valid=0, p01_start=1
wait_rising out_valid
  120:p01_in0=   2, p01_in_valid=1, p01_out0=   0, p01_out_valid=0, p01_start=1
  140:p01_in0=   2, p01_in_valid=1, p01_out0=   4, p01_out_valid=0, p01_start=1
  150:p01_in0=   2, p01_in_valid=1, p01_out0=   4, p01_out_valid=1, p01_start=1
4

tests/io に開発者のテスト用ソースがあります。興味のある方は確認してみてください。残念なことにまだチュートリアルがありません。チュートリアルはでき次第、公開します。

Windows10

Windows 用の Python3 をインストールすることで Windows でも(cygwin なしに)使うことが出来ます。

www.python.org

Python 3.6.0 あるいは 3.5.3 をインストールします。なお、Visual Studio 2013 から Python を使おうとすると 3.5.3 を使うことになるので(VS 2015 なら 3.6.0 が使えます。2017 は?不明)。
私の場合
C:\Users\<ユーザ名>\AppData\Local\Programs\Python\Python36
にインストールされました。

polyphony のインストールは pip3 を使用します(Python をインストールした後に)。pip3 は
C:\Users\<ユーザ名>\AppData\Local\Programs\Python\Python36\Scripts
の下にあるのでコマンド・プロンプトを開き pip3 install polyphony とします。Scripts の下に polyphony というファイルができていれば OK です。(exe ではないのはご容赦)

Python Tools for Visual Studio

これをインストールすると Visual Studio で(無料で使用可能な Express でも!!) Python の開発ができるようになります。Polyphony の開発は主に Visual Studio で行っています。Editor が便利なようです。

https://www.visualstudio.com/ja/vs/python/

PTVS を使用するには、あらかじめ Python をインストールすることになります。VS のバージョンと Python のバージョンには組み合わせがあるので(上記にも書きましたが)注意してください。

Polyphony で SPI

Vivado HLS でいろいろ試していたのですが、結局、この手のは Python で書いて、Polyphony で Verilog に落とすことにしました。

polyphony のバージョンは 0.3.0 を使用。
GitHub - ktok07b6/polyphony: Polyphony is Python based High-Level Synthesis compiler. から 0.3.0 の branch を使用してください。最新だとエラーになる模様。

import polyphony
from polyphony.io import Bit, Uint
from polyphony.timing import clksleep, clkfence, wait_rising, wait_falling
import pdb


CONVST_PULSE_CYCLE = 10
CONVERSION_CYCLE = 40


@polyphony.module
class SPIController:
    def __init__(self):
        self.sclk = Bit()
        self.sdo  = Bit()
        self.sdi  = Bit()
        self.convst_n = Bit(init=1)
        self.cs_n = Bit(init=1)
        self.dout = Uint(width=12)
        self.chout = Uint(width=3)
        self.din = Uint(width=16)
        self.data_ready = Bit()
        self.append_worker(self.main)

    def main(self):
        while polyphony.is_worker_running():
            self.convst_n.wr(1)
            self.cs_n.wr(1)
            self.data_ready.wr(0)
            clkfence()

            self.convst_n.wr(0)
            clksleep(CONVST_PULSE_CYCLE)

            self.convst_n.wr(1)
            clksleep(CONVERSION_CYCLE)

            # starting ADC I/O
            self.cs_n.wr(0)
            sdo_tmp = 0
            clksleep(1)

            for i in range(16):
                self.sclk.wr(0)
                sdi_tmp = 1 if (self.din() & (1 << (15-i))) else 0
                self.sdi.wr(sdi_tmp)
                clksleep(1)

                self.sclk.wr(1)
                sdo_tmp = sdo_tmp<<1 | self.sdo.rd()

            self.sclk.wr(0)
            self.dout.wr(sdo_tmp & 0x0fff)
            self.chout.wr((sdo_tmp & 0x7000)>>12)
            self.cs_n.wr(1)
            clkfence()
            self.data_ready.wr(1)

これを polyphony で光合成。じゃなくて高位合成。

> ../bin/polyphony spi.py
> ls
polyphony_out.v  polyphony_out_SPIController_spic.v  spi.py

Vivado に入れてみる。

f:id:ryos36:20170228215651p:plain

なんとなくできた。