シミュレーターからPythonを実行して協調シミュレーションをする

この記事は、[半導体・ハードウェア開発 Advent Calendar 2017] (https://qiita.com/advent-calendar/2017/hardware) の10日目の記事です。


Linuxで作ったアプリケーションからFPGAを制御する場合、
アプリのデバッグが面倒だったのでHDLのシミュレータで
ソフトウェアとハードウェア側のコードの同時にデバッグを行ってみました。

Linuxのアプリはpython & c++で実装したのでDPI-Cだけでは
全部は確認できないためPythonも一緒に行えるようにしています。

# ゴール
Modelsim Altera Starter EditionでPythonを読み込み、
SystemVerilog DPI-CでexportしたtaskまたはfunctionをPythonから実行できるようにします。

# 実行環境

- CentOS6.9 32bit
- Modelsim ASE 10.5b
- Python2.7

現状では32bit環境で実行する必要があります。
またPythonのバージョンも2.7である必要があります。
理由については後述します。

# 準備
## Python環境のインストール
Pythonの環境は、pyenv+anacondaで構築します。
Python2.7はcondaで仮想環境をつくってインストールします。
anacondaのバージョンは何でもいいかもしれません。

```bash
git clone https://github.com/pyenv/pyenv ~/.pyenv_32bit
export PYENV_ROOT="$HOME/.pyenv_32bit"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

pyenv install anaconda3-4.0.0
conda create -n py2 python=2.7
source activate py2
```

## boostのインストール
DPI-Cとの接続はCまたはC++でしか接続できない(と思う)のでpythonとC/C++を接続する必要があります。
pythonとC/C++との接続にはboost-pythonを使用し、これもcondaでインストールします。
なぜboostかというと一番簡単そうだったからです。
anacondaのboostはこの時点では1.65.1で確認しています。

```bash
conda install boost
```

# 構成
1.SystemVerilogからDPI-CでimportしたCの関数を実行する。
2.importされたCの関数からPythonのスクリプトを実行する。
3.実行されたPythonのスクリプトからCのモジュールをimportする。
4.importされたCのモジュールからDPI-CでexportされたSystemVerilogのtaskを実行する。

# コード
## SystemVerilog
テストベンチのTOP階層です。
test_main()をimport、test_task()をexportします。

```verilog:top.sv
`timescale 1ns/1ps

module top();
    import "DPI-C" context task test_main();
    export "DPI-C" task test_task;

    initial begin
        test_main();
        $finish();
    end

    task test_task();
        $display("Hello World @SystemVerilog");
    endtask // test_task

endmodule // top
```

## SystemVerilogでimportしたtest_main()
test_main()は、Cの関数でここでpythonを呼び出します。
boost-pythonで実行するpythonを指定し、スクリプト内にあるfunc()を実行します。

```cpp:test.cpp
#include <boost/python.hpp>
#include "dpiheader.h"

int test_main() {
    Py_Initialize();

    auto main_ns = boost::python::import("__main__").attr("__dict__");

    try {
        boost::python::object result = boost::python::exec_file("test.py", main_ns, main_ns);
        boost::python::object func = main_ns["func"];
        func();
    }
    catch (boost::python::error_already_set) {
        PyErr_Print();
    }

    return 0;
}
```

## cから実行されるpythonスクリプト
pythonからはpy_moduleのモジュールをimportします。
py_moduleからimportしたpy_funcという関数を実行しています。

```python:test.py

from py_module import *

def func():
    print("Hello World @Python")
    print (py_func())

```

## pythonでimportしたモジュール
SystemVerilogでexportしたtest_task()を
boost-pythonでpythonに対して公開しています。

```cpp:py.cpp

#include <iostream>
#include <boost/python.hpp>
#include "dpiheader.h"

using namespace boost::python;

std::string py_func()
{
    std::cout << "Hello World @C++\n";
    test_task();
    return std::string("Hello World");
}

BOOST_PYTHON_MODULE(py_module)
{
    def("py_func",&py_func);
}

```

## Makefile
Makefileは下記のようになります。

```:Makefile
SV_FILE = top.sv
TOP_NAME = top
SRC = py.cpp test.cpp
DPI_OBJ = py_module

MODELSIM_HOME = /**********/modelsim_ase
ANACONDA_HOME = $(PYTHONHOME)
VLIB = vlib
VLOG = vlog
VSIM = vsim
CC = g++

OBJ = $(SRC:%.cpp=%.o)

all : vlib vlog vsim

vlib:
	$(VLIB) work

vlog:
	$(VLOG) -sv -dpiheader dpiheader.h $(SV_FILE)

vsim: $(DPI_OBJ).so
	$(VSIM) -c -sv_liblist lib.txt $(TOP_NAME)  -do "run -all; quit -f"

.cpp.o:
	$(CC) -c -fpic -m32 -std=c++0x -L$(ANACONDA_HOME)/lib -I$(ANACONDA_HOME)/include  -I$(MODELSIM_HOME)/include -lboost_python `python-config --cflags` $<

$(DPI_OBJ).so :$(OBJ)
	$(CC) -shared -m32 -L$(ANACONDA_HOME)/lib -lboost_python `python-config --ldflags` $(OBJ) -o $(DPI_OBJ).so

clean:
	rm -rf work $(DPI_OBJ).so $(OBJ) transcript *.wlf dpiheader.h

```

vsim時はpy_module.soをロードするようにしていしますが
boostのバージョンの関係でglibcの要求がmodelsimのgccに含まれているバージョンでは足りなかったので
anacondaでのglibcを同時にロードしています。
そのためにlib.txtのbootstrapファイルを作成しています。
HOMEの部分は書き換える必要があります。

```lib.txt
#!SV_LIBRARIES
HOME/.pyenv_32bit/versions/anaconda3-4.0.0/envs/py2/lib/libstdc++
py_module
```

## 実行
実行は環境変数を設定し、makeします。

```
export PYTHONHOME="${HOME}/.pyenv_32bit/versions/anaconda3-4.0.0/envs/py2"
export PYTHONPATH="."
export LD_LIBRARY_PATH="${PYTHONHOME}/lib":${LD_LIBRARY_PATH}

make
```

# 実行結果
一応、全部実行できています。

```
# 10.5b

# vsim -c -sv_liblist lib.txt top -do "run -all; quit -f"
# Start time: 11:45:43 on Dec 05,2017
# Loading /*********************_dpi_4199/linuxpe_gcc-4.7.4/export_tramp.so
# Loading sv_std.std
# Loading work.top
# Compiling /*********************_dpi_4199/linuxpe_gcc-4.7.4/exportwrapper.c
# Loading /tmp/*****************_dpi_4199/linuxpe_gcc-4.7.4/vsim_auto_compile.so
# Loading /*****************/pyenv_32bit/versions/anaconda3-4.0.0/envs/py2/lib/libstdc++.so
# Loading ./py_module.so
# run -all
# Hello World @Python
# Hello World @C++
# Hello World @SystemVerilog
# Hello World
# ** Note: $finish    : top.sv(9)
#    Time: 0 ps  Iteration: 0  Instance: /top
# End time: 11:45:43 on Dec 05,2017, Elapsed time: 0:00:00
# Errors: 0, Warnings: 0
```

# 制約事項

- osの32bit
pyenvからanacondaをインストールするのに強制的に
x86にする方法がおそらくないため32bitで作成しています。
いったん32bitで作成したpyenvの環境はx86_64からも実行可能で
同様にシミュレーションは実行できました。

- python2.7
python3にするといろいろ問題があって解決できていません。
   - python3にするとboost-1.65.1にするとundefined functionが発生する。
   - boost-1.61.0にするとpythonからのprintがmodelsimのプロンプトに表示されない。  
    （実行自体はきちんとできているようです）

# おわり
制約事項が多いので適用する範囲は限られますが実機前の確認としては十分だと思います。
もっと簡単な方法があれば教えてください。

# 参考

[データサイエンティストを目指す人のpython環境構築 2016](https://qiita.com/y__sama/items/5b62d31cb7e6ed50f02c)
[ModelSimASE+clangでSystemVerilog DPI-Cをシミュレーションする](https://qiita.com/ginnyu_tei/items/c6095df17bcbbcec8541)