shtaxxx日記

コンピュータアーキテクチャについて研究している研究者の日記や技術紹介

macOS Mojave (10.14.4) 向けにEmacsを野良ビルドする際にxml.cでエラーとなる場合の対処法

新しいEmacsのバージョンが出るといつも、こちらを参考に、インラインパッチを当てて野良ビルドをしているのだが、今回は、Emacs 26.2のビルド中に、"xml.c:23:10: fatal error: 'libxml/tree.h' file not found"とエラーが出てビルドができなかった。以前は"xcode-select --install"でCommand Line Toolsをインストールしておけば大丈夫だったはずだが、再インストールしても直らないので、ちょっと手こずった。

解決策は、ここを参考に、Command Line Toolsのをヘッダーファイルをインストールすればよい。

open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg

KLダイバージェンスを用いた異なる量子化パラメータの比較

NVIDIAのINT8量子化では、 KL Divergenceを使って活性値の分布をより正確に近似できる量子化パラメータ(最大値と最小値)を探索しているらしいので、それを実装してみた。

量子化前後でヒストグラムのビンの幅が異なるので、そのままではKLダイバージェンスの計算ができない。そこで、量子化後のヒストグラム量子化前のビンの幅を使ったヒストグラムに変換して、KLダイバージェンスを計算することにした。

以下の例だと、q1はq2はそれぞれ(-20.0, 20.0)と(-7.0, 7.0)という異なる最小値・最大値でクリップするようにしており、16個のビンでヒストグラムを作る(=4ビット量子化)場合にどちらが良いかをKL-divで判定する。 この場合、q1よりもq2の方がKL-divが小さく、元の分布を正確に表現できることがわかる。

from __future__ import absolute_import
from __future__ import print_function

import matplotlib.pyplot as plt
import numpy as np

N = 1000 * 1000
loc = 0
scale = 2
epsilon = 0.00001

ref_num_bins = 1024
q_num_bins = 16

dist = np.random.normal(loc, scale, N)
q1_dist = np.clip(dist, -20.0, 20.0)
q2_dist = np.clip(dist, -7.0, 7.0)

ref_hist, ref_bins = np.histogram(dist, bins=ref_num_bins, density=True)
q1_hist, q1_bins = np.histogram(q1_dist, bins=q_num_bins, density=True)
q2_hist, q2_bins = np.histogram(q2_dist, bins=q_num_bins, density=True)


def to_hist_with_orig_bins(targ_hist, targ_bins, orig_hist, orig_bins):
    targ_v = 0.0
    targ_i = 0
    targ_bin = targ_bins[0]
    ret_hist = np.zeros_like(orig_hist)

    for i, orig_bin in enumerate(orig_bins[:-1]):
        if targ_bin <= orig_bin:
            if targ_i < len(targ_bins) - 1:
                targ_v = targ_hist[targ_i]
                targ_i += 1
                targ_bin = targ_bins[targ_i]
            else:
                targ_v = 0.0
                targ_bin = orig_bin.max() + 1.0

        ret_hist[i] = targ_v

    return ret_hist


c_q1_hist = to_hist_with_orig_bins(q1_hist, q1_bins, ref_hist, ref_bins)
c_q2_hist = to_hist_with_orig_bins(q2_hist, q2_bins, ref_hist, ref_bins)

pad_ref_bins = np.pad(ref_bins, [1, 0], 'constant')
sumd = np.sum((ref_bins - pad_ref_bins[:-1])[1:])


ref_hist = (ref_hist + epsilon) / (1.0 + epsilon * sumd)
c_q1_hist = (c_q1_hist + epsilon) / (1.0 + epsilon * sumd)
c_q2_hist = (c_q2_hist + epsilon) / (1.0 + epsilon * sumd)

kl_ref = np.sum(ref_hist * np.log(ref_hist / ref_hist))
kl_c_q1 = np.sum(ref_hist * np.log(ref_hist / c_q1_hist))
kl_c_q2 = np.sum(ref_hist * np.log(ref_hist / c_q2_hist))


def to_labels(bins):
    labels = []
    for i in range(len(bins) - 1):
        labels.append((bins[i] + bins[i + 1]) / 2)
    return labels


ref_labels = to_labels(ref_bins)
q1_labels = to_labels(q1_bins)
q2_labels = to_labels(q2_bins)

plt.figure(figsize=(10, 5))

#plt.bar(ref_labels, ref_hist, label='ref')

plt.plot(ref_labels, ref_hist, label='ref')
plt.plot(q1_labels, q1_hist, label='q1')
plt.plot(q2_labels, q2_hist, label='q2')

plt.plot(ref_labels, c_q1_hist, label='q1 KL=%f' % kl_c_q1)
plt.plot(ref_labels, c_q2_hist, label='q2 KL=%f' % kl_c_q2)

plt.legend(title='histogram', loc='best')
plt.grid()

# plt.show()
plt.savefig('out.png')

https://gist.github.com/shtaxxx/6ca20df2cb7933291fdb9cb02ccf2088

f:id:sxhxtxa:20190422121826p:plain

参考

https://paper.hatenadiary.jp/entry/2018/10/13/164539

macOS Sierra上のMicrosoft Office各種でEmacsキーバインドを使う

macOS Sierraでは(現状)Karabinerが動作しないため,標準でEmacsキーバインドが利用できないアプリケーションにもEmacsキーバインドを割り当てることができずに困っていた.英かなでもキーバインドの変更が可能であるが,一部のアプリケーションでは一部のキーバインドを無効にしたい場合には対応していないため,Hammerspoonを使ってみた.


下記の記事に倣って,Hammerspoonをインストールし,.hammerspoon/init.luaを書いた.

qiita.com

自分の設定ファイルは以下の通りである. Word,ExcelPowerPointだけで良かったのだが,面倒だったので,アプリケーション名がMicrosoftを含むとき時だけアクティブになるように設定した. その他のアプリケーションでも同様の方法で設定できるはず.

local function keyCode(key, modifiers)
   modifiers = modifiers or {}
   return function()
      hs.eventtap.event.newKeyEvent(modifiers, string.lower(key), true):post()
      hs.timer.usleep(1000)
      hs.eventtap.event.newKeyEvent(modifiers, string.lower(key), false):post()      
   end
end

local function remapKey(modifiers, key, keyCode)
   hs.hotkey.bind(modifiers, key, keyCode, nil, keyCode)
end

local function disableAllHotkeys()
   for k, v in pairs(hs.hotkey.getHotkeys()) do
      v['_hk']:disable()
   end
end

local function enableAllHotkeys()
   for k, v in pairs(hs.hotkey.getHotkeys()) do
      v['_hk']:enable()
   end
end

local function handleGlobalAppEvent(name, event, app)
   if event == hs.application.watcher.activated then
      -- hs.alert.show(name)
      -- if name == "Microsoft Word" then
      if string.find(name, "Microsoft") then
         enableAllHotkeys()
      else
         disableAllHotkeys()
      end
   end
end

appsWatcher = hs.application.watcher.new(handleGlobalAppEvent)
appsWatcher:start()

remapKey({'ctrl'}, 'p', keyCode('up'))
remapKey({'ctrl'}, 'n', keyCode('down'))
remapKey({'ctrl'}, 'b', keyCode('left'))
remapKey({'ctrl'}, 'f', keyCode('right'))

remapKey({'ctrl'}, 'a', keyCode('home'))
remapKey({'ctrl'}, 'e', keyCode('end'))

remapKey({'ctrl'}, 'h', keyCode('delete')) -- backspace
remapKey({'ctrl'}, 'd', keyCode('forwarddelete')) -- delete

remapKey({'alt'}, 'w', keyCode('c', {'cmd'})) -- copy
remapKey({'ctrl'}, 'w', keyCode('x', {'cmd'})) -- cut
remapKey({'ctrl'}, 'y', keyCode('v', {'cmd'})) -- paste

Mac上のemacsでauto-complete-clang-asyncを使う

Emacsc/c++の補完をするためにauto-complete-clang-asyncを使うことにしました. 以下はその設定時のメモ.

Homebrewでemacs-clang-complete-asyncをインストールする

brew install emacs-clang-complete-async 

emacsでauto-complete-clang-asyncをインストールする

僕はpackage.elで自動インストールするように管理しています. 手動の場合には,"M-x package-list-packages" でpackage一覧を表示してからauto-complete-clang-asyncを指定すればインストールできるのでしょうか. 自動でパッケージをインストールする方法はコードを参照.

github.com

emacsのinit.elとかに設定を書く

僕はinit-loaderで設定を分けて管理しています.

20-auto-complete.el

(require 'auto-complete)
(require 'auto-complete-config)
(ac-config-default)
(global-auto-complete-mode t)

70-cc-mode.el

(autoload 'google-c-style "google-c-style" nil t)
(add-hook 'c-mode-common-hook 'google-set-c-style)
(add-hook 'c++-mode-common-hook 'google-set-c-style)
(add-hook 'c-mode-common-hook 'google-make-newline-indent)

(add-hook 'c-mode-common-hook
          '(lambda()
             (setq ac-clang-complete-executable "clang-complete")
             (when (executable-find ac-clang-complete-executable)
               (require 'auto-complete-clang-async)
               (setq ac-sources '(ac-source-clang-async))
               (ac-clang-launch-completion-process))))

設定一式

GitHubにアップロードしてあるのでそちらを参照してください.

github.com

Debian Linux on Zynq Setup Flow (for Vivado 2015.4)

Xilinx Zynq(ARM搭載FPGA)の上でDebian Linux(8.0 Jessie)を動作させるためのチュートリアルを作りました。Slideshareからどうぞ。

以下の手順で専用ハードウェア(といってもチュートリアルではGPIOのみ)をLinux経由で制御するHW/SW協調システムを開発します。

  • Zybo用FPGAボード設定ファイルの追加
  • Vivadoを用いたハードウェア開発
  • U-boot SPLとU-bootの生成
  • Linuxカーネルとデバイスツリーのカスタマイズと生成
  • Debianルートファイルシステムの構築
  • SDカードの準備
  • SDカードからの起動と初期設定
  • CMA (Continuous Memory Allocator)ドライバーのインストール
  • テストアプリケーションの実行
  • ビットファイルの変更方法

www.slideshare.net

アドバイス・コメント等がありましたらお知らせ頂けますと助かります。

PythonとVeriloggenのデータフローライブラリでパイプライン回路をお手軽に設計する

Veriloggen 0.5.0をリリースしました。Python 3.5をサポートしたり、遅延評価・合成の仕組みを入れたり、かなり意欲的な更新です。

github.com

最近、GoogleTensorFlowが流行っていますね。データフローですね。そこで、今回の目玉は、パイプライン回路をお手軽に設計できる、データフローライブラリ(lib.dataflow)です。

データフローライブラリを使えば、、制御信号(ready, valid)を持つ、RTLでの設計が面倒なパイプライン回路をPythonだけで設計することができます。しかも、データフローの可視化にも対応しています。

更に、シミュレーションライブラリ(lib.simulation)を使えば、Pythonだけでそのまま回路シミュレーションもできます。

では早速試してみましょう!

準備(ダウンロード&インストール)

以前の記事を参考にインストールしてください。 Pythonのライブラリ一式に加えて、Icarus Verilogが別途インストールされている必要があります。

shtaxxx.hatenablog.com

早速データフローを書いてみる

veriloggen/examples/dataflow_example/ にある例を参考にデータフローモデルで回路を書いてみましょう。

ヘッダー部

Veriloggenを含む各種ライブラリをインポートします。

from __future__ import absolute_import
from __future__ import print_function
import sys
import os
from veriloggen import *

回路本体 入出力ポート

今回は x*c+y (Mutiply Add)を計算するデータフローパイプラインを作ります。

ModuleオブジェクトのmがVerilogのモジュール定義を管理します。データフローで書けるんじゃないの?と思うと思いますが、lib.dataflowもあくまでVeriloggenのライブラリの一つなので、Verilogソースコードの一部としてデータフローを高い抽象度で組み立てていきます。

x, vx, rxがそれぞれ入力xのdata・valid・readyの信号に対応します。入力y、出力zも同様です。入力cは定数を入力するためのポートなので、valid, readyがありません。

def mkMultAdd():
    m = Module('multadd')
    clk = m.Input('CLK')
    rst = m.Input('RST')

    # data in X
    x = m.Input('x', 32)
    vx = m.Input('vx')
    rx = m.Output('rx')
    
    # data in Y
    y = m.Input('y', 32)
    vy = m.Input('vy')
    ry = m.Output('ry')

    # constant
    c = m.Input('c', 32)

    # data out Z
    z = m.Output('z', 32)
    vz = m.Output('vz')
    rz = m.Input('rz')

回路本体 データフロー本体

lib.Dataflowオブジェクトのdfがデータフローの定義を管理します。データフローを追加するVerilogモジュールとデータフロー名のプレフィックス、クロック信号・リセット信号を引数で渡します。

まず、通常のVerilogの信号からデータフロー変数を作ります。df.input()メソッドに、データフロー変数を構成する生の信号を渡します。データフロー変数pxは、入力xのデータと制御信号で定義しています。入力yのデータフロー変数pyも同様です。

    # dataflow manager
    df = lib.Dataflow(m, 'df', clk, rst)

    # input -> dataflow variable
    px = df.input(x, valid=vx, ready=rx)
    py = df.input(y, valid=vy, ready=ry)

次に、実際に演算を定義しましょう。まず、x*cを定義します。入力xのデータフロー変数pxに定数cを掛け合わせます。データフロー管理オブジェクトdfをメソッドとして呼び出し、演算の定義を引数で渡します。データフローに基づく演算結果もデータフロー変数になるため、dfメソッド呼び出しは新しいデータフロー変数を返します。

次にyの加算を追加し、x*c+yを定義しましょう。pzは pxc(px*cの結果)とpy(入力yのデータフロー変数)との積で定義されています。

    # dataflow definitions
    pxc = df(px * c)
    pz = df(pxc + py)

そして、データフロー変数pzを生のVerilog信号に接続して、データフロー変数の世界からVerilogの世界に戻ります。 pz.output()メソッドに接続先のVerilog信号を渡せば、自動的に接続されます。

最後にmake_always()で順序回路としてデータフロー回路を出力します。ついでに、データフローの定義をdraw_graph()メソッドで可視化しましょう。これを使うにはpygraphvizのインストールが必要です。

    # dataflow variable -> output
    pz.output(z, valid=vz, ready=rz)

    # generate always statement
    df.make_always()

    # draw dataflow graph in png
    try:
        df.draw_graph()
    except:
        print('Dataflow graph could not be generated.', file=sys.stderr)
    
    return m

テストベンチを含めた記述全体

上記の回路本体だけではVerilogシミュレータでの検証ができないので、シミュレーション用コードを付加した、コード全体を以下に記します。

from __future__ import absolute_import
from __future__ import print_function
import sys
import os
from veriloggen import *

def mkMultAdd():
    m = Module('multadd')
    clk = m.Input('CLK')
    rst = m.Input('RST')

    # data in X
    x = m.Input('x', 32)
    vx = m.Input('vx')
    rx = m.Output('rx')
    
    # data in Y
    y = m.Input('y', 32)
    vy = m.Input('vy')
    ry = m.Output('ry')

    # constant
    c = m.Input('c', 32)

    # data out Z
    z = m.Output('z', 32)
    vz = m.Output('vz')
    rz = m.Input('rz')

    # dataflow manager
    df = lib.Dataflow(m, 'df', clk, rst)

    # input -> dataflow variable
    px = df.input(x, valid=vx, ready=rx)
    py = df.input(y, valid=vy, ready=ry)

    # dataflow definitions
    pxc = df(px * c)
    pz = df(pxc + py)

    # dataflow variable -> output
    pz.output(z, valid=vz, ready=rz)

    # generate always statement
    df.make_always()

    # draw dataflow graph in png
    try:
        df.draw_graph()
    except:
        print('Dataflow graph could not be generated.', file=sys.stderr)
    
    return m

def mkTest():
    m = Module('test')

    # target instance
    madd = mkMultAdd()
    
    # copy paras and ports
    params = m.copy_params(madd)
    ports = m.copy_sim_ports(madd)

    clk = ports['CLK']
    rst = ports['RST']
    
    x = ports['x']
    vx = ports['vx']
    rx = ports['rx']
    y = ports['y']
    vy = ports['vy']
    ry = ports['ry']
    c = ports['c']
    z = ports['z']
    vz = ports['vz']
    rz = ports['rz']
    
    uut = m.Instance(madd, 'uut',
                     params=m.connect_params(madd),
                     ports=m.connect_ports(madd))

    reset_done = m.Reg('reset_done', initval=0)
    
    reset_stmt = []
    reset_stmt.append( reset_done(0) )
    reset_stmt.append( x(0) )
    reset_stmt.append( y(0) )
    reset_stmt.append( c(8) )
    reset_stmt.append( vx(0) )
    reset_stmt.append( vy(0) )
    
    lib.simulation.setup_waveform(m, uut)
    lib.simulation.setup_clock(m, clk, hperiod=5)
    init = lib.simulation.setup_reset(m, rst, reset_stmt, period=100)

    nclk = lib.simulation.next_clock
    
    init.add(
        Delay(1000),
        reset_done(1),
        nclk(clk),
        Delay(10000),
        Systask('finish'),
    )
    
    x_count = m.TmpReg(32, initval=0)
    y_count = m.TmpReg(32, initval=0)
    z_count = m.TmpReg(32, initval=0)
    
    xfsm = lib.FSM(m, 'xfsm', clk, rst)
    xfsm.add(vx(0))
    xfsm.goto_next(cond=reset_done)
    xfsm.add(vx(1))
    xfsm.add(x.inc(), cond=rx)
    xfsm.add(x_count.inc(), cond=rx)
    xfsm.goto_next(cond=AndList(x_count==10, rx))
    xfsm.add(vx(0))
    xfsm.make_always()
    
    
    yfsm = lib.FSM(m, 'yfsm', clk, rst)
    yfsm.add(vy(0))
    yfsm.goto_next(cond=reset_done)
    yfsm.add(vy(1))
    yfsm.add(y.add(2), cond=ry)
    yfsm.add(y_count.inc(), cond=ry)
    yfsm.goto_next(cond=AndList(y_count==10, ry))
    yfsm.add(vy(0))
    yfsm.make_always()

    
    zfsm = lib.FSM(m, 'zfsm', clk, rst)
    zfsm.add(rz(0))
    zfsm.goto_next(cond=reset_done)
    zfsm.goto_next()
    zinit= zfsm.current()
    zfsm.add(rz(1), cond=vz)
    zfsm.goto_next(cond=vz)
    for i in range(10):
        zfsm.add(rz(0))
        zfsm.goto_next()
    zfsm.goto(zinit)
    zfsm.make_always()


    m.Always(Posedge(clk))(
        If(reset_done)(
            If(AndList(vx, rx))(
                Systask('display', 'x=%d', x)
            ),
            If(AndList(vy, ry))(
                Systask('display', 'y=%d', y)
            ),
            If(AndList(vz, rz))(
                Systask('display', 'z=%d', z)
            )
        )
    )
    
    return m
    
if __name__ == '__main__':
    test = mkTest()
    verilog = test.to_verilog('tmp.v')
    print(verilog)

    sim = lib.simulation.Simulator(test)
    rslt = sim.run()
    print(rslt)

    #sim.view_waveform()

スクリプト実行

では、上記のコードを実行してみましょう。test.pyなど、好きな名前で上記のコードを保存して、実行します。

python test.py

すると、Verilogの長いソースコードが表示された後、シミュレーション結果が表示されたはずです。

スクリプトを実行したディレクトリに"tmp.v"というVerilogのファイルができているので、中身を確認しましょう。その中に、以下の様な回路本体の記述があるはずです。

Veriloggenのコードでは、演算の関係だけを定義したのですが、実際に生成される回路は、入力が有効かどうかを示すvalidビットや、出力側で現在の値を受理できるかどうかを示すreadyビットでパイプラインの動作が制御される必要があるため、その制御ロジックが自動で追加されています。これは手では書きたくないですね・・・。

module multadd
(
  input CLK,
  input RST,
  input [(32 - 1):0] x,
  input vx,
  output rx,
  input [(32 - 1):0] y,
  input vy,
  output ry,
  input [(32 - 1):0] c,
  output [(32 - 1):0] z,
  output vz,
  input rz
);

  assign rx = (_df_ready_0 || (!_df_valid_0));
  assign ry = (_df_ready_1 || (!_df_valid_1));
  reg [(32 - 1):0] _df_data_0;
  reg _df_valid_0;
  wire _df_ready_0;
  assign _df_ready_0 = (_df_ready_2 || (!_df_valid_2));
  reg [(32 - 1):0] _df_data_1;
  reg _df_valid_1;
  wire _df_ready_1;
  assign _df_ready_1 = (_df_ready_2 || (!_df_valid_2));
  reg [(32 - 1):0] _df_data_2;
  reg _df_valid_2;
  wire _df_ready_2;
  assign _df_ready_2 = (_df_ready_3 || (!_df_valid_3));
  reg [(32 - 1):0] _df_data_3;
  reg _df_valid_3;
  wire _df_ready_3;
  assign _df_ready_3 = rz;
  assign z = _df_data_3;
  assign vz = _df_valid_3;

  always @(posedge CLK) begin
    if(RST) begin
      _df_data_0 <= 0;
      _df_valid_0 <= 0;
      _df_data_1 <= 0;
      _df_valid_1 <= 0;
      _df_data_2 <= 0;
      _df_valid_2 <= 0;
      _df_data_3 <= 0;
      _df_valid_3 <= 0;
    end else begin
      if(((vx && rx) && (_df_ready_0 || (!_df_valid_0)))) begin
        _df_data_0 <= (x * c);
      end 
      if((_df_ready_0 || (!_df_valid_0))) begin
        _df_valid_0 <= (vx && rx);
      end 
      if(((vy && ry) && (_df_ready_1 || (!_df_valid_1)))) begin
        _df_data_1 <= y;
      end 
      if((_df_ready_1 || (!_df_valid_1))) begin
        _df_valid_1 <= (vy && ry);
      end 
      if((((_df_valid_0 && _df_ready_0) && (_df_valid_1 && _df_ready_1)) && (_df_ready_2 || (!_df_valid_2)))) begin
        _df_data_2 <= (_df_data_0 + _df_data_1);
      end 
      if((_df_ready_2 || (!_df_valid_2))) begin
        _df_valid_2 <= ((_df_valid_0 && _df_ready_0) && (_df_valid_1 && _df_ready_1));
      end 
      if(((_df_valid_2 && _df_ready_2) && (_df_ready_3 || (!_df_valid_3)))) begin
        _df_data_3 <= _df_data_2;
      end 
      if((_df_ready_3 || (!_df_valid_3))) begin
        _df_valid_3 <= (_df_valid_2 && _df_ready_2);
      end 
    end
  end


endmodule

データフロー定義の可視化

定義したデータフローがどのようなものか、ソースコードだけではイメージしづらいので、可視化しましょう。 Pygraphvizが正しくインストールされていれば、すでに df.draw_graph() でデータフロー定義の可視化結果が画像として生成されているので、それを開きます。今回の例ではout.pngです。

f:id:sxhxtxa:20151117013146p:plain

長方形がデータフロー変数、楕円が演算子、三角形がVerilogの生信号、台形がデータフロー変数を定義するVerilogの生信号の組をそれぞれ表しています。

Verilog信号のx, vx, rxでデータフロー変数が定義されており、それとcを掛け合わせたものが_df_data_0となっています。そしてそれと、Verilog信号y, vy, ryで定義されるデータフロー変数の1ステージ分遅れた値が足し合わされ、_df_data_2、_df_data_3と伝搬していき、出力zに接続されています。

途中、データフロー変数が自動で追加されるのは、入力された世代(段数)が同じ値ごとに演算を適用するためです。入力からの段数が違うもの同士を演算する場合、段数が少ない方に調整用のデータフロー変数が自動で追加されます。Verilogでの設計の場合には、時刻・段数が違う場合には設計者が明示的にレジスタを挟む必要がありますが、Veriloggenのデータフローライブラリの場合には、自動的に調整を行ってくれます。

シミュレーション

先ほどのスクリプト実行で表示されたシミュレーション結果は以下の通りのはずです。 今回の例ではc=8なので、xの10倍の値がzから出力されていればOKです。

VCD info: dumpfile uut.vcd opened for output.
x=         1
y=         2
x=         2
y=         4
x=         3
y=         6
x=         4
y=         8
z=        10
x=         5
y=        10
z=        20
x=         6
y=        12
z=        30
x=         7
y=        14
z=        40
x=         8
y=        16
z=        50
x=         9
y=        18
z=        60
x=        10
y=        20
z=        70
z=        80
z=        90
z=       100

テキストではわかりにくいので、波形で確認しましょう。GTKwaveをインストールした上で、上記スクリプトの最終行"#sim.view_waveform()"のコメントアウトを削除して、再実行すればよいGTKwaveで波形が表示されます。もしくは、以下の様に直接GTKwaveを起動しても良いです。

gtkwave --giga uut.vcd &

図の様に、x, yの入力に応じて、zの値が変化していることが確認できます。上記のシミュレーションパターンでは、出力zのready信号rzを意図的にぱたぱた変化させ、連続的にデータを受信できないようなケースを検証しています。rzの変化に応じて、パイプライン全体が制御されているのがわかります。

f:id:sxhxtxa:20151117012915p:plain

まとめ

Veriloggenのデータフローライブラリを使うと、お手軽に高性能なデータフローパイプライン回路がPythonだけで開発できます。データフロー定義の可視化もできます。FPGAなどのハードウェアアクセレータで高い性能・電力効率を達成するには、高い稼働率を持つ演算パイプラインを構築することが非常に重要です。そのような場合にも、Veriloggenを使えば、既存のHDLよりも少ない労力で、既存の高位合成よりも高い性能のハードウェアを設計することができます。

Pythonベースの高位合成コンパイラPolyphonyを試してみた

はじめに

高位合成友の会の第3回が12/8に開催されるらしいです。 (僕はPythonでのハードウェアメタプログラミングの話をします。)

hls.connpass.com

プログラムによると、PolyphonyというPythonベースの高位合成コンパイラの発表があるらしいです。Pythonで高位合成コンパイラを作っている自分としては、これは試さずにいられない!ということで、早速試してみました。

ダウンロード

公式Webからダウンロードしましょう。将来的にはオープンソースになるようですが、現在(11月12日)はzip化された実行形式のみが利用可能です。

http://www.sinby.com/PolyPhony/we-need-your-feedback.html

実行権限が必要なので、chmodしましょう。

chmod 755 polyphony

コンパイル対象のPythonソースコードを準備する

今回は、PyCoRAMのtestsにある、フィボナッチ数列を求める回路の記述を利用しました。以下のように、最低限のコードにしましょう。ここでは名前は"test.py"としましょう。

github.com

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

コンパイル

先ほどダウンロードしたpolyphonyを実行しましょう。"-o"オプションで出力ファイル名のprefixが指定出来るようです。

./polyphony -o fib test.py 

生成されたVerilog HDLコードをチェックする

"fib.v"という名前で、以下のようなコードが出力されているはずです。

ステートマシンをベースとした回路が生成されています。"READY", "ACCEPT", "VALID"で動作を制御するようですね。

module fib
  (
    input signed [31:0] fib_IN0,
    input CLK,
    input RST,
    input fib_READY,
    input fib_ACCEPT,
    output reg signed [31:0] fib_OUT0,
    output reg fib_VALID
  );

  //localparams
  localparam fib_grp_top0_INIT = 0;
  localparam fib_grp_top0_S0 = 1;
  localparam fib_grp_top0_S1 = 2;
  localparam fib_grp_top0_S2 = 3;
  localparam fib_grp_top0_S8 = 4;
  localparam fib_grp_top0_FINISH = 5;
  localparam fib_grp_ifthen1_S1 = 6;
  localparam fib_grp_ifthen3_S2 = 7;
  localparam L1_grp_top0_S0 = 8;
  localparam L1_grp_top0_S1 = 9;
  localparam L1_grp_forbody5_S2 = 10;
  localparam L1_grp_bridge6_S0 = 11;
  localparam L1_grp_bridge6_S3 = 12;
  
  //internal regs
  reg /*unsigned*/ [3:0] fib_state;
  reg signed [31:0] i;
  reg signed [31:0] prev_r1;
  reg signed [31:0] r0;
  reg signed [31:0] r1;
  reg signed [31:0] t10;
  
  
  //internal wires
  wire cond11;
  wire cond8;
  wire cond9;
  
  //functions
  
  //sub module instances
  
  //assigns
  assign cond8 = (fib_IN0 <= 0);
  assign cond9 = (fib_IN0 == 1);
  assign cond11 = (i < t10);
  
  always @(posedge CLK) begin
    if (RST) begin
      fib_OUT0 <= 0;
      fib_VALID <= 0;
      fib_state <= fib_grp_top0_INIT;
    end else begin //if (RST)
      case(fib_state)
      fib_grp_top0_INIT: begin
        if (fib_READY == 1) begin
          fib_VALID <= 0;
          fib_state <= fib_grp_top0_S0;
        end
      end
      fib_grp_top0_S0: begin
        if (cond8) begin
          fib_state <= fib_grp_ifthen1_S1;
        end else begin
          fib_state <= fib_grp_top0_S1;
        end
      end
      fib_grp_top0_S1: begin
        if (cond9) begin
          fib_state <= fib_grp_ifthen3_S2;
        end else begin
          fib_state <= fib_grp_top0_S2;
        end
      end
      fib_grp_top0_S2: begin
        r0 <= 0;
        r1 <= 1;
        i <= 0;
        fib_state <= L1_grp_top0_S0;
      end
      fib_grp_top0_S8: begin
        fib_OUT0 <= r1;
        fib_state <= fib_grp_top0_FINISH;
      end
      fib_grp_top0_FINISH: begin
        fib_VALID <= 1;
        if (fib_ACCEPT == 1) begin
          fib_state <= fib_grp_top0_INIT;
        end
      end
      fib_grp_ifthen1_S1: begin
        fib_OUT0 <= 0;
        fib_state <= fib_grp_top0_S1;
      end
      fib_grp_ifthen3_S2: begin
        fib_OUT0 <= 1;
        fib_state <= fib_grp_top0_S2;
      end
      L1_grp_top0_S0: begin
        t10 <= (fib_IN0 - 1);
        fib_state <= L1_grp_top0_S1;
      end
      L1_grp_top0_S1: begin
        if (cond11) begin
          fib_state <= L1_grp_forbody5_S2;
        end else begin
          fib_state <= fib_grp_top0_S8;
        end
      end
      L1_grp_forbody5_S2: begin
        prev_r1 <= r1;
        r1 <= (r0 + r1);
        fib_state <= L1_grp_bridge6_S0;
      end
      L1_grp_bridge6_S0: begin
        i <= (i + 1);
        fib_state <= L1_grp_bridge6_S3;
      end
      L1_grp_bridge6_S3: begin
        r0 <= prev_r1;
        fib_state <= L1_grp_top0_S0;
      end
      endcase
    end
  end
  
endmodule

まとめ

Pythonで手軽にハードウェア設計ができるのは素敵ですね! 自分以外にもPythonで高位合成に取り組んでいる方がいるのも嬉しいです。

PyCoRAMの高位合成エンジンも単体で切り出して、普通の高位合成コンパイラとして利用できるようにしようかなぁ、と思いました。