ModelSim XE を使った SystemVerilog DPI-C テスト の履歴(No.6)
更新- 履歴一覧
- 差分 を表示
- 現在との差分 を表示
- ソース を表示
- 電気回路/HDL/ModelSim XE を使った SystemVerilog DPI-C テスト へ行く。
Verilog HDL のテストに C++ コードを用いたい†
最近、Verilog を使って FPGA 内部の回路設計をしています。
開発環境としては Xilinx の ISE WebPack で Verilog コードを書いて、
ModelSim XE (Xilinx Edition) Free でテストベンチを動かしています。
通常、Verilog のテストをするには Verilog でテストベンチを書きます。
しかし Verilog でソフトウェアとのインタフェースなどの複雑なテストを
記述するのは、言語が非力なためにかなり骨が折れます。
ISE は Verilog 2001 までしか対応していないのに対して、
ModelSim は上位互換の SystemVerilog を使うことができるので、
テストベンチの記述がいくらか楽になります。
さらに、SystemVerilog の DPI-C という機能を使うと、テストの一部を
C や C++ のコードを使って記述でき、非常に複雑なテストも書ける
ようになります。
この DPI-C を Xilinx から入手可能な無料の開発環境上で利用する
方法を調べてみました。
情報源†
- All of SystemVerilog - SystemVerilogの世界へようこそ > SystemVerilog DPI-C
http://sites.google.com/site/allofsystemverilog/Home/dpi-c
- Tech Village - 無償ツールで実践する「ハード・ソフト協調検証」(1) —— SystemVerilogのDPI-C機能
http://www.kumikomi.net/archives/2009/12/_1systemverilogdpi-c.php
- FPGAの部屋 - 無償ツールで実践する「ハード・ソフト協調検証」をやってみる
http://marsee101.blog19.fc2.com/blog-entry-1389.html
- ModelSim の [Help] から User's Manual の Appendix D - Verilog Interface to C
- C:\Modeltech_xe_starter\examples\systemverilog\dpi にあるサンプル
- HDL Simulator Veritak - SystemVerilog Tutorial
http://japanese.sugawara-systems.com/systemverilog/SystemVerilog_Simulator.htm - C:\Modeltech_xe_starter\include\svdpi.h などの記述
- GNU make 日本語訳(Coop編)
http://www.ecoop.net/coop/translated/GNUMake3.77/make_toc.jp.html
DPI-C でできること†
DPI-C により、
- verilog から C の関数を呼び出せる
- verilog から呼び出された C の関数から verilog の task / function を呼び出せる
ようになります。
C から Verilog コードを呼ぶ準備として、Verilog の task や function を export しておきます。
逆に Verilog コードから C の関数を呼ぶには、Verilog 側で C の関数を import しておきます。
import / export される Verilog task/function の、C 側でのインターフェースを
ヘッダファイル(下では dpiheader.h)として自動生成できるので、それに合わせて
C 側のコードを記述します。
Verilog 側:
LANGUAGE:verilog // Verilog の task を実装する task verilog_task; begin // 何らかの処理 end endtask // Verilog の task をエクスポートする export "DPI-C" task verilog_task; // C の関数 c_task() をインポートする import "DPI-C" context task c_task(); // C の関数を呼び出す initial c_task();
C 側:
LANG:C #include "svdpi.h" #include "dpiheader.h" int c_task() // C 側の関数を実装する { verilog_task(); // Verilog の task を呼び出す }
このようなテストが無償のソフトウェアだけで実現できます。
必要なもの†
Xilinx の FPGA 開発に使える無償環境として以下を揃えました。
- Xilinx ISE WebPack
統合開発環境
- ModelSim Xilinx Edition-III (MXE-III)
Xilinx 向けの ModelSim 無償版
- ModelSim Xilinx Edition Libraries Update
Xilinx でダウンロード可能な ModelSim XE は古いので、 そのライブラリを最新版に置き換える必要があります
- MinGW
C/C++ のコンパイル・リンクを行うための gcc や g++ といった開発環境
MinGW-5.1.6.exe などという名前のファイルがインストーラなので、 それを落として起動すると、サブコンポーネントとして何を入れるかを聞かれます。 c++ や make を使うのであれば、ここで忘れずに選択します。
- cygwin
MinGW とは異なる系統の Windows 上の GNU 環境。
Windows のコマンドプロンプトが使いにくすぎるので、 cygwin を入れて bash 上でコマンドを入力しています。
同様なものに、MinGW 上で bash などを提供する MSYS というのがあるらしいのだけれど、 cygwin の X-Window や sshd などを他の用途に使っているので、敢えて cygwin で。
本当は cygwin の gcc で開発ができれば言うことがないのだけれど、 どうやら ModelSim の DPI-C は cygwin の gcc と合わせて使うことはできないようです。
MSYS でもほとんど何も変わらずできるはず?!
前提条件†
- ISE と ModelSim で通常の開発ができる事を確認しておく
環境変数等の整備†
ここでは筆者の都合で cygwin を使っているので面倒な手順が必要になっています。 始めから MinGW 上で MSYS を使う場合には、make の名称変更以外の手順は必要ないのだと思います。
cygwin の bash を通常通り起動すると、cygwin 側の gcc や g++ にパスが通っているために MinGW の開発環境を使うことができません。そこで、PATH 環境変数の先頭に MinGW のパスを入れます。
また、MinGW の make は mingw32-make.exe という名前になっているので、 これを make という名前で使えるようにします。
最後に、ModelSim へのパスも通しておきます。
LANG:console $ export PATH=/cygdrive/c/MinGW/bin:$PATH $ which gcc /cygdrive/c/MinGW/bin/gcc $ which make $ alias make='/cygdrive/c/MinGW/bin/mingw32-make.exe' $ make mingw32-make: *** No targets specified and no makefile found. Stop. $ export PATH=/cygdrive/c/Modeltech_xe_starter/win32xoem:$PATH
毎回これを入力するのは面倒なので、mingw-environment.sh というスクリプトを作っておき、次回からはこれを起動することにします。
LANG:console $ cat > mingw-environment.sh #/usr/bin/bash export PATH=/cygdrive/c/MinGW/bin:$PATH alias make=/cygdrive/c/MinGW/bin/mingw32-make.exe export PATH=/cygdrive/c/Modeltech_xe_starter/win32xoem:$PATH $ chmod u+x mingw-environment.sh $ ./mingw-environment.sh
簡単な例で確かめる†
verilog から C を、C から verilog を呼び出せることを確認するため、 以下の手順を踏みました。
- Verilog コードを記述する
Verilog と C との相互呼び出しのための import / export を含んだコードを書きます
- C 用のヘッダファイルを自動生成する
Verilog コードをコンパイルすることで、import / export される task / function の
C 言語におけるプロトタイプを記述したヘッダファイル dpiheader.h を自動生成します。
- C から Verilog を呼び出すためのオブジェクトコードを自動生成する
Verilog の export 記述を元に、C から Verilog を呼び出すためのコードを自動生成します
- 生成したヘッダファイルを使って C コードを記述する
Verilog の要求するプロトタイプ通りに C コードを記述します
- C コードをコンパイルする
- 3. と 5. のオブジェクトコードをリンクして dll を作る
- dll を指定して ModelSim を起動する
詳細手順は以下の通りです。
Verilog コードを記述する†
Verilog と C との相互呼び出しのための import / export を含んだコードを書きます
LANG:console $ mkdir test_dpi-c $ cd test_dpi-c $ cat > hello.v `timescale 1ns / 1ps // トップモジュール module hello_top; // verilog で書いた task task verilog_task(input int i, output int o); $display("Hello from verilog_task(%d)", i); endtask // C から使うためにエクスポートする export "DPI-C" task verilog_task; // C で書かれた関数をインポートする import "DPI-C" context task c_task(input int i, output int o); // C で書かれた関数を呼び出す int ret; initial c_task(1, ret); // c_task は verilog_task を呼び出す endmodule
C 用のヘッダファイルを自動生成する†
verilog ソースには、import / export される関数やタスクがすべて書かれているはずです。
これをコンパイルすることで、C 言語側で使うヘッダファイル dpiheader.h を作成します。
LANG:console $ vlib work $ vlog -sv -novopt -dpiheader dpiheader.h hello.v Model Technology ModelSim XE III vlog 6.4b Compiler 2008.11 Nov 15 2008 -- Compiling module hello_top Top level modules: hello_top
-sv は .v ファイルを SystemVerilog として解釈させるためのオプションで、 始めから hello.sv というファイル名にしておけば必要ありません。
できあがった dpiheader.h は次のようになりました。
LANG:C /* MTI_DPI */ /* * Copyright 2002-2008 Mentor Graphics Corporation. * * Note: * This file is automatically generated. * Please do not edit this file - you will lose your edits. * * Settings when this file was generated: * PLATFORM = 'win32pe' */ #ifndef INCLUDED_DPIHEADER #define INCLUDED_DPIHEADER #ifdef __cplusplus #define DPI_LINK_DECL extern "C" #else #define DPI_LINK_DECL #endif #include "svdpi.h" DPI_LINK_DECL DPI_DLLESPEC int c_task( int i, int* o); DPI_LINK_DECL int verilog_task( int i, int* o); #endif
中を見てみると、
LANG:Verilog export "DPI-C" task verilog_task;
に対応する、
LANG:C DPI_LINK_DECL int verilog_task(int i, int* o);
および、
LANG:Verilog import "DPI-C" context task c_task(input int i, output int o);
に対応する、
LANG:C DPI_LINK_DECL DPI_DLLESPEC int c_task(int i, int* o);
が宣言されているのが分かります。
DPI_LINK_DECL は C / C++ の両方で使えるライブラリを作成するときの常套手段で、 関数名が C++ で マングリング されないようにするための記述です。
C から Verilog を呼び出すためのオブジェクトコードを自動生成する†
C から Verilog の task/function を呼び出すためのコード cexports.obj を作成します。
LANG:console $ vsim hello_top -dpiexportobj cexports -c Reading C:/Modeltech_xe_starter/tcl/vsim/pref.tcl # 6.4b # vsim -dpiexportobj cexports -c hello_top # Loading sv_std.std # Loading work.hello_top # Compiling c:\...\work\_dpi\win32pe_gcc-3.4.5\exportwrapper.c # Successfully generated DPI export object 'cexports.obj'.
vsim の直後に与えるのは トップモジュール名
cexports は出力される .obj ファイル名
ここでできあがった cexports.obj に、DPI_LINK_DECL int verilog_task(int i, int* o); の実体が定義されています。
生成したヘッダファイルを使って C コードを記述する†
dpiheader.h のプロトタイプに合わせて、c_task を記述し、verilog_task を呼び出します。
LANG:console $ cat > hello.c #include "svdpi.h" #include "dpiheader.h" int c_task(int i, int *o) { printf("Hello from c_task(%d)\n", i); verilog_task(i, o); /* Call back into Verilog */ *o = i; return(0); /* Return success (required by tasks) */ }
C コードをコンパイルする†
C コードをコンパイルして hello.o を作ります。
LANG:console $ gcc -c -g -I'C:\Modeltech_xe_starter\include' hello.c -o hello.obj
hello.o と cexports.obj とをリンクして dll を作成する†
hello.o と cexports.obj とをリンクして cimports.dll という dll を作成します。
LANG:console $ gcc -shared -o cimports.dll hello.obj cexports.obj -L'C:\Modeltech_xe_starter\win32xoem' -lmtipli
dll を指定して ModelSim を起動する†
dll を指定して ModelSim を起動すると、 シミュレータが dll 内のコードを呼び出すことで C 側の関数が起動されます。
LANG:console $ vsim -c -sv_lib cimports hello_top -do "run -all;quit -f" Reading C:/Modeltech_xe_starter/tcl/vsim/pref.tcl # 6.4b # vsim -c -sv_lib cimports hello_top -do "run -all; quit -f" # Loading sv_std.std # Loading work.hello_top # Loading .\cimports.dll # run -all # Hello from c_task(1) # Hello from verilog_task(1) # quit -f
-c はバッチモードで(コマンドラインで)起動を意味します
-c が無ければ gui が起動します
Verilog から C の呼び出し、
C から Verilog の呼び出し、
共にうまく行っている様子です。
C++ を使うには†
dpiheader.h には
LANG:C++ #ifdef __cplusplus #define DPI_LINK_DECL extern "C" #else #define DPI_LINK_DECL #endif
という定義が含まれており、
import / export された関数のプロトタイプには DPI_LINK_DECL が付いていますので、
dpiheader.h で宣言される関数名が C++ の規則で マングル
されることはありません。
したがって、ユーザーが用意する C++ のソースでしなければならないのは、 関数の定義に DPI_LINK_DECL を付けることだけです。
hello.cpp
LANG:C++ #include "svdpi.h" #include "dpiheader.h" #include <iostream> DPI_LINK_DECL int c_task(int i, int *o) { std::cout << "Hello from c_task() in cpp" << std::endl; verilog_task(i, o); /* Call back into Verilog */ *o = i; return(0); /* Return success (required by tasks) */ }
後は、コンパイルに gcc ではなく g++ を使えばそのまま行けます。
LANG:console $ g++ -c -g -I'C:\Modeltech_xe_starter\include' hello.cpp -o hello.obj $ g++ -shared -o cimports.dll hello.obj cexports.obj -L'C:\Modeltech_xe_starter\win32xoem' -lmtipli $ vsim -c -sv_lib cimports hello_top -do "run -all;quit -f" Reading C:/Modeltech_xe_starter/tcl/vsim/pref.tcl # 6.4b # vsim -do {run -all;quit -f} -c -sv_lib cimports hello_top # Loading sv_std.std # Loading work.hello_top # Loading .\cimports.dll # run -all # Hello from c_task() in cpp # Hello from verilog_task() # quit -f
実用的な環境を構築†
上のような手順を毎回手作業で行うのは手間なので、 テストベンチ起動用の Makefile を作ります。
Makefile
# デフォルトターゲット default: simulate # コマンド (PHONY ターゲット) .PHONY : cleanup .PHONY : rebuild # 設定 V_INCS = V_SRC_TOP = hello.v V_SRCS = $(V_SRC_TOP) C_HDRS = C_OBJS = hello.o TOP_MODULE = hello_top VLOG_OPTIONS = VSIM_LIBS = -L xilinxcorelib_ver -L unisims_ver -L unimacro_ver -lib work VSIM_OPTIONS = -do "run -all;quit -f" MTI_HOME = /Modeltech_xe_starter # 個別の依存関係を記述 hello.o: hello.c dpiheader.h # 共有の依存関係 .SUFFIXES: .o .c .cpp .obj work: $(V_SRCS) $(V_INCS) vlib work vlog $(VLOG_OPTIONS) -incr -sv $(V_SRCS) dpiheader.h: work $(V_SRC_TOP) $(V_INCS) vlog $(VLOG_OPTIONS) -sv -dpiheader dpiheader.h $(V_SRC_TOP) cexports.obj: dpiheader.h vsim -c $(VSIM_LIBS) -lib work $(TOP_MODULE) -dpiexportobj cexports .c.o: gcc -c -g -I$(MTI_HOME)/include $< -o $@ .cpp.o: g++ -c -g -I$(MTI_HOME)/include $< -o $@ cimports.dll: cexports.obj $(C_OBJS) g++ -shared $(C_OBJS) $(wildcard cexports.obj) -o $@ -L$(MTI_HOME)/win32xoem -lmtipli simulate: cimports.dll vsim -t 1ps $(VSIM_LIBS) $(TOP_MODULE) -sv_lib $< $(VSIM_OPTIONS) cleanup: -rm -rf work *.o cimports.dll cexports.obj dpiheader.h rebuild: cleanup simulate
先の例だとこれでうまく行きますが、実際に ISE 上で開発中のコードをデバッグするためには 始めの方の変数設定にかなり手を入れなければなりません。
このとき参考になるのが、ISE の作る .fdo ファイルです。
.fdo ファイルを参考に、vsim に与えるライブラリや -do オプションを記述します。
V_INCS = global.inc utils.inc V_SRC_TOP = mymodule_test.v V_SRCS = $(V_SRC_TOP) mymodule.v mymodule_sub1.v mymodule_sub2.v C_HDRS = mycpp_main.h mycpp_sub1.h mycpp_sub2.h global.h C_OBJS = mycpp_main.o mycpp_sub1.o mycpp_sub2.o TOP_MODULE = mymodule_test glbl VSIM_LIBS = -L xilinxcorelib_ver -L unisims_ver -L unimacro_ver VSIM_OPTIONS = -do "do {mymodule_test_wave.fdo};view wave; view structure; view signals; run 100us;do {mymodule_test.udo};" ... # 個々の依存関係を記述 mycpp_main.o: mycpp_main.cpp mycpp_main.h mycpp_sub1.h mycpp_sub2.h global.h dpiheader.h mycpp_sub1.o: mycpp_sub1.cpp mycpp_sub1.h global.h mycpp_sub2.o: mycpp_sub2.cpp mycpp_sub2.h global.h
こんな感じになると思います。
以下、少々解説
必要なファイルだけをコンパイル†
上記 Makefile で vlog への引数に -incr が無いと .v ファイルが多いときに毎回全てをコンパイルするため非常に時間が掛かってしまいます。
ModelSim のマニュアルを見ると、vlog を呼び出す際に -incr というオプションを付けておけば、 必要なファイルのみがコンパイルされるようですので、このオプションを加えてあります。
-incr と -dpiheader を同時に使うのは NG†
上記 Makefile では work を作るのに vlog を -incr 付きで呼び出して、
dpiheader.h を作るのにもう一度 vlog を呼び出しています。
これらを1つにまとめて、
vlog -incr -sv -dpiheader dpiheader.h $(V_SRCS)
とすると、初回はうまく行ったように見えるものの、 2度目から dpiheader.h の中身が空になってしまいます。
どうやら、-incr でスキップされた Verilog ソース中で定義される import / export は dpiheader.h に出力されないようなのです。
-dpiheader 付きで vlog を呼ぶときには -incr を付けてはいけない、 と覚えておく必要があります。
verilog から export された task / function が1つも無いとき†
cimports.dll を作るための以下の記述を、
cimports.dll: cexports.obj $(C_OBJS) g++ -shared $(C_OBJS) $(wildcard cexports.obj) -o $@ -L$(MTI_HOME)/win32xoem -lmtipli
$(wildcard ... ) を使わずに、
g++ -shared $(C_OBJS) cexports.obj -o $@ -L$(MTI_HOME)/win32xoem -lmtipli
と書いていると、Verilog から export された task / function が1つも無いときにおかしな動作をします。
これは、export された task / function が無いとき、vsim は cexports.obj を作らないためです。
そのため、
- cexports.obj が無ければエラーで止まります。
- 古い cexports.obj があればそれが使われます。
$(wildcard ... ) を使うことで、cexports.obj が無いときにはコマンド中に cexports.obj が現れないようになります。
同時に、
cexports.obj: dpiheader.h rm -f cexports.obj vsim -c $(VSIM_LIBS) -lib work $(TOP_MODULE) -dpiexportobj cexports
とすることで、export が無いときに古い cexports.obj が残らないようになっています。
disable への対応†
cpp 側でシミュレーション時間を進める処理を行う場合、 disable コマンドに対応しなければなりません。
呼び出し元のスレッドが disable されたかどうかは int svIsDisabledState() コマンドで判断します。
これが 1 を返したら disable されているので、void svAckDisabledState() を呼んだ後、すぐに return しなければなりません。
このとき、返り値は 0 ではなく 1 とします。
hello.v
LANG:verilog `timescale 1ns / 1ps // トップモジュール module hello_top; // #10 だけ進めるタスク task verilog_task(input int i, output int o); begin #10; $display("proceed #10 - %d", i); end endtask export "DPI-C" task verilog_task; // C の関数をインポート import "DPI-C" context task c_task(input int i, output int o); int ret; initial begin:main c_task(1, ret); // verilog_task を 10 回呼び出すはずだが end // #35 つまり3回呼び出し後に disable する initial #35 disable main; endmodule
hello.cpp
LANG:c++ #include "svdpi.h" #include "dpiheader.h" #include <iostream> DPI_LINK_DECL int c_task(int i, int *o) { std::cout << "c_task(" << i << ") called" << std::endl; // 10 回呼び出すはずが、途中で disable される for (int j=0; j<10; j++) { if (svIsDisabledState()) { std::cout << "disabled" << std::endl; svAckDisabledState(); return 1; } verilog_task(i, o); } std::cout << "done - " << i << std::endl; return 0; }
結果:
LANG:console $ vsim ... # run -all # c_task() called # proceed #10 - 1 # proceed #10 - 1 # proceed #10 - 1 # disabled # quit -f
ちゃんと3回目終了後に disable されました。
マルチスレッド対応について†
シミュレーションのタイミングを考えると、 cpp コードの関数は複数スレッドから同時に起動される可能性があるんでしょうか。
もしそうだとすると、C 側で使うグローバル変数等に mutex 等を使った同期処理が必要になりそうですが・・・
上記コードの c_task() 呼び出し部分を、次のように変更してみました。
// C で書かれた関数を呼び出す int ret; initial fork:main c_task(1, ret); c_task(2, ret); c_task(3, ret); c_task(4, ret); c_task(5, ret); c_task(6, ret); c_task(7, ret); c_task(8, ret); c_task(9, ret); c_task(10, ret); c_task(11, ret); c_task(12, ret); c_task(13, ret); c_task(14, ret); c_task(15, ret); c_task(16, ret); c_task(17, ret); c_task(18, ret); c_task(19, ret); c_task(20, ret); join
すると結果は、
# run -all # c_task(1) called # c_task(2) called # c_task(3) called # c_task(4) called # c_task(5) called # c_task(6) called # c_task(7) called # c_task(8) called # c_task(9) called # c_task(10) called # c_task(11) called # c_task(12) called # c_task(13) called # c_task(14) called # c_task(15) called # c_task(16) called # c_task(17) called # c_task(18) called # c_task(19) called # c_task(20) called # proceed #10 - 20 # proceed #10 - 1 # proceed #10 - 2 # proceed #10 - 3 # proceed #10 - 4 # proceed #10 - 5 # proceed #10 - 6 # proceed #10 - 7 # proceed #10 - 8 # proceed #10 - 9 # proceed #10 - 10 # proceed #10 - 11 # proceed #10 - 12 # proceed #10 - 13 # proceed #10 - 14 # proceed #10 - 15 # proceed #10 - 16 # proceed #10 - 17 # proceed #10 - 18 # proceed #10 - 19 # proceed #10 - 20 # proceed #10 - 1 # proceed #10 - 2 # proceed #10 - 3 # proceed #10 - 4 # proceed #10 - 5 # proceed #10 - 6 # proceed #10 - 7 # proceed #10 - 8 # proceed #10 - 9 # proceed #10 - 10 # proceed #10 - 11 # proceed #10 - 12 # proceed #10 - 13 # proceed #10 - 14 # proceed #10 - 15 # proceed #10 - 16 # proceed #10 - 17 # proceed #10 - 18 # proceed #10 - 19 # proceed #10 - 20 # proceed #10 - 1 # proceed #10 - 2 # proceed #10 - 3 # proceed #10 - 4 # proceed #10 - 5 # proceed #10 - 6 # proceed #10 - 7 # proceed #10 - 8 # proceed #10 - 9 # proceed #10 - 10 # proceed #10 - 11 # proceed #10 - 12 # proceed #10 - 13 # proceed #10 - 14 # proceed #10 - 15 # proceed #10 - 16 # proceed #10 - 17 # proceed #10 - 18 # proceed #10 - 19 # disabled - 20 # disabled - 19 # disabled - 18 # disabled - 17 # disabled - 16 # disabled - 15 # disabled - 14 # disabled - 13 # disabled - 12 # disabled - 11 # disabled - 10 # disabled - 9 # disabled - 8 # disabled - 7 # disabled - 6 # disabled - 5 # disabled - 4 # disabled - 3 # disabled - 2 # disabled - 1 # quit -f
どうやら個々のスレッドは本当に並列に動いているわけでは無いようなので、 同期処理など、複雑なことを考える必要は無いみたい・・・ですかね。
ModelSim 起動中のリコンパイル†
http://marsee101.blog19.fc2.com/blog-entry-1392.html で触れられているように、 上記の方法で cimports.dll をリンクして ModelSim を立ち上げると cimports.dll が使用中になるため、シミュレータを落とさない限り cimports.dll のコンパイルができません。
これを回避するには、quit -sim コマンドで一旦シミュレータを落としてから dll をコンパイルして、再度シミュレータを起動すればいいそうです。
1点注意として、ModelSim から exec で Makefile を呼び出し、 その Makefile から vsim が起動されると、 「2重起動できない」というエラーになってしまいます。
そこで、Verilog 関連の更新を ModelSim 上で行ってから、 Makefile を呼び出す形にしました。 Verilog 関連が最新の状態になっていれば Makefile から vsim が呼び出されることはないのでうまくいきます。
Makefile の後半部分を以下のようにします。
simulate.do: Makefile echo "vsim $(VSIM_OPTIONS) -t 1ps $(VSIM_LIBS) $(TOP_MODULE) -sv_lib cimports" > $@ echo "do {mymodule_test_wave.fdo}" >> $@ echo "view wave" >> $@ echo "view structure" >> $@ echo "view signals" >> $@ echo "run 10us" >> $@ echo "do {mymodule_test.udo}" >> $@ recompile.do: Makefile echo "quit -sim" > $@ echo "vlog $(VLOG_OPPTIONS) -incr -sv $(V_SRCS)" >> $@ echo "vlog $(VLOG_OPPTIONS) -sv -dpiheader dpiheader.h trimac_test.v" >> $@ echo "vsim $(VSIM_OPTIONS) -c $(VSIM_LIBS) -lib work $(TOP_MODULE) -dpiexportobj cexports" >> $@ echo 'exec c:/MinGW/bin/mingw32-make.exe compile' >> $@ echo "do simulate.do" >> $@ compile: cimports.dll simulate.do recompile.do simulate: compile vsim -do "do simulate.do" & console: compile vsim -do "do simulate.do" -c cleanup: -rm -rf work *.o cimports.dll cexports.obj dpiheader.h simulate.do recompile.do rebuild: cleanup simulate
ここで、VSIM_OPTIONS には -do は必要なくなります。 代わりにワーニング回避のためのオプションなどを指定することができます。
上記変更により、シミュレーション前に simulate.do と recompile.do というファイルができます。
ModelSim のコマンドラインから do recompile.do とすれば、 C ソース側で変更された部分のみ反映して、再度シミュレータを起動できます。
こういったコマンドを ModelSim のメニューかボタンに割り付けられると操作が 楽になりそうなのですが、残念ながら ModelSim は tk の menu コマンドなどを 受け付けてくれないみたいですね。
コマンドの alias†
メニューに割り当てる方法は分かりませんでしたが、 長いコマンドに短い名前を割り当てる alias が便利に 使えることが分かりました。
alias re do recompile.do
としておくことで、re [Enter] とタイプするだけでリコンパイル用のスクリプトを呼び出せます。
他にも例えば、
alias re do recompile.do # リコンパイル alias rs do { restart -force; run 100us } # リスタート(wave を追加した後などに) alias g do run 100us # 100 us 進める alias sv do write format wave C:/working_dir/mymodule_test_wave.fdo # wave 設定を保存する
などとしておくと、シミュレーションが手軽に行えます。
simulate.do: Makefile echo "vsim -t 1ps $(VSIM_LIBS) $(TOP_MODULE) -sv_lib cimports $(VSIM_OPTIONS)" > $@ echo "do {mymodule_test_wave.fdo}" >> $@ echo "view wave" >> $@ echo "view structure" >> $@ echo "view signals" >> $@ echo "run 10us" >> $@ echo "do {mymodule_test.udo}" >> $@ echo "alias re do recompile.do" >> $@ echo "alias sv write format wave C:/working_dir/mymodule_test_wave.fdo" >> $@ echo "alias g run 300us" >> $@ echo "alias rs { restart -force; run 400us }" >> $@
のようにして simulate.do に入れておけば、いつでも使うことができます。 もちろん .udo に入れておいても良いです。
Warning: (vsim-WLF-5000) WLF file currently in use: vsim.wlf†
上記 simulate.do や recompile.do を実行したところ、 下記のような警告が出て、古い wlf ファイルがどんどん溜まっていくという 現象に遭遇しました。
LANG:console # ** Warning: (vsim-WLF-5000) WLF file currently in use: vsim.wlf # File in use by: osamu Hostname: XXXXXXXX ProcessID: 7100 # Attempting to use alternate WLF file "./wlftcvr91n". # ** Warning: (vsim-WLF-5001) Could not open WLF file: vsim.wlf # Using alternate file: ./wlftcvr91n # ** Warning: (vsim-WLF-5001) Could not open WLF file: vsim.wlf # Using alternate file: ./wlftcvr91n
http://www.edaboard.com/ftopic40567.html によると、 ライセンスファイルの問題が疑われるとのことでした。
そこで自身の環境を調べたところ、 環境変数 LM_LICENSE_FILE が以前使っていた古い ModelSim 用のライセンスファイルを指していました。
これを正しい値に直し、古い vsim.wlf を削除したら問題が 解消された様子です。