7. VPI を使う (Version 2.16以降、開発中)
VPIとは、Verilog Procedural Interfaceで、第3世代のPLIです。CやC++等の外部言語ルーチンを使う場合のインターフェースを定義したものです。VPIのライブラリは、膨大で,とてもその全てをサポートすることはできません。Veritakでは、よく使うであろうサブセットのみを実装し提供します。
7.1シミュレータとDLL(Dynamic Link Library)の関係
DLLは、Delay Locked Loopではありません。動的なライブラリ(ルーチン集)です。ユーザルーチンは、DLLで作成します。
Verilog シミュレータが主で、DLLは従の関係になります。DLLは、Verilogシミュレータのプロセスと同じ空間に動的にマップされます。外部アプリケーションと、シミュレータを通信させたい場合は、DLLを介して通信を行います。この場合、プロセス同期の問題があり、パイプや、共有ファイル等のOSに備わっている高度なテクニックを使いながら、プロセス同期をDLL内で行うことになります。
このチュートリアルでは、シミュレータとDLLだけで完結する簡単な例をとりあげながら、VeritakにDLLを組み込む方法を説明します。
7.2 VPIでできること
サンプルで見ていきましょう。DLLまで組み込ん済みのプロジェクトがsamples\vpi
にあります。
最初のサンプルは、first_callback.vtakprj です。走らせてみてください。
7.2.1 first_callback.vtakprj
このソースのmodule には、中身がありません。にもかかわらず、コンソールにメッセージが出ていますね。
これは、あらかじめ登録されていた関数を呼び出すことで実現しています。これをコールバック(callback)
といいます。呼び出された関数は、次のソースです。(なお以降のソースは、samples\vpi\vpi_source\下にあるmath_vpi.cppから抜き出したものです。)
static int sys_end_of_simulation(p_cb_data cb_data) { vpi_printf("Simulation Finished. Reported by vpi. Good Bye.. \n\n"); return 0; } static int sys_start_of_simulation(p_cb_data cb_data) { vpi_printf("Simulation Starts. Reported by vpi. Thanks a lot..\n\n"); return 0; }
ところで、これを、いつ呼び出されるのかを決めないといけません。そのための登録が必要になります。
。これは、
void sys_math_vpi_register()//VPI call/callback register routine
という関数中の、
// cbStartOfSimulation cb_data.reason = cbStartOfSimulation; cb_data.cb_rtn = sys_start_of_simulation; cb_data.user_data = 0; vpi_register_cb(&cb_data);//register callback // cbEndOfSimulation cb_data.reason = cbEndOfSimulation; cb_data.cb_rtn = sys_end_of_simulation; cb_data.user_data = 0; vpi_register_cb(&cb_data);//register callback
で登録しています。vpi_register_cb というのが、登録の関数です。また、cb_rtnで、飛び先を登録しています。 では、いつ飛ぶのか?を決めているのが、reason
のところで、それぞれ、
です。これは、vpi_user.h で決められている定数です。(大抵のシミューレータベンダーは、Verilog
2001 の規格書に載っている値をそのまま使っていると思います。) その名の通り、それぞれ、シミュレーション開始と終わりに呼び出す、という意味になります。
7.2.2 hello.vtakprj
次は、定番のhelloです。今度は、ソース中から関数を呼んでいることが想像つくと思います。
呼び出された関数は、
static int sys_hello(char* name) { vpi_printf("Hello! Verilog VPI world.\n"); return 0; }
です。登録は、次のように、vpi_register_systf で行います。calltfが飛び先になっていることが分かります。
tf_data.type = vpiSysTask;//Task Jun.15.2006 tf_data.tfname = "$Hello"; tf_data.user_data = "$Hello"; tf_data.calltf = sys_hello;//call address tf_data.compiletf = 0; vpi_register_systf(&tf_data);
簡単ですね。VPIの登録方法は、2種類しかありません。つまり、
です。vpi_register_systf で登録される関数は、verilog ソース中のtaskや、functionと同じ種類のもので分かりやすいと思います。
(systfのtfは、taskやfunctionという意味だと思います。) Verilogでは、書けない、または、書けない事はないが、他の言語で書いた方が速いとか、何らかの処理をまとめて置き換えるものではないかと思います。
これに対して、vpi_register_cb は、コールバックで、少し複雑ですので、さらに例で見ていきましょう。
7.2.3 callback_test.vtakprj
このプロジェクトでは、波形にご注目ください。
このソースは、
です。
clock ,reset,load,data_load の信号は動いていますが、記述中には見当たりません。
これは、テストベンチの駆動がVPI側で行われているからです。しかし、$run_test_port
は、通常のtask callに近いもののはずです。ですから、呼び出した後でも波形が動くためには、このファンクション処理以外にVPI側でなにかしていないと説明ができません。
どうやっているか興味がありますね。
vpi_register_systfで登録されたrun_test_port 関数に飛んで来たとします。
最初の障害は、引数の受け取り方です。ちょっと特殊なので、次の3行を定型として覚えてしまいます。
全て、引数はvpiHandleという型で受け取りますが、ハンドルとは、なにか分かりづらいと思います。Veritakでのハンドルの実体は、アドレスです。(C言語ならvoid*) です。
systfref = vpi_handle(vpiSysTfCall, NULL); /* シミュレータさん。今、呼び出した人のアドレスをください。get system function that invoked C routine */ argsiter = vpi_iterate(vpiArgument, systfref);/*呼び出した人さんへ、引数のイタレータ(STLのVectorだと思ってもよいでしょう。一種のリストです)を作成してください。 get iterator (list) of passed arguments */ argh = vpi_scan(argsiter);/*イタレータさん、引数を一つください。 get the one argument - add loop for more args */
ここで、最初の引数は、argh に入っています。次の引数を受け取りたいときは、もう一度、
argl = vpi_scan(argsiter);
とすれば、次の引数がargl に入ります。同様にして、NULLが返ってくるまで任意個数の引数を受け取れます。
Tips:
専門的になりますが、argsiter のメモリの開放は、シミュレータが持っている引数とscanの個数が一致していれば、シミュレータ側でやりますので、開放しなくてもよいです。しかし、引数個数分のscanをしないで、次回、処理をするとイタレータのメモリは開放されず、メモリリークとなり長時間のシミュレーションでは、問題が生じます。そこで、サンプルみたいに、最後がNULLで終わっていなけば、vpi_free_objectで開放する、と決めておいた方がよいかもしれません。なお、NULLが返ったあとは、もうオブジェクトは破棄されているので、アクセスはできません。NULLの後にscanするのは、illegal
です。
下のソースでは、引数の変数がreg タイプであることを確認し、それぞれの信号に対して処理内容を分けています。
引数に値を設定してやるには?
最初のclock の場合は、1ビットであることは、明白なので、1ビット専用のscalar
で戻してやります。
値の設定の仕方は、次の3行が定石です。
value.value.scalar=vpi0;//値を設定 value.format=vpiScalarVal;//タイプを設定 vpi_put_value(argh, &value, NULL, vpiNoDelay);//書く。つまり clock=0;
これが、整数の場合は、
value.value.integer=0;//値を設定(ZX はなし) value.format=vpiIntVal;//タイプを設定 vpi_put_value(argh, &value, NULL, vpiNoDelay);//書く。つまり clock=0;
にすればよいです。integer は、32ビットまでなので、それより大きいビット数の場合は、
最初にビット幅を求め、32ビット単位で設定してやります。
unsigned wwid = vpi_get(vpiSize, argh); value.format=vpiVectorVal; unsigned words=(wwid+31)/32; value.value.vector =(t_vpi_vecval*) calloc(words, sizeof(s_vpi_vecval));// for (unsigned i=0;i<words;i++){ value.value.vector[i].aval=0xabcdffff;//Initialize abcdffff as initial counter value value.value.vector[i].bval=0; } vpi_put_value(argh, &value, NULL, vpiNoDelay);//write load_data=xx; free(value.value.vector);//free vector
で、メモリを確保して設定してやります。なお、構造体の定義は、vpi_user.h を参照してください。
さて、本題のcallback ですが、上の波形では、load,reset,clock が変化していますので、それぞれ、cbAfterDelayで、Delay時間後のコールバックを予約してやります。なお、時刻0の設定は上で終わっています。
static int run_test_port(char* name) { vpiHandle systfref, argsiter, argh; s_vpi_value value; systfref = vpi_handle(vpiSysTfCall, NULL); /* get system function that invoked C routine */ argsiter = vpi_iterate(vpiArgument, systfref);/* get iterator (list) of passed arguments */ vpiHandle clock_handle=0; vpiHandle load_handle=0; vpiHandle reset_handle=0; for (unsigned i=0;i<4;i++){ argh = vpi_scan(argsiter);/* get the one argument - add loop for more args */ if(!argh){ vpi_printf("$VPI run_test_port: missing parameter. \n"); vpi_sim_control(vpiFinish, 1);//パラメータがない、プログラムエラーで終了 return 0; } string str=vpi_get_str(vpiName,argh);//引数の名前を受け取る if(vpi_get(vpiType, argh) !=vpiReg){//variable type の変数でないと駄目 vpi_printf("$VPI Error:run_test_port requires variable type of parameter.\n"); vpi_sim_control(vpiFinish, 1); return 0; } if (!str.compare("clock")) {//クロックだったら、 value.value.scalar=vpi0; value.format=vpiScalarVal; vpi_put_value(argh, &value, NULL, vpiNoDelay);//write clock=0; clock_handle=argh;//for save }else if (!str.compare("reset")){//リセットだったら value.value.scalar=vpi1; value.format=vpiScalarVal; vpi_put_value(argh, &value, NULL, vpiNoDelay);//write reset=1; reset_handle=argh; }else if (!str.compare("load")){//load だったら value.value.scalar=vpi1; value.format=vpiScalarVal; vpi_put_value(argh, &value, NULL, vpiNoDelay);//write load=0; load_handle=argh; }else if(!str.compare("load_data")){//load_data だったら unsigned wwid = vpi_get(vpiSize, argh); value.format=vpiVectorVal; unsigned words=(wwid+31)/32; value.value.vector =(t_vpi_vecval*) calloc(words, sizeof(s_vpi_vecval));// for (unsigned i=0;i<words;i++){ value.value.vector[i].aval=0xabcdffff;//Initialize abcdffff as initial counter value value.value.vector[i].bval=0; } vpi_put_value(argh, &value, NULL, vpiNoDelay);//write load_data=xx; free(value.value.vector);//free vector } } if (!load_handle || !reset_handle || !clock_handle) { //パラメータが全て揃ったことを確認する vpi_printf("$VPI Error:run_test_port some parameter is missing.\n"); vpi_sim_control(vpiFinish, 1); return 0; } if(argh) vpi_free_object(argsiter);//メモリを開放 //Reserve next clock cycle struct t_vpi_time time; time.type = vpiSimTime; time.high = 0; time.low = CLOCK_HALF_CYCLE; struct t_cb_data cb; cb.reason = cbAfterDelay;//ディレイ時間後にコールバックを予約 cb.cb_rtn = clock_iterator;//next callback address cb.time = &time; cb.obj = clock_handle; cb.value = 0; cb.user_data = 0; vpi_register_cb(&cb); //Reserve reset=0 after CLOCK_HALF_CYCLEx5 time.type = vpiSimTime; time.high = 0; time.low = CLOCK_HALF_CYCLE*6; cb.reason = cbAfterDelay;//ディレイ時間後にコールバックを予約 cb.cb_rtn = load_iterator;//next callback address cb.time = &time; cb.obj = reset_handle; cb.value = 0; cb.user_data = 0; vpi_register_cb(&cb); //Reserve load=0 after CLOCK_HALF_CYCLEx11 time.type = vpiSimTime; time.high = 0; time.low = CLOCK_HALF_CYCLE*12; cb.reason = cbAfterDelay;//ディレイ時間後にコールバックを予約 cb.cb_rtn = load_iterator;//next callback address cb.time = &time; cb.obj = load_handle; cb.value = 0; cb.user_data = 0; vpi_register_cb(&cb); return(0); }
クロックのコールバックです。クロックは、always #50 clock=~clock; の動作が必要なので、
コールバックされたら、値を反転し、再度、自分自身を予約してやればよいです。
load とresetについては、もう値の変化は必要ないので、予約は不要です。
値を得るには
vpi_put_value は、ライトファンクションですが、vpi_get_valueは、リードです。
s_vpi_value value; value.format = vpiScalarVal; vpi_get_value(new_cb.obj, &value);
以上で、テストベクタをプログラムで書くことができました。 verilog HDL なら、1行もかからない文が、VPIで書くと面倒ですね。ただ、外部から動的に駆動制御される場合は、どうしてもこの機構がないとVerilog HDLと外部とのインターフェースができません。 面倒な手続きや、複雑に見える構造体も、よく考えられていてこれ以上簡単にするのは、難しいと思います。
このVPIというのは、Verilog 2001になってからのPLIですが、前のVersionのPLIに比べてファンクションの機能の抽象化が図られていて、iterateや、scan、get*、put*などの一般化したファンクションで呼んで、数百種類あるPLIのファンクションの数を少なくしようという意図が感じられます。なお、前のVersionのPLIは、保守扱いになっているので、これから、外部プログラムを書かれる場合は、VPIをお勧めします。VPIは、前のVersionのtf_xxや、acc_xxのsupersetになっています。
#define CLOCK_HALF_CYCLE 50 static int clock_iterator(p_cb_data cb) { //Reserve next clock cycle struct t_vpi_time time; time.type = vpiSimTime; time.high = 0; time.low = CLOCK_HALF_CYCLE; struct t_cb_data new_cb; new_cb.reason = cbAfterDelay; new_cb.cb_rtn = clock_iterator; new_cb.time = &time; new_cb.obj = cb->obj; new_cb.value=0; new_cb.user_data = 0; vpi_register_cb(&new_cb); s_vpi_value value; value.format = vpiScalarVal; vpi_get_value(new_cb.obj, &value); value.value.scalar ^=1;// vpi_put_value(cb->obj, &value, NULL, vpiNoDelay);//write inverted value clock=~clock; return 0; } static int load_iterator(p_cb_data cb) { s_vpi_value value; value.format = vpiScalarVal; value.value.scalar =vpi0; vpi_put_value(cb->obj, &value, NULL, vpiNoDelay);//write inverted value clock=~clock; return 0; }
7.2.4 cbReadOnlySynch_sample.vtakprj
次のプロジェクトもコールバックに関するものです。
まずは、$cbReadOnly_sample のソースを見てみます。
今度のポイントは、cbReadOnlySynch です。これは、タイムスロットの内、もう変化は起こらない、次のタイムスロットに行く直前にコールバックされます。これでなにか便利かというと、その時刻の最終値をモニタできる、ことです。$strobeというのがありますが、実は、このcbReadOnlySynch を使って書けます。
static int next_time_cbReadOnlySynch_sample(char*name) { vpiHandle systfref, argsiter, argh; s_vpi_value value; systfref = vpi_handle(vpiSysTfCall, NULL); /* get system function that invoked C routine */ argsiter = vpi_iterate(vpiArgument, systfref);/* get iterator (list) of passed arguments */ argh = vpi_scan(argsiter);/* get the one argument - add loop for more args */ if(argh){ struct t_cb_data cb; s_vpi_time time; time.type = vpiSimTime; time.high = 0; time.low = 0; cb.reason = cbReadOnlySynch; cb.cb_rtn = callback_cbNextSimTime;//next callback address cb.time = &time; cb.obj = argh; cb.value = 0; cb.user_data = 0; vpi_register_cb(&cb); } return 0; }
次のコールバックアドレスでは、cbNextSimTime で自分自身をコールバックしています。これは、次の時刻のタイムスロットへのコールバックを意味します。こうして、時刻が変化したときのカウンタの最終値をモニタできる、という例になっています。
static int callback_cbNextSimTime(p_cb_data cb) { s_vpi_time now; s_vpi_value value; value.format = vpiIntVal; vpi_get_value(cb->obj, &value); now.type = vpiSimTime; vpi_get_time(0, &now); vpi_printf("callback_cbNextSimTime time=%d val=%d\n",now.low,value.value.integer); struct t_cb_data new_cb; new_cb.reason = cbNextSimTime; new_cb.cb_rtn = callback_cbNextSimTime;//next callback address new_cb.time = 0; new_cb.obj = cb->obj; new_cb.value = 0; new_cb.user_data = 0; vpi_register_cb(&new_cb); struct t_cb_data new1_cb; new1_cb.reason = cbReadOnlySynch; new1_cb.cb_rtn = callback_cbReadOnlySynch;//next callback address s_vpi_time now1; now1.type = vpiSuppressTime; now1.low=0; now1.high=0; new1_cb.time = &now1;//s_vpi_time definition is must. new1_cb.obj = cb->obj; new1_cb.value = 0; new1_cb.user_data = 0; vpi_register_cb(&new1_cb); return 0; }
7.2.5 print_nets
次の例は、解析的にVerilog HDLのソースの構造を出力する例です。
注意点として、データを受け取る場合ファンクションの場合(vpi_get_str,vpi_get_value等) シミュレータ側で、メモリは確保されますが、それは、次回も使いまわしされるので、受け取ったらすぐにコピーして使うようにします。
static void print_net_in_module(vpiHandle module_handle) { char* module_name = vpi_get_str(vpiName, module_handle); vpi_printf("module %s has :\n",module_name); //vpiNet vpiHandle net_iterator=vpi_iterate(vpiNet,module_handle); vpi_printf(" vpiNet:\n"); if(net_iterator){ while( vpiHandle netHandle = vpi_scan(net_iterator)){ string NetFullName = vpi_get_str(vpiFullName, netHandle);//prepare another string buffer for another vpi operation string NetName=vpi_get_str(vpiName,netHandle);//prepare another string buffer for another vpi operation vpi_printf(" %s %s\n",NetFullName.c_str(),NetName.c_str()); //Check by vpi_handle_by_name function vpiHandle net= vpi_handle_by_name(const_cast<char*>(NetFullName.c_str()),(vpiHandle) NULL); string NetFullName2=vpi_get_str(vpiFullName,net); if (NetFullName.compare(NetFullName2)){ assert(0); } } }else { vpi_printf(" No nets.\n"); } {//vpiReg vpiHandle net_iterator=vpi_iterate(vpiReg,module_handle); vpi_printf(" vpiReg:\n"); if(net_iterator){ while( vpiHandle netHandle = vpi_scan(net_iterator)){ string NetFullName = vpi_get_str(vpiFullName, netHandle); string NetName=vpi_get_str(vpiName,netHandle); vpi_printf(" %s %s\n",NetFullName.c_str(),NetName.c_str()); } }else { vpi_printf(" No variable.\n"); } } vpi_printf("\n"); } static int printModules(char *){ vpiHandle topModIterator; vpiHandle topModHandle; /* create a module iterator that starts at the top as indicated by NULL */ topModIterator = vpi_iterate(vpiModule,NULL); if( !topModIterator ){ return 0; } /* use vpi_scan to iterate throught modules */ while( topModHandle = vpi_scan(topModIterator)){ print_net_in_module(topModHandle); vpiHandle module_iterator=vpi_iterate(vpiModule,topModHandle); if (module_iterator){ vpiHandle module_handle; while (module_handle=vpi_scan(module_iterator)){ print_net_in_module(module_handle); } } } return 0; }
7.2.6 my_findwindow
次は、ちょっとお遊びです。LEDチカチカと7セグメントLEDの駆動ををシミュレータ上で行うことができます。
VPIの使い道
たとえば、$GetTimeofDay のようにちょっとした小物ファンクションでも、ある場合には便利です。テスト開始、終了日時をファイルに書き込んだり、出来上がったテストファイルをシミュレーションが終わったら、自動的に転送したり、とVPIを使えば、それこそ無限の使い方ができるようになります。 Verilog
2001 のVPIは、System Verilogになってもそのまま使えますし、勿論ベンダーを選びません。Veritak
でやっているようにリニアシステムシミュレータを組み込むこともできます。
ソースです。
//32bit 専用 static int sys_systems_size_tf(PLI_BYTE8*)//Aug.42003 { return 32; } static int sys_system_calltf(char* name) { vpiHandle systfref, argsiter, argh; t_vpi_value value; systfref = vpi_handle(vpiSysTfCall, NULL); /* get system function that invoked C routine */ argsiter = vpi_iterate(vpiArgument, systfref);/* get iterator (list) of passed arguments */ argh = vpi_scan(argsiter);/* get the one argument - add loop for more args */ if(!argh){ vpi_printf("$VPI missing parameter.\n"); return 0; } value.format = vpiStringVal; vpi_get_value(argh, &value); char *s = value.value.str; int result=system(s);//Do command on DOS! value.value.integer =result; value.format = vpiIntVal;/* return the result */ vpi_put_value(systfref, &value, NULL, vpiNoDelay); vpi_free_object(argsiter); return(0); } static int sys_shell_execute_calltf(char* name) { vpiHandle systfref, argsiter, argh; t_vpi_value value; systfref = vpi_handle(vpiSysTfCall, NULL); /* get system function that invoked C routine */ argsiter = vpi_iterate(vpiArgument, systfref);/* get iterator (list) of passed arguments */ argh = vpi_scan(argsiter);/* get the one argument - add loop for more args */ if(!argh){ vpi_printf("$VPI missing parameter.\n"); return 0; } value.format = vpiStringVal; vpi_get_value(argh, &value); char *s = value.value.str; //Do shell command on DOS! int result=reinterpret_cast<int>( ShellExecute(NULL, "open", s, NULL, NULL, SW_SHOWNORMAL)); value.value.integer =result;//; value.format = vpiIntVal;/* return the result */ vpi_put_value(systfref, &value, NULL, vpiNoDelay); vpi_free_object(argsiter); return(0); } //Jun.11.2006 static int sys_FindWindow_calltf(char* name) { vpiHandle systfref, argsiter, argh; t_vpi_value value; systfref = vpi_handle(vpiSysTfCall, NULL); /* get system function that invoked C routine */ argsiter = vpi_iterate(vpiArgument, systfref);/* get iterator (list) of passed arguments */ argh = vpi_scan(argsiter);/* get the one argument - add loop for more args */ if(!argh){ vpi_printf("$VPI missing parameter.\n"); return 0; } value.format = vpiStringVal; vpi_get_value(argh, &value); char *s = value.value.str; while (*s==' '){//Skip space s++; } //Do shell command on DOS! int result=reinterpret_cast<int>(FindWindow(NULL,s)); value.value.integer =result;//; value.format = vpiIntVal;/* return the result */ vpi_put_value(systfref, &value, NULL, vpiNoDelay); vpi_free_object(argsiter); return(0); } int sys_get_time_of_day(char* name) { vpiHandle systfref, argsiter, argh; t_vpi_value value; systfref = vpi_handle(vpiSysTfCall, NULL); /* get system function that invoked C routine */ argsiter = vpi_iterate(vpiArgument, systfref);/* get iterator (list) of passed arguments */ argh = vpi_scan(argsiter);/* get the one argument - add loop for more args */ if(!argh){ vpi_printf("$VPI Error:sys_get_time_of_day.\n"); return 0; } if(vpi_get(vpiType, argh) !=vpiReg){ vpi_printf("$VPI Error:sys_get_time_of_day 1st parameter must be variable type.\n"); return 0; } unsigned wwid = vpi_get(vpiSize,argh); if (wwid%8!=0 || !wwid){ vpi_printf("$VPI Error:sys_get_time_of_day size of 1st parameter must be multiple of 8.\n"); return 0; } time_t timer; time(&timer); char buffer[27]; ctime_s(buffer,27,&timer);//Get Local time. use new secure version value.format =vpiStringVal;//Put by string value.value.str=buffer; vpi_put_value(argh, &value, NULL, vpiNoDelay); return(0); } //Jun.11.2006 static int sys_Sleep_calltf(char* name) { vpiHandle systfref, argsiter, argh; s_vpi_value value; systfref = vpi_handle(vpiSysTfCall, NULL); /* get system function that invoked C routine */ argsiter = vpi_iterate(vpiArgument, systfref);/* get iterator (list) of passed arguments */ argh = vpi_scan(argsiter);/* get the one argument - add loop for more args */ if(!argh){ vpi_printf("$VPI sys_Sleep_calltf: missing parameter. \n"); // vpi_sim_control(vpiFinish, 1); return 0; } value.format = vpiIntVal; vpi_get_value(argh, &value); Sleep(value.value.integer);//Wait val msec if(argh) vpi_free_object(argsiter); return(0); } //Jun.11.2006 static int sys_PostMessage(char* name) { vpiHandle systfref, argsiter, argh; s_vpi_value value; systfref = vpi_handle(vpiSysTfCall, NULL); /* get system function that invoked C routine */ argsiter = vpi_iterate(vpiArgument, systfref);/* get iterator (list) of passed arguments */ unsigned message_parameter[4]; for (unsigned i=0;i<4;i++){ argh = vpi_scan(argsiter);/* get the one argument - add loop for more args */ if(!argh){ vpi_printf("$VPI sys_PostMessage: missing parameter. \n"); // vpi_sim_control(vpiFinish, 1); return 0; } value.format = vpiIntVal; vpi_get_value(argh, &value); message_parameter[i]=value.value.integer; } ::PostMessage(reinterpret_cast<HWND>(message_parameter[0]),//handle message_parameter[1],//WM_COMMAND message_parameter[2],//WPARAM message_parameter[3]);//LPARAM if(argh) vpi_free_object(argsiter); return(0); }
7.2.7 my_cb_value
$monitor もどきを作ってみましょう。$MyCB_ChangeがVPIで作成したファンクションです。$monitor,
$strobe, $display 等ありますが、いずれも、変数aの値を表示します。Time..で始まる部分が今回作成したファンクションです。aの値が変化したときに出力しています。
・$monitor との違い
$monitor も値の変化を出力していますが、それは、そのタイムスロットの一番最後(同時刻の最終確定値)だけです。それに対して、今回作成したファンクションでは、aの値の変化に応じて出力しています。
・$strobe との違い
$strboeは、$displayと同様ですが、そのタイムスロットの一番最後(同時刻の最終確定値)を出力する点が違います。
VPIソースです。
登録部です。
tf_data.type = vpiSysTask; tf_data.tfname = "$MyCB_Change"; tf_data.calltf = my_cb_change; tf_data.compiletf = 0; tf_data.sizetf = 0; vpi_register_systf(&tf_data);
ファンクション部です。
任意個数の引数をHDLソースから受け取りcbValueChangeでコールバックを登録します。cbValueChange ではvalue や、time構造体のポインタをセットしておきます。これは、シミュレータ側でCOPYしているので、スタックオブジェクトで構いません。しかしながら、user_dataは、ユーザ側で確保してください。
static int my_cb_change(char* name)//Jul.19.2006 { vpiHandle systfref, argsiter, argh; s_vpi_value value; systfref = vpi_handle(vpiSysTfCall, NULL); /* get system function that invoked C routine */ argsiter = vpi_iterate(vpiArgument, systfref);/* get iterator (list) of passed arguments */ while (argh = vpi_scan(argsiter)){ /* get the one argument - add loop for more args */ unsigned type=vpi_get(vpiType,argh); if (type !=vpiReg && // type !=vpiNet && type !=vpiIntegerVar && type !=vpiTimeVar// ) { vpi_printf("invalid arguments in $my_monitor.%d\n",type); vpi_sim_control(vpiFinish, 1); } struct t_cb_data cb; s_vpi_time now; now.type = vpiScaledRealTime;//コールバックされた時刻は、Real型でください value.format = vpiBinStrVal;//2進文字列ででください cb.reason = cbValueChange; cb.cb_rtn = my_cb_1;//コールバックアドレス next callback address cb.time = &now; cb.obj = argh; cb.value = &value; char* net_name = vpi_get_str(vpiFullName, argh); cb.user_data = strdup(net_name);//allocate and copy vpi_register_cb(&cb); } return(0); }
コールバック部です。時刻と名前、文字列にした値を受け取り、表示します。
static int my_cb_1(p_cb_data cb) { vpi_printf("Time %5.3f: %s = %s\n", cb->time->real, cb->user_data, cb->value->value.str); return 0; }
cbValueChange で注意する点は、同時刻での変化の仕方です。同時刻最終値は同じになりますが、最終値にいたる遷移中の値は、シミュレータ毎に異なることに注意してください。これは、同一シミュレータの異なるVersion及び、シミュレータの内部オプティマイズでも変わってきます。下は、Fast ModeをOnにしたときの実行結果です。時刻最終値は、同じですが、遷移数が少なくなっています。
F:\workveritakwin\veritakwin305Aprepare\samples\vpi\my_cb_value.v(2)::my_cb_value_sample Verilogのシミュレーションの準備が完了しました。スタートは,Goボタンを押してください。 ------------- シミュレーションを開始します。-------------------- Simulation Starts. Reported by vpi. Thanks a lot.. Time 0.000: my_cb_value_sample.a = 00000000 Time 0.000: my_cb_value_sample.a = 00000001 Time 0.000: my_cb_value_sample.a = 00000011 Time 0.000: my_cb_value_sample.a = 00000101 Time 0.000: my_cb_value_sample.a = 11111111 $monitor Time= 0***** a=11111111 $strobe Time= 0******* a=11111111 Time 10.000: my_cb_value_sample.a = 00000000 $monitor Time= 10***** a=00000000 Time 20.000: my_cb_value_sample.a = 11111111 $display Time= 20 a=ff Time 20.000: my_cb_value_sample.a = 00000000 $monitor Time= 20***** a=00000000 Simulation Finished. Reported by vpi. Good Bye.. ---------- シミュレーションを終了します。time=20----------
TIPS: さて$monitorと同じ動作にしたい場合は、どうしたらよいでしょうか? それは、cbValueChange で値の変化を受けたら、 さらにcbReadOnlySynchで飛ばして、値を取得すれば、値の最終値のみを表示することができます。
7.2.8 read_test_vector
ファイルからテストベクタを読んで、VPIで値をセットしてみましょう。
実行結果とHDLソースです。7.2.3 callback_test.vtakprj と同じようなソースと結果ですが、今回は、$xxで呼んでいません。
そのままで、合成可能な純ハード記述になっています。これにどうやって値をセットしているのでしょうか?
実は、7.2.3のHDLソースにの次のソースを追加して、テストベクタをファイルに落としました。それをVPIで読んでセットしていた訳です。
ソースの追加部です。
//For Later use integer fp; initial begin fp=$fopen("read_vector.txt","w"); end always @(posedge clock,negedge clock) begin $fdisplay(fp,"%d:%b,%b,%b,%h;",$time,clock,reset,load,load_data); end
落としたファイルは、こんな感じです。
今回製作した、
登録部です。
cb_data.reason = cbStartOfSimulation; cb_data.cb_rtn = my_read_test_vector; cb_data.user_data = 0; tf_data.compiletf = 0; tf_data.sizetf = 0; vpi_register_cb(&cb_data);//register callback
本体です。
ちょっとソースは長いですが、ポイントは、vpi_put_value で、時刻情報をセットしてやることだけです。なお、時刻は、相対時刻です。(時刻0でセットする分には、=絶対時刻になります。)
int my_read_test_vector(p_cb_data cb_data)//Jul.24.2006 { vpiHandle topModIterator; vpiHandle topModHandle; s_vpi_vlog_info options_s; s_vpi_value value; bool go_this_routine=false; bool debug=false; vpi_get_vlog_info(&options_s); for (unsigned i=1; i<options_s.argc; i++) { string temp=options_s.argv[i]; if (strcmp(options_s.argv[i], "+DEBUG_VPI") == 0) { debug = true; ///flag on } if (strcmp(options_s.argv[i], "+READ_TEST_VECTOR") == 0) { go_this_routine = true; ///flag on } } if (!go_this_routine) return 0;//Stop further processing if +READ_TEST_VECTOR is not defined at command line. See "Edit Display" of the project /* create a module iterator that starts at the top as indicated by NULL */ topModIterator = vpi_iterate(vpiModule,NULL); if( !topModIterator ){ return 0; } /* use vpi_scan to iterate throught modules */ topModHandle = vpi_scan(topModIterator); if (!topModHandle) return 0; string top_module_name=vpi_get_str(vpiDefName,topModHandle); if (debug) { vpi_printf("Top module name=%s\n",top_module_name.c_str()); } string clock=top_module_name; clock+="."; clock+="clock"; vpiHandle clock_handle=vpi_handle_by_name((char*)clock.c_str(),topModHandle); if (!clock_handle){ vpi_printf("Missing =%s\n",clock.c_str()); return 0; } string load=top_module_name; load+="."; load+="load"; vpiHandle load_handle=vpi_handle_by_name((char*)load.c_str(),topModHandle); if (!load_handle){ vpi_printf("Missing =%s\n",load.c_str()); return 0; } string load_data=top_module_name; load_data +="."; load_data+="load_data"; vpiHandle load_data_handle=vpi_handle_by_name((char*)load_data.c_str(),topModHandle); if (!load_data_handle){ vpi_printf("Missing =%s\n",load_data.c_str()); return 0; } string reset=top_module_name; reset +="."; reset +="reset"; vpiHandle reset_handle=vpi_handle_by_name((char*)reset.c_str(),topModHandle); if (!reset_handle){ vpi_printf("Missing =%s\n",reset.c_str()); return 0; } string file_name="read_vector.txt"; FILE* fp=fopen(file_name.c_str(),"r"); if (!fp) { vpi_printf("Missing=%s\n",file_name.c_str()); } char buffer[1000]; string o_clock,o_reset,o_load,o_load_data; while (1) { if(fgets(buffer,1000,fp) ==NULL) break; char clock_char[4]; char reset_char[4]; char load_char[4]; char load_data_char[100]; unsigned t; sscanf(buffer,"%d:%[^','],%[^','],%[^','],%[^';']",&t,clock_char,reset_char,load_char,load_data_char);//Use delimiter if (debug) { vpi_printf("%s\n",buffer); } string clock_str=clock_char; string reset_str=reset_char; string load_str=load_char; string load_data_str=load_data_char; s_vpi_time time_s; time_s.type = vpiSimTime;//セット時刻を設定 time_s.low = t;//セット時刻 time_s.high = 0; if (clock_str.compare(o_clock)){//Only changed value makes scheduling.No changed value makes no scheduling. o_clock=clock_str; value.format = vpiBinStrVal; value.value.str =clock_char; vpi_put_value(clock_handle, &value, &time_s, vpiTransportDelay); } if (reset_str.compare(o_reset)){ o_reset=reset_str; value.format = vpiBinStrVal; value.value.str =reset_char; vpi_put_value(reset_handle, &value, &time_s, vpiTransportDelay); } if (load_str.compare(o_load)){ o_load=load_str; value.format = vpiBinStrVal; value.value.str =load_char; vpi_put_value(load_handle, &value, &time_s, vpiTransportDelay); } if (load_data_str.compare(o_load_data)){ o_load_data=load_data_str; value.format = vpiHexStrVal; value.value.str =load_data_char; vpi_put_value(load_data_handle, &value, &time_s, vpiTransportDelay); } } return(0); }
短めのテストベクタでは、この方法が簡単でよいのですが、長めのテストベクタでは、問題があります。それは、時刻0で、全ての実行予約をしてしまうために、シミュレータ内で保持するキューイングデータが大きくなりすぎることです。
TIPS:
<名前からハンドルに変換するには>
vpi_handle_by_name を使います。
<ソースをいじらないで、違うパラメータで走らせたい>
vpi_get_vlog_infoを使います。上の例では、プロジェクト編集画面でREAD_TEST_VECTOR をPlusargしています。vpi_get_vlog_info でコマンドラインに与えられた引数をVPIで受け取ることができます。
<モジュール名を知りたい>
vpiDefName を使います。 vpiFullName は、フルインスタンス名, vpiName は、インスタンス名になります。
<値をセットする>
vpi_put_value を使います。セットするハンドルは、Variable型である必要があります。
<ポート情報を得るには、>
vpiPort を使います。 使い方は、インストールフォルダのsamples\vpi\mycosim\shared_class/shared_file.cppをご参照ください。
7.2.9 read_test_vector_long
7.2.8を改良して、長いテストベクタでも問題ないようにします。
登録部です。
cb_data.reason = cbStartOfSimulation; cb_data.cb_rtn = my_read_test_vector_long; cb_data.user_data = 0; tf_data.compiletf = 0; tf_data.sizetf = 0; vpi_register_cb(&cb_data);//register callback
本体です。テストベクタの読み出し時に次の次の行の読み出しの予約と、値のセットを行っています。 キューイングされるデータは、限定的になるため、テストベクタ長に制限はなくなります。
この実行で必要になる情報は、read_test_vector_work としてまとめ、ポインタをuser_dataにセットして情報を渡しています。
//work struct struct read_test_vector_work { read_test_vector_work(){ fp=0; debug=false; previous_time.low=0; previous_time.high=0; previous_time.type=vpiSimTime; } struct handle_format{ vpiHandle handle; int format; string name; }; FILE* fp; vector<handle_format> vpi_handles; bool debug; s_vpi_time previous_time; }; int do_last_time_slot(p_cb_data cb_data) { vpi_printf("Ending.. \n"); vpi_sim_control(vpiFinish, 1); return 0; } void read_line(read_test_vector_work* work_ptr); int Read_Next_line(p_cb_data cb_data)//Jul.24.2006 { read_test_vector_work* work_ptr=(read_test_vector_work* )(cb_data->user_data); read_line(work_ptr); return 0; } void read_line(read_test_vector_work* work_ptr) { FILE* fp=work_ptr->fp; bool debug=work_ptr->debug; s_vpi_value value; s_cb_data data_s; char buffer[1000]; if(fgets(buffer,1000,fp) ==NULL) { data_s.reason = cbReadOnlySynch; data_s.cb_rtn = do_last_time_slot; data_s.obj = 0; data_s.time = 0; data_s.value = 0; data_s.user_data = 0; vpi_register_cb(&data_s); return ; } char clock_char[4]; char reset_char[4]; char load_char[4]; char load_data_char[100]; unsigned t; sscanf(buffer,"%d:%[^','],%[^','],%[^','],%[^';']",&t,clock_char,reset_char,load_char,load_data_char);//Use delimiter if (debug) { vpi_printf("%s\n",buffer); } s_vpi_time time_s; time_s.type = vpiSimTime; time_s.low = t-work_ptr->previous_time.low;//Set relative time time_s.high = 0; work_ptr->previous_time.low=t; for (unsigned i=0;i< work_ptr->vpi_handles.size();i++){ value.format = work_ptr->vpi_handles[i].format; switch (i) { case(0):value.value.str =clock_char; break; case(1):value.value.str =reset_char; break; case(2):value.value.str =load_char; break; case(3):value.value.str =load_data_char; break; default: assert(0); } vpi_put_value(work_ptr->vpi_handles[i].handle, &value, &time_s, vpiTransportDelay); } /* schedule callback to this routine when time to read next vector */ data_s.reason = cbAfterDelay; data_s.cb_rtn = Read_Next_line; data_s.obj = 0; data_s.time = &time_s; data_s.value = NULL; data_s.user_data = reinterpret_cast<char *>(work_ptr);// vpi_register_cb(&data_s); } int my_read_test_vector_long(p_cb_data cb_data)//Jul.24.2006 { vpiHandle topModIterator; vpiHandle topModHandle; s_vpi_vlog_info options_s; s_vpi_value value; bool go_this_routine=false; bool debug=false; vpi_get_vlog_info(&options_s); for (int i=1; i<options_s.argc; i++) { string temp=options_s.argv[i]; if (strcmp(options_s.argv[i], "+DEBUG_VPI") == 0) { debug = true; ///flag on } if (strcmp(options_s.argv[i], "+READ_TEST_VECTOR_LONG") == 0) { go_this_routine = true; ///flag on } } if (!go_this_routine) return 0;//Stop further processing if +READ_TEST_VECTOR is not defined at command line. See "Edit Display" of the project /* create a module iterator that starts at the top as indicated by NULL */ topModIterator = vpi_iterate(vpiModule,NULL); if( !topModIterator ){ return 0; } /* use vpi_scan to iterate throught modules */ topModHandle = vpi_scan(topModIterator); if (!topModHandle) return 0; string top_module_name=vpi_get_str(vpiDefName,topModHandle); if (debug) { vpi_printf("Top module name=%s\n",top_module_name.c_str()); } string clock=top_module_name; clock+="."; clock+="clock"; vpiHandle clock_handle=vpi_handle_by_name((char*)clock.c_str(),topModHandle); if (!clock_handle){ vpi_printf("Missing =%s\n",clock.c_str()); return 0; } string load=top_module_name; load+="."; load+="load"; vpiHandle load_handle=vpi_handle_by_name((char*)load.c_str(),topModHandle); if (!load_handle){ vpi_printf("Missing =%s\n",load.c_str()); return 0; } string load_data=top_module_name; load_data +="."; load_data+="load_data"; vpiHandle load_data_handle=vpi_handle_by_name((char*)load_data.c_str(),topModHandle); if (!load_data_handle){ vpi_printf("Missing =%s\n",load_data.c_str()); return 0; } string reset=top_module_name; reset +="."; reset +="reset"; vpiHandle reset_handle=vpi_handle_by_name((char*)reset.c_str(),topModHandle); if (!reset_handle){ vpi_printf("Missing =%s\n",reset.c_str()); return 0; } string file_name="read_vector.txt"; FILE* fp=fopen(file_name.c_str(),"r"); if (!fp) { vpi_printf("Missing=%s\n",file_name.c_str()); } //Now initial check complete //Make some work area read_test_vector_work * work_ptr=new read_test_vector_work; work_ptr->fp=fp; read_test_vector_work::handle_format hf; hf.format=vpiBinStrVal; hf.name="clock"; hf.handle=clock_handle; work_ptr->vpi_handles.push_back(hf); hf.format=vpiBinStrVal; hf.name="reset"; hf.handle=reset_handle; work_ptr->vpi_handles.push_back(hf); hf.format=vpiBinStrVal; hf.name="load"; hf.handle=load_handle; work_ptr->vpi_handles.push_back(hf); hf.format=vpiHexStrVal; hf.name="load_data"; hf.handle=load_data_handle; work_ptr->vpi_handles.push_back(hf); read_line(work_ptr); return(0); }
7.2.10 スタートアップ
ところで、最初のスタートアップはどうすればよいでしょうか?それは、sys_tabl.cにあるvlog_startup_rontines に登録ルーチン(ここでは,sys_math_vpi_register) を登録すればよいのです。
include "vpi_user.h" extern void sys_math_vpi_register(void); __declspec(dllexport) void (*vlog_startup_routines[])() = { sys_math_vpi_register,// 登録ルーチンを書き加えていく。0がエンドマークになっている 0 //エンドマーク };
7.2.11 コールバック実装
Veritak のcbReadWriteSynchは、Non-Blocking Assignmentの後に起こります。
Veritak Uniqueコールバックで、cbReadWriteNBA_veritakは、NBA中に発生します。使い方は、インストールフォルダのsamples\vpi\mycosim\shared_class/shared_file.cppをご参照ください。
7.3 サポートコンパイラ
Visual C++ のみとします。
7.3.1準備
7.4 Visual C++ Express (無償版)を使う
7.4.1 Visual C++のインストール
製品版では、苦もなくDLLを作成することができますが、Express版は、そのままでは、DLLを作成できず、パッチを当てないと使えません。(環境を構築するまでが、かなり面倒です。)
全般の手順は、
http://msdn.microsoft.com/vstudio/express/visualc/usingpsdk/default.aspx
に書いてあります。
以下にポイントの手順を示します。(SDKとVisualC++は同じドライブ下としてください。)
Step 1) Platform SDK のインストール PSDK-x86.exeをダウンロード・インストールします。
Step 2) Visual C++ Expressのインストール
Step 3) Expressをインストールしたフォルダ下に、
Program Files\Microsoft Visual Studio 8\VC\VCProjectDefaults下にある
corewin_express.vsprops
をエディタで編集します。編集内容は、上記ページを参照してください。
Step 4) Microsoft Visual Studio 8\VC\VCWizards\AppWiz\Generic\Application\html\1xxx\ 下にある
AppSettings.htm
をエディタで4行コメントアウトします。コメントアウト箇所は、上記ページを参照してください。なお(1xxx)は、Versionらしく、数字は変わるみたいです。
ちなみに、1041(6月13日2006現在)では、536-539行目にありました。
7.4.2 Visual C++プロジェクトの作成
起動します。ファイル->新規作成->プロジェクトで、プロジェクトを作成します。
プロジェクトは、Win32コンソールアプリケーションとします。プロジェクト名を適当に決めます。ここでは、「mydll5」にしました。
7.4.1のパッチが成功している場合は、「アプリケーションの設定」で、次のようにDLLを設定する○が出てくるので、設定し完了を押します。
すると次のような画面になります。
インストールパッケージsamples\vpi\vpi_sourcesにある全てのファイルをVisualC++のプロジェクトフォルダ下にコピーします。
以下のファイルを選択して、「ソースファイル」->右クリックメニュー->「追加」->「既存の項目」、で追加します。
「libの規則..」というメッセージが出ますが、「いいえ」を押します。すると次の状態となります。
mydll5 を右クリックしプロパティ画面を設定します。文字セットは、「マルチバイト」としてください。
次は、Veritak固有ですが、構造体のアライメントを4としてください。シミュレーションエンジンとの整合を取るためです。
最後に「プリコンパイル済みヘッダは、使用しない」にして、OKを押して設定終了です。
DLLのビルドは、ビルド->mydll5のビルドをクリックすると始まります。いくつかワーニングは出ます。エラー0になっていれば、ビルド成功です。デバッグビルドでは、次のフォルダにmydll5.dllファイルが出来上がっていることがわかります。
Note1:
vpi_user.h と veritak2.lib は、開発中のため、常にアップデートしています。veritak2.exe
とセットでお使いください。(インストールパッケージにあるものをそのままお使いください。)
Note2: エラーが大量に出力される場合は、恐らく、プリコンパイルヘッダの設定の問題です。上の設定を確認してください。
Note3: windows.h が見つからない、というメッセージでのコンパイルエラーは、恐らく、Platform SDKのINCLUDEフォルダへのパスが通っていません。プロジェクトのINCLUDEでPlatform SDKのパスを設定するか、microsoftの文書にあるように、VCProjectEngine.dll.express.config にPlatform SDKのINCLUDEフォルダへのパスを加えます。
Note4: リンカでのエラーは、Platform SDKのLIBフォルダへのパスが通っていません。プロジェクトリンカ設定にPlatformSDK LIBフォルダへのパスを追加するか、microsoftの文書にあるように、VCProjectEngine.dll.express.config にPlatform SDKのLIBフォルダへのパスを加えます。
Note5: Version 3.01Aより、このサンプルのDLL の名称をmy_vpi5.dll に変更しています。
Note6: $myfindwindow でアプリケーションを正しく初期化できませんでした(0xc0000135) とエラーがでるときは、.net framework がPCにインストールされていません。.net
framework をインストールしてください。
Note7: 本サンプルのVisual C++ Express Edition Project/C# Project/Veritak
Project のソースは、ここの ”Verilogで LEDちかちか..” の章にあります。
7.4.3 DLLのデバッグの方法
デバッグは、各々の環境により違いがあります。話を簡単にするために、インストールパッケージのVeritakの実行ファイル郡と、samples\vpi下のファイルを上のフォルダにコピーします。ただし、mydll5.dll
は、コピーしないで、デバッグビルドしたものをそのままお使いください。
DLLは、単独では、実行できませんので、起動は、シミュレーションエンジンであるVeritak2.exeから行います。また、GUIの VeritakWin.exeが走っているとデバッグができませんので、終了させておいてください。
コマンドラインの取得
上のフォルダ上のVeritakWin.exeをクリックして、上のフォルダ上のたとえば、print_netsのプロジェクトを走らせると、"veritak_command.txt"
というテキストファイルが同フォルダに出来ます。これは、GUIがシミュレーションエンジンVeritak2.exeに渡しているコマンドラインになっています。したがってこのファイルから、COPY&Pasteすると簡単です。
デバッガの起動
コマンドを veritak2.exeに
コマンド引数を上のコピーをPasteします。
作業フォルダは、上のフォルダに設定します。(クリックして参照..です)
次にソースを開いて呼び出される箇所にブレークポイントを置きます。
「デバッグ」->「デバッグの開始」で、ブレークポイントで停止します。お使いになると、Visual C++は、強力なデバッグ環境であることが分かると思います。
なお、デバッグビルドでは、実行速度が10倍程度遅いので、デバッグが終了したらリリースビルドされることをお勧めします。その際、プロジェクトの設定は、再度必要になりますので、ご注意ください。
Note1:
Veritak2.exeのデバッグシンボルがない、というメッセージが出ることがありますが、構わずに進んでください。
7.4.4 作成したDLLの組み込み
通常どおりVerilog ソースファイルをプロジェクトに加えるのは同じです。DLLの組み込みは、ファイルの種類を「すべての種類」にすると、DLLファイルが見えますので、通常ソースと同様にダブルクリックするか、Addボタンでプロジェクトに加えます。