自作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
に実装している。
拡張型の定義
今回オブジェクトとして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
(テスト用)、simulate
、load_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)