FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

ET/IoT Technology 2018のテクニカルセッションで講演します

f:id:msyksphinz:20181104135305p:plain

Embedded Technology(今はET/IoT Technologyと言うのかな) 2018のテクニカルセッションで講演することになりました。

発表の一週間前になりましたので、例によって告知です。

90分もしゃべります。まるで講義です。大学です。こんなの、普通にしゃべっていたら聴講者の皆さんは爆睡すること間違いなしです。

なるべく、デモンストレーションとか、動画とか出したいのですが、RISC-V関係で動画って良いのがあまり無いんです。HiFive Unleashedとか買っておけばよかった。

90分なので、少し細かいところまで立ち入る話をしたいです。ただし細かすぎる話をすると皆さん寝てしまうでしょうから、どうすればいいのかはまだ悩んでいます。

RISC-V関係で面白いビデオとか無いかなあ... まあ、アーキテクチャだけで90分しゃべるってのがそもそも厳しいんですけどね。

ちなみに、日経BPの関係者の方が、先月出版された「RISC-V原典」の日本語版の立ち読み版(1章だけ入っているペラペラの版)を会場に置いてくださるそうです。 ありがたや。

下の写真の薄い方です。

f:id:msyksphinz:20181019005314p:plain

という訳で、90分も時間が取れてしかも私のつまらない話を聞いて下さる心の広い方は、聴講をお待ちしております。

お手柔らかにお願いします。

Chisel-Bootcampを試してみる(2. FIRRTLについて)

f:id:msyksphinz:20181105012126p:plain

Chisel-Bootcampの続き。第4章はFIRRTLについて学習した。

Chiselで記述したハードウェアデザインは、FIRRTLのモジュールを組み込むことでVerilogに変換できる。 また、ASTの情報を作り上げて出力することができる。

Chiselを使ったこれらの処理の利点として、Verilogの情報をAST経由で集めることができる。 例えば、Chiselの中間表現のツリーを利用してレジスタの数を数えたり、回路構造の解析を行うことができるというのがメリットだ。

チュートリアルでは、stringifyASTとか、compileFIRRTLなどが出てくるが、これらはsource/load-ivy.scに出ているので写経する。 compileFIRRTLなんてのも、最終的にはcompiler.compileを呼び出しているのであまり中身をしっかり見る意味はないけれども...

という訳で一通り読み切った(と思うけど結構読み飛ばしながらやってるから復習しなきゃ...)。

次に見るべきは、

などを読み込んで、Chiselの高度な使い方について見ていこうと思う。

Chisel-Bootcampを試してみる(1. Chiselの基礎からScalaを活用した回路設計法まで)

f:id:msyksphinz:20181105012126p:plain

Chiselの勉強を本格的にやっているのだが、教材として良いものを見つけた。Chisel-Bootcampというものだ。

github.com

Chiselの基礎から、チュートリアル風に、サンプルを交えながら、途中でExerciseも出てくるのでそれをこなしながら進めていく。

本来はJupyter Notebook上でやるものらしいが、手元にJupyterの環境を作るのが面倒だし、どうせならLinux上で実行できる環境が欲しかったのでCUI上で実行できるリポジトリを作ってそちらで試している。

一応Chapter-1とChapter-2は適当に流してみた。Exerciseもやってみたが分からないものは模範解答を調べてしまったし、最適化も不十分なのであまり優良なコードではないかもしれない。 とはいえ、Chiselをフル活用して回路をデザインするためにはどうしたらよいのかしっかり解説してあるので、おすすめだ。

結構参考になっているのは、ScalaのMap関数やReduce関数を使ってうまく回路を組み上げていくこと。 この辺はソフトウェアの力を利用することで、かなり綺麗な記述でまとめ上げることができる。

例えば、FIRフィルタを設計する場合、

out = reg[0] * c[0] + reg[1] * c[1] + reg[2] * c[2] + reg[3] * c[3]
reg = {reg[2:0], in[0]}

という回路を作らなければならない。さらにFIRフィルタの長さが変わる要件も必要になる。この辺をChiselで書けば、

  val taps = RegInit(Vec(Seq.fill(length)(0.U(8.W))))
  for (i <- 0 until length) {
    if (i == 0) taps(0) := io.in
    else        taps(i) := taps(i-1)
  }
  io.out := (taps zip io.consts).map { case (a, b) => a * b }.reduce(_ + _)

となる(tapsの部分はまだ最適化不十分でまだ簡略化できる)。これだけだとChiselの方が長いしイマイチだが、Verilogでは不可能な型に対してGenericな書き方も可能だ。 例えばInt型に対するFIRと、FixedPointに対するFIRを同じデザインで設計できる。 その辺を使いこなしてやっと、Chiselマスターと言えるだろう、という気がしてきた。

ちゃんとまとめたいな、と思ったらすでにChisel-Bootcampを進めている方がいらっしゃった。 こちらの方が私の適当な文章よりも何倍もしっかり解説してあり、ありがたい。

www.tech-diningyo.info

Environmental-Modulesを止めてLmodに移行した

ツールチェインとかコンパイラの環境を変更するために、Environmental-Modulesというツールを使用している。

Environmental-Modulesは、使用するツールチェインに応じてPATHや環境変数を変えるためのツールだ。 ソフトウェア界隈ではあまり知られていないと思う。ハードウェア界隈でも知られていないと思うけど。 GCCのバージョンを変えたり、ツールを切り替えるのに便利なので重宝している。

しかし、Modulesは設定ファイルをtclで記述しなければならないなど、若干古臭いと思う(ここ最近でもバージョンアップを繰り返しているのでそうではないだろうが)。 一方でLmodはLuaを使って記述することができる(Lua自体はメジャーでは無いと思うが、tclよりかはマシか)。

Lmod: A New Environment Module System — Lmod 7.0 documentation

という訳で、Lmodを導入してみる。使用したのはUbuntu 18.04 LTSだ。

まずはLuaを導入する。

$ sudo apt install liblua5.1-0-dev lua5.1

次に、lua-posixを導入する。Ubuntu 18.04 LTSにはlua-posixのパッケージが存在しない?ので、ソースをダウンロードしてインストールした。

$ cd /tmp
$ curl -L https://github.com/luaposix/luaposix/archive/v34.0.4.tar.gz | tar xz
$ cd luaposix-34.0.4/
$ sudo apt install luarocks
$ sudo luarocks install luaposix-34.0.4-1.rockspec

次に、Lmodを導入する。こちらもソースをダウンロードしてインストールする。

$ cd /tmp
$ curl -L https://github.com/TACC/Lmod/archive/7.8.8.tar.gz | tar xz
$ cd Lmod-7.8.8/
$ ./configure
$ sudo make install

最後に、Bash用の設定をシンボリックリンクしておく。

$ sudo ln -s /usr/local/lmod/lmod/init/profile        /etc/profile.d/z00_lmod.sh

インストールできたか確認する。以下のコマンドをたたいて、moduleが有効かどうか確認しよう。

$ type module
module is a function
module ()
{
    eval $($LMOD_CMD bash "$@") && eval $(${LMOD_SETTARG_CMD:-:} -s sh)
}

Environmental-ModulesからLmodの設定移行

ModulesとLmodのコマンド系列は全く同じだ。 見た感じだと、Environmental-Modulesの設定ファイル(tclで書いてある)もLmodで食わせることができる様だ。

しかし、せっかくなのでLuaで設定を書き直したい。

どのようにして変更するのか調査した。

  • Environmental-Modulesの設定ファイル (riscv64)
#%Module1.0
##
proc ModulesHelp { } {
    puts stderr "RISC-V 64-bit Toolchain\n"
}

module-whatis   "RISC-V 64-bit Toolchain"

# append pathes
setenv       RISCV           $::env(HOME)/riscv64/
prepend-path PATH            $::env(HOME)/riscv64/bin
prepend-path LD_LIBRARY_PATH $::env(HOME)/riscv64/lib
  • Lmodの設定ファイル(riscv64.lua)
help([[
The TACC modulefile defines ...
]])

whatis("Keywords: System, Utility")
whatis("URL: https://github.com/riscv/riscv-tools")
whatis("Description: RISC-V Environment for BOOMv2")

setenv(       "RISCV"          , os.getenv("HOME").."/riscv64/"   )
prepend_path( "PATH"           , os.getenv("HOME").."/riscv64/bin")
prepend_path( "LD_LIBRARY_PATH", os.getenv("HOME").."/riscv64/lib")
  • setenvはそのまま書き換えられる。
  • prepend-pathprepend_pathに置き換える。
  • module-whatiswhatisに置き換える。
  • proc ModulesHelphelpに置き換えられる。
  • 環境変数の読み込みはos.getenv()で実行できる。

という訳で移植完了である。 快適、というか、使い心地は変わらないけれども。。。

$ module avail riscv

----------------------------- /home/msyksphinz/modules ------------------------------
   riscv/riscv32           riscv/riscv64_latest    riscv/riscv64 (L,D)
   riscv/riscv64_boomv2    riscv/riscv64_linux
   riscv/riscv64_fpga      riscv/riscv64_local

  Where:
   L:  Module is loaded
   D:  Default Module

Use "module spider" to find all possible modules.
Use "module keyword key1 key2 ..." to search for all possible modules matching any of the "keys".

$ module load riscv/riscv64

The following have been reloaded with a version change:
  1) riscv/riscv64_fpga => riscv/riscv64
f:id:msyksphinz:20181103180608p:plain

Pythonを経由してC++のオブジェクトを扱う方法(2. C++のオブジェクトをPythonのオブジェクトのように扱う方法)

f:id:msyksphinz:20181031233537p:plain

自作RISC-Vシミュレータは、バイナリファイルを指定するとそれを読み込んで、指定したプログラムカウンタの場所からシミュレーションを実行し、tohostのアクセスに到達するか最大実行サイクルに到達すると終了するのだが、そうでなく、もう少しInteractiveに操作できるようになるとうれしい。

前回は中途半端な状態でC++のシミュレータをPythonで動くようにした。ただし、最終目標は以下のように、 シミュレーションターゲットをオブジェクトのように扱うことだ。

#本当はchip.simulate()のように記述したいのだけれども、まだその方法が良く分からない。。。

import riscv_sim as riscv
chip = make_chip()
riscv.set_pc(chip, 0x0)
riscv.load_hex(chip, "test.riscv")
riscv.debug_mode(chip, true)
riscv.simulate(chip, 1000)

これを実現するための方法について調査していたのだが、やっとその方法を見つけて実装ができたのでそれをまとめておく。

参考にしたのは以下だ。

2. 拡張の型の定義: チュートリアル — Python 3.7.1 ドキュメント

実装したのは以下。python3_env.cppに実装している。

github.com

拡張型の定義

今回オブジェクトとしてPython上で扱いたいのは、シミュレータの実体(ここではRiscvPeThreadというクラスで定義されている、RISC-Vのアーキテクチャとシミュレーションエンジンを含んだクラス)だ。

これをPythonで扱うために、Python用のオブジェクトでWrapする。ここではRiscvPeObjectとした。

typedef struct {
  PyObject_HEAD
  RiscvPeThread *pe_thread;
} RiscvPeObject;

このオブジェクトに対する変数と、メソッドを定義するためにPyTypeObject構造体を新しい型を定義する。

static PyTypeObject RiscvPeType = {
  PyVarObject_HEAD_INIT(NULL, 0)
  tp_name      : "riscv.RiscvPe",
  tp_basicsize : (Py_ssize_t) sizeof(RiscvPeObject),
  tp_itemsize  : 0,
  tp_dealloc   : (destructor) RiscvPeDealloc,
  tp_flags     : Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
  tp_doc       : "Riscv objects",
  tp_methods   : riscv_chip_methods,
  tp_members   : NULL,
  tp_init      : (initproc) InitRiscvChip,
  tp_new       : MakeRiscvChip
};

ここで設定したのは、

  • tp_name : オブジェクトの型名
  • tp_basicsize : オブジェクトのサイズ
  • tp_methods : オブジェクトから呼び出すことのできるメソッド。ここではriscv_chip_methods変数でメソッド一覧を定義した。
  • tp_members : オブジェクトの参照できる変数。ここではNULLだが、変数を経由して参照できる変数一覧を定義できる。
  • tp_init : オブジェクトをインスタンスしたときに呼ばれるinit関数を定義する。
  • tp_new : オブジェクトをnewしたときに呼ばれる関数を定義する。

riscv_chip_methodsでこのオブジェクトに割り付けられているメソッドを定義した。 ここでは、py_add(テスト用)、simulateload_binの3つのメソッドを定義している。

PyMethodDef riscv_chip_methods[] = {
  { "py_add"     , (PyCFunction)HelloAdd            , METH_VARARGS, "Example ADD"                    },
  { "simulate"   , (PyCFunction)SimRiscvChip        , METH_VARARGS, "Simulate RiscvChip"             },
  { "load_bin"   , (PyCFunction)LoadBinaryRiscvChip , METH_VARARGS, "Load Binary file"               },
  { NULL         , NULL                             ,            0, NULL                             } /* Sentinel */
};

simulate()が呼び出されると、C++側ではSimRiscvChip()が、load_bin()が実行されるとLoadBinaryRiscvChip ()が呼び出されるようになっている。

それぞれは以下のように定義している。

static PyObject* SimRiscvChip (RiscvPeObject *self, PyObject* args)
{
  self->pe_thread->SetMaxCycle (100);
  self->pe_thread->StepSimulation(100, LoopType_t::FiniteLoop);

  return PyLong_FromLong(0);
}

static PyObject* LoadBinaryRiscvChip (RiscvPeObject *self, PyObject* args)
{
  char *filename;
  if (!PyArg_ParseTuple(args, "s", &filename)) {
    return PyLong_FromLong (-1);
  }

  if (self->pe_thread->LoadBinary("swimmer_riscv", filename, true) == -1) {
    return PyLong_FromLong (-1);
  }

  return PyLong_FromLong (0);
}

さらに、tp_newで設定したnew時に呼ばれる関数として MakeRiscvChip()を実装した。

static PyObject* MakeRiscvChip (PyTypeObject *type, PyObject* args, PyObject *kwds)
{
  RiscvPeObject *self = (RiscvPeObject *) type->tp_alloc(type, 0);

  RiscvPeThread *chip = new RiscvPeThread (stdout, RiscvBitMode_t::Bit64, 0xffffffff, PrivMode::PrivUser,
                                           true, true, stdout, true, "trace_out.log");

  self->pe_thread = chip;

  return (PyObject *)self;
}

これでビルドをして、Pythonモードを呼び出してみる。 以下のコマンドでシミュレーションができるようになった。これはうれしい。

$ swimmer_riscv --py
>>> chip = riscv.RiscvChip()
>>> chip.load_bin("test.elf")
>>> chip.simulate(1000)

Quantum Convolutional Neural Networksの勉強

ニューラルネットワーク量子コンピュータの考え方で改良する方法、Quantum Convolutional Neural Networksについて論文を読んでみた。

Quantum Convolutional Neural Networksを「量子畳み込みニューラルネットワーク」と考えればよいのだろうか。 畳み込みニューラルネットワーク量子コンピュータの考え方で変形して、量子コンピュータを使って解くためのネットワークらしい。

Our quantum convolutional neural network (QCNN) makes use of only O(log(N)) variational parameters for input sizes of N qubits, allowing for its efficient training and implementation on realistic, near-term quantum devices.

うーん、N量子ビットを入力して、どうして \log Nのパラメータで畳み込みニューラルネットワークを作れるのか、まだ理解できていない。

[1810.03787] Quantum Convolutional Neural Networks

面白そうだから、という理由で読んでみたのだが、私の量子物理学の知識が無さすぎて何が何だか良く分からない。 私の手元には量子情報処理の本しか無いので、ネット上の情報に頼るしかなく、前提知識が無さすぎるのがかなりつらい。

一生懸命読んでみると、これまでの畳み込みニューラルネットワークの構造をそのままに、そこに量子ゲートを適用するという考え方のように見える。

f:id:msyksphinz:20181103221459p:plain
図. 畳み込みニューラルネットワークと、量子畳み込みニューラルネットワーク (本文より引用)

畳み込み処理の部分はユニタリ行列を使った量子ゲートの適用、Pooling層の部分も同様にユニタリ行列を適用するらしい。

Pythonを経由してC++のオブジェクトを扱う方法

自作RISC-Vシミュレータは、バイナリファイルを指定するとそれを読み込んで、指定したプログラムカウンタの場所からシミュレーションを実行し、tohostのアクセスに到達するか最大実行サイクルに到達すると終了するのだが、そうでなく、もう少しInteractiveに操作できるようになるとうれしい。

たとえば、条件を指定して(特定のメモリにアクセスすると停止する、とか)内部の状態を読み取ったり、状態を変更できるようにしたい。 一番便利なのはGDBプロトコルをサポートすることだ。これはそのうち実装したい。 しかし、GDBを使うとリグレッションの時に不便だ。もう少し気軽にシミュレータの挙動を制御するために、Pythonインタラクティブモードをサポートする。

f:id:msyksphinz:20181031233537p:plain

イメージとしては、以下のようなPythonスクリプトをシミュレータに食わせると、Pythonコードに従ってシミュレーションが実行される。

#本当はchip.simulate()のように記述したいのだけれども、まだその方法が良く分からない。。。

import riscv_sim as riscv
chip = make_chip()
riscv.set_pc(chip, 0x0)
riscv.load_hex(chip, "test.riscv")
riscv.debug_mode(chip, true)
riscv.simulate(chip, 1000)

make_chip()によりシミュレータのオブジェクト(内部ではRiscvPeThreadというRISC-Vのシミュレーションを司るクラス)を作成し、それをPythonのオブジェクトとして渡している。

このmake_chip()C++で具体的には以下のように実装している。

PyMethodDef riscv_methods[] = {
  { "py_add"     , (PyCFunction)HelloAdd     , METH_VARARGS, "Example ADD"                    },
  { "riscv_chip" , (PyCFunction)MakeRiscvChip, METH_VARARGS, "Make RiscvChip"                 },
...
PyObject* MakeRiscvChip (PyObject* self, PyObject* args)
{
  RiscvPeThread *chip = new RiscvPeThread (stdout, RiscvBitMode_t::Bit64, 0xffffffff, PrivMode::PrivUser,
                                           true, true, stdout, true, "trace_out.log");

  PyObject* chip_capsule = PyCapsule_New((void *)chip, "chip_ptr", NULL);
  PyCapsule_SetPointer(chip_capsule, (void *)chip);

  return Py_BuildValue("O", chip_capsule);
}

あまり情報が無くて困ったのだが、C++で生成したオブジェクトをPythonのオブジェクト(PyObject)として返すためには、PyCapsule_New()という関数を使用する。

カプセル — Python 3.6.5 ドキュメント

より詳しい解説は、以下のWebサイトを参考にした。

C++ class in Python3 – Speed up code

上記では、RiscvPeThreadという型のオブジェクトをPyCapsule_New()でPyObjectに変換し、Py_BuildValue()を使ってPythonのオブジェクトに変換して返す。

一方で、Pythonのオブジェクトを受け取ってそれをC++のオブジェクトに変換するためには、PyCapsule_GetPointer()を使用する。 PyCapsule_GetPointer()により、PythonのオブジェクトからC++のオブジェクトに変換する訳だ。

PyObject* SimRiscvChip (PyObject* self, PyObject* args)
{
  PyObject *py_chip;
  int max_inst;
  if (!PyArg_ParseTuple(args, "Oi", &py_chip, &max_inst)) {
    return NULL;
  }

  RiscvPeThread *chip = (RiscvPeThread *)PyCapsule_GetPointer(py_chip, "chip_ptr");

  chip->SetMaxCycle (max_inst);
  chip->StepSimulation(max_inst, (max_inst == 0) ? LoopType_t::InfLoop : LoopType_t::FiniteLoop);

  return Py_BuildValue("I", 0);
}

上記の機能を実装して、自作RISC-VシミュレータをPythonスクリプトで扱えるようにした。

今後は、Pythonから扱える機能を増やしていこう。

github.com