FPGA開発日記

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

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