Verilog HDLでMixed Signal

DWMの2006年11月号にVerilog HDLでクロックリカバリー回路をシミュレーションする記事が掲載されています。クロックリカバリ、ルンゲクッタ、VCOのシミュレーションの仕方、動作原理についての詳しい解説は、記事を読んで頂くことにして、ここでは、VPIを適用してシミュレーションを高速化することを狙ってみます。








高速化の狙いどころーまとまったFunctionルーチンを呼び出す


記事では、ユーザVPIは使わずに、全てをVerilog HDLで書かれています。記事が書かれた当時、Veritakでは、未だVPIをサポートしていませんでしたが、現在は、VisualC++Expressという、高速なC++コンパイラを無償で使える環境も整ったので、VPIで処理を高速化してみたいと思います。

記事中、VCOの非線形部のモデリングや、ルンゲクッタなどは、処理としてまとまっており、VPIで呼び出すことで高速化が期待できます。

一般に、このようなモデリングで、

という風に、計算自体に時間がかかっているところ、かつ、処理として一つのfunction call にまとめられるところが、VPIによる高速化の狙いどころです。ルンゲクッタの方は、内部状態を持ちますので、単純なFunction CALLというより、一つのC++クラスによる実装になりますが、そういう場合でもVPIを適用することは可能です。

一方、VPI化によって、次のマイナス面についても考慮が必要です。

折角、VPI化しても、呼び出す引数の数が多いと、そこで時間を食ってしまいます。記述上の工夫は後述しますが、なるべくなら引数は、少ない方がよいです。また、引数にない内部信号は、観測ができませんので、デバッグが難しくなる問題もあります。
(今回は、Verilog HDLで記述されたソースをほぼそのままC++で書き換えるだけですので、その点では、あまり苦労はしませんでした。)
 

それでは、VPIの実装方法について見ていきましょう。
次は、VCOの非線形部のモデリング部ソース(抜粋)です。

initial begin
                PI2 = 6.28318530717958647692528676655901;
                theta=THETA_INIT;
                dlt_t=TM_PRCS*CYCLE_VCO;
                cycle=CYCLE_VCO;

                vin24b_lmtH=24'h0E_6666;
                vin24b_lmtL=24'h00_0000;

                a0=4.6984;//a0〜a4:4次多項式近似の係数。
                a1=0.2565;
                a2=-0.9054;
                a3=1.6146;
                a4=-0.548;
        end

        always #(cycle) begin
                if (rst) begin
                        theta=THETA_INIT;
                        out0p = 1'b0;
                        out0n = ~out0p;
                        out45p = 1'b0;
                        out45n = ~out45p;
                        out90p = 1'b0;
                        out90n = ~out90p;
                        out135p = 1'b0;
                        out135n = ~out135p;
                end else begin
                        //シングルエンド入力vinに対するリミッタ。
                        if(vin[23]==1'b0)
                                vin2=(vin > vin24b_lmtH)? vin24b_lmtH : vin;
                        else
                                vin2=vin24b_lmtL;

                        //整数から実数へ変換。
                        tmp32b_1={ {8{vin2[23]} }, vin2};
                        tmp_r1=$itor(tmp32b_1);
                        vin_rl=tmp_r1/K_RTOI;

                        //入力電圧を、周波数、位相へ変換。
                        frq = a0 
                                +a1*vin_rl
                                +a2*(vin_rl*vin_rl)
                                +a3*(vin_rl*vin_rl*vin_rl)
                                +a4*(vin_rl*vin_rl*vin_rl*vin_rl);
                        tmp_r2=theta +dlt_t*PI2*frq;
                        theta=(tmp_r2 > PI2)? (tmp_r2-PI2) : tmp_r2;

                        //位相(4つの位相)を電圧に変換し、ディジタル化する。
                        tmp_r3a=$sin(theta);
                        tmp_r3b=$sin(theta-(PI2/8.0));
                        tmp_r3c=$sin(theta-(PI2/4.0));
                        tmp_r3d=$sin(theta-(PI2*3/8.0));
                        tmp_r4a=tmp_r3a*K_RTOI;
                        tmp_r4b=tmp_r3b*K_RTOI;
                        tmp_r4c=tmp_r3c*K_RTOI;
                        tmp_r4d=tmp_r3d*K_RTOI;
                        tmp32b_2=$rtoi(tmp_r4a);
                        out0p = (tmp32b_2[31]==1'b0)? 1'b1: 1'b0;
                        out0n = ~out0p;
                        tmp32b_2=$rtoi(tmp_r4b);
                        out45p = (tmp32b_2[31]==1'b0)? 1'b1: 1'b0;
                        out45n = ~out45p;
                        tmp32b_2=$rtoi(tmp_r4c);
                        out90p = (tmp32b_2[31]==1'b0)? 1'b1: 1'b0;
                        out90n = ~out90p;
                        tmp32b_2=$rtoi(tmp_r4d);
                        out135p = (tmp32b_2[31]==1'b0)? 1'b1: 1'b0;
                        out135n = ~out135p;
                end
        end

//($sinは、Veritak Unique Functionです。内部実装は、やはりVPIを使っています。算術ライブラリ編をご参照ください。)


下は、VPIを使ったVersionです。初期化と毎回部分と二つのVPI CALLにまとめています。

initial begin
                PI2 = 6.28318530717958647692528676655901;
                theta=THETA_INIT;
                dlt_t=TM_PRCS*CYCLE_VCO;
                cycle=CYCLE_VCO;

                vin24b_lmtH=24'h0E_6666;
                vin24b_lmtL=24'h00_0000;

                a0=4.6984;//a0〜a4:4次多項式近似の係数。
                a1=0.2565;
                a2=-0.9054;
                a3=1.6146;
                a4=-0.548;

                THETA_INIT_val=THETA_INIT;
                K_RTOI_val=K_RTOI;
                $init_my_vco(theta,dlt_t,vin24b_lmtH,vin24b_lmtL,
                a0,a1,a2,a3,a4,THETA_INIT_val,K_RTOI_val,rst,vin,out0p,out45p,out90p,out135p,vin_rl);//VCO のVPI初期化ルーチン
        end

        always #(cycle) begin
                        $calc_my_vco();//VCO VPI CALL
                        out0n = ~out0p;
                        out45n = ~out45p;
                        out90n = ~out90p;
                        out135n = ~out135p;
                
        end


このVPI CALLの中身は、次のC++記述です。
最初のiniialize Callで、パラメータの初期値を渡しているほかに、毎回呼び出す部分のインターフェース信号のハンドルを渡している点がポイントです。
これで、毎回呼び出す部分では、お決まりの引数を呼び出す部分が不要になり、高速化に寄与します。

このソースの構造は、
 本体の前で、Verilog HDL信号の値の読み込み、
 本体:ほぼVerilogHDLソースをそのまま移植しただけ
 本体後で、Verilog HDL信号への値の書き込み

になっています。それほど難しくはありませんね。

double theta;
double dlt_t;
unsigned vin24b_lmtH,   vin24b_lmtL;
double a0,a1,a2,a3,a4;//a0〜a4:4次多項式近似の係数。
double K_RTOI_val,THETA_INIT;

vpiHandle vin_handle=0;
vpiHandle vco_reset_handle=0;
vpiHandle out0p_handle=0,out45p_handle=0,out90p_handle=0,out135p_handle=0;
vpiHandle vin_rl_handle=0;

static int init_my_vco_calltf(char* name)
{
  vpiHandle systfref = vpi_handle(vpiSysTfCall, NULL); /* get system function that invoked C routine */
  vpiHandle argsiter = vpi_iterate(vpiArgument, systfref);/* get iterator (list) of passed arguments */
                 
  vpiHandle argh=0;             
  s_vpi_value value;
  vpi_printf(" Initializing vco parameter.\n");
  while(argh = vpi_scan(argsiter)){
                string str=vpi_get_str(vpiName,argh);
                if (!str.compare("theta")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        theta=value.value.real;
                        vpi_printf("theta=%g\n",theta);
                        
                }else if (!str.compare("dlt_t")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        dlt_t=value.value.real;
                        vpi_printf("dlt_t=%g\n",dlt_t);
                }else if (!str.compare("a0")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        a0=value.value.real;
                        vpi_printf("a0=%g\n",a0);
                }else if (!str.compare("a1")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        a1=value.value.real;
                        vpi_printf("a1=%g\n",a1);
                }else if (!str.compare("a2")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        a2=value.value.real;
                        vpi_printf("a2=%g\n",a2);
                }else if (!str.compare("a3")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        a3=value.value.real;
                        vpi_printf("a3=%g\n",a3);
                }else if (!str.compare("a4")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        a4=value.value.real;
                        vpi_printf("a3=%g\n",a4);
                }else if (!str.compare("vin24b_lmtH")) {
                        value.format=vpiIntVal;
                        vpi_get_value(argh, &value);
                        vin24b_lmtH=value.value.integer;
                        vpi_printf("vin24b_lmtH=%d\n",vin24b_lmtH);     
                }else if (!str.compare("vin24b_lmtL")) {
                        value.format=vpiIntVal;
                        vpi_get_value(argh, &value);
                        vin24b_lmtL=value.value.integer;
                        vpi_printf("vin24b_lmtL=%d\n",vin24b_lmtL);     
                
                }else if (!str.compare("vin")) {
                        vin_handle=argh;
                }else if (!str.compare("rst")) {
                        vco_reset_handle=argh;
                }else if (!str.compare("out0p")) {
                        out0p_handle=argh;
                }else if (!str.compare("out45p")) {
                        out45p_handle=argh;
                }else if (!str.compare("out90p")) {
                        out90p_handle=argh;
                }else if (!str.compare("out135p")) {
                        out135p_handle=argh;
                }else if (!str.compare("vin_rl")) {
                        vin_rl_handle=argh;
                }else if (!str.compare("K_RTOI_val")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        K_RTOI=value.value.real;
                        vpi_printf("K_RTOI_val=%g\n",K_RTOI);

                }else if (!str.compare("THETA_INIT_val")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        THETA_INIT=value.value.real;
                        vpi_printf("THETA_INIT_val=%g\n",THETA_INIT);
                }
   }
   vpi_printf("vco parameter initilization done.\n\n");

  return 0;
}

static int calc_my_vco_calltf(char* name)
{

        int out0p,out45p,out90p,out135p;
        unsigned vin2,vin;
        int rst;
        double tmp_r3a,tmp_r3b,tmp_r3c,tmp_r3d;
        double tmp_r4a,tmp_r4b,tmp_r4c,tmp_r4d;
        double vin_rl;
        double tmp_r1,tmp_r2;
        double PI2=6.28318530717958647692528676655901;
        double frq;
        unsigned tmp32b_2;
//Verilogの値を読む     
    s_vpi_value value;
        if (!vco_reset_handle ||
                !vin_handle ||
                !out0p_handle ||
                !out45p_handle ||
                !out90p_handle ||
                !out135p_handle ||
                !vin_rl_handle
                ) {
                
                vpi_printf("VPI Programming Error. VCO Handle Error.\n");
                vpi_sim_control(vpiFinish, 1);                  
                return 0;
        }
 //rst
        value.format = vpiScalarVal;
        vpi_get_value(rst_handle, &value);
        rst=value.value.scalar & 0x01;
 //vin
        value.format = vpiIntVal;
        vpi_get_value(vin_handle, &value);
        vin=value.value.integer;
 
//読み込み終了

//本体始まり
        if (rst) {
                        theta=THETA_INIT;
                        out0p = 0;
                //      out0n = ~out0p;
                        out45p = 0;
                //      out45n = ~out45p;
                        out90p = 0;
                //      out90n = ~out90p;
                        out135p = 0;
                //      out135n = ~out135p;
        } else {
                        //シングルエンド入力vinに対するリミッタ。
                        if(vin>>23 /*[23]*/==0)
                                vin2=(vin > vin24b_lmtH)? vin24b_lmtH : vin;
                        else
                                vin2=vin24b_lmtL;

                        //整数から実数へ変換。
                        if (vin2>> 23 ==1) tmp_r1=-(int)(vin2& 0x07ffffff);
                        else tmp_r1=vin2;
                        //tmp32b_1={ {8{vin2[23]} }, vin2};
                        //tmp_r1=$itor(tmp32b_1);
                        vin_rl=tmp_r1/K_RTOI;
                    
                        value.format=vpiRealVal;
                        value.value.real=vin_rl;
                        vpi_put_value(vin_rl_handle, &value, NULL, vpiNoDelay);//

                        //入力電圧を、周波数、位相へ変換。
                        frq = a0 
                                +a1*vin_rl
                                +a2*(vin_rl*vin_rl)
                                +a3*(vin_rl*vin_rl*vin_rl)
                                +a4*(vin_rl*vin_rl*vin_rl*vin_rl);
                        tmp_r2=theta +dlt_t*PI2*frq;
                        theta=(tmp_r2 > PI2)? (tmp_r2-PI2) : tmp_r2;

                        //位相(4つの位相)を電圧に変換し、ディジタル化する。
                        tmp_r3a=sin(theta);
                        tmp_r3b=sin(theta-(PI2/8.0));
                        tmp_r3c=sin(theta-(PI2/4.0));
                        tmp_r3d=sin(theta-(PI2*3/8.0));
                        tmp_r4a=tmp_r3a*K_RTOI;
                        tmp_r4b=tmp_r3b*K_RTOI;
                        tmp_r4c=tmp_r3c*K_RTOI;
                        tmp_r4d=tmp_r3d*K_RTOI;
                        tmp32b_2=/*$rtoi*/(tmp_r4a);
                        out0p = (tmp32b_2 >>31 /*[31]*/==0)? 1: 0;
                        //out0n = ~out0p;
                        tmp32b_2=/*$rtoi*/(tmp_r4b);
                        out45p = (tmp32b_2>>31 /*[31]*/==0)? 1: 0;
                        //out45n = ~out45p;
                        tmp32b_2=/*$rtoi*/(tmp_r4c);
                        out90p = (tmp32b_2>>31 /*[31]*/==0)? 1: 0;
                        //out90n = ~out90p;
                        tmp32b_2=/*$rtoi*/(tmp_r4d);
                        out135p = (tmp32b_2>>31/*[31]*/==0)? 1: 0;
                        //out135n = ~out135p;
        }
//本体終わり
//Verilogに値を戻す。

        value.format=vpiScalarVal;
        value.value.scalar=out0p;
        vpi_put_value(out0p_handle, &value, NULL, vpiNoDelay);//

        value.value.scalar=out45p;
        vpi_put_value(out45p_handle, &value, NULL, vpiNoDelay);//

        value.value.scalar=out90p;
        vpi_put_value(out90p_handle, &value, NULL, vpiNoDelay);//

        value.value.scalar=out135p;
        vpi_put_value(out135p_handle, &value, NULL, vpiNoDelay);//

        

        return 0;
}

次は、ルンゲクッタ部の実装です。
VPI適用後のソースです。
上と同様に、初期化部と毎回部を分けています。
Verilog HDLソースで ノンブロッキングアサイン(<=)をしている信号があるのですが、VPIでは、ブロッキングアサイン(=)しかありません。そこで、Verilog HDLソースに信号を追加し、VPIで計算された信号*_BKの後に <=代入を行う文を追加しています。

initial begin
                rl_relerr=RELERR;
                rl_abserr=ABSERR;
                rst_rk=1'b1;//サイクル数のリセット。
                hit_min_h=0;//最小時間刻みの使用頻度を0にセット。

                tmp_r1=TS_RK/TM_PRCS;
                tmp_i1=tmp_r1;
                cycle=(tmp_i1 < 1)? 1:tmp_i1;
                tmp_r2=cycle;
                a11=-(TM_PRCS*1000.0*tmp_r2)/(C2*RP);
                a12=-a11;
                a21=(TM_PRCS*1000.0*tmp_r2)/(CP*RP);
                a22=-a21;
                b1=(TM_PRCS*1000.0*tmp_r2)/C2;

                tmp_r1=cycle;
                tmp_r2=(N_STP_INI/(TM_PRCS*cycle));
                tmp_i1=tmp_r2;
                h_n0=(tmp_i1 < 1)? 1:tmp_i1;

                ip_pd1=CUR_CP_PD1*0.000001;//[A]、PDにおける正側出力電流値。
                in_pd1=-RTO_B_A_PD1*CUR_CP_PD1*0.000001;//[A]、PDにおける負側出力電流値。
                ip_fd=CUR_CP_FD*0.000001;//[A]、FDにおける正側出力電流値。
                in_fd=-RTO_B_A_FD*CUR_CP_FD*0.000001;//[A]、FDにおける負側出力電流値。
                im_fd=OFS_CUR_CP_FD*0.000001;//[A]、FDにおける中間レベル出力電流値(理想は零)。
                rl_x1=0;
                rl_x2=0;

                tmp32b_lmtH={8'h00, 24'h7F_FFFF};
                tmp32b_lmtL=~tmp32b_lmtH;

                K_RTOI_val=K_RTOI;
                $init_my_runge_kutta(rl_relerr,rl_abserr,a11,a12,a21,a22,h_n,h_n0,rl_x1,rl_x2,
                tmp32b_lmtH,tmp32b_lmtL,vout_BK,rst_rk_BK,rst,rst_rk,hit_min_h,K_RTOI_val,rl_b1_iin);
        end

        //チャージポンプ
        always @ (pd1p or fdp) begin
                if(pd1p==1'b1) i_pd1=ip_pd1;
                else i_pd1=in_pd1;

                if((fdp==1'b1) &(fdn==1'b0)) i_fd=ip_fd;
                else if((fdp==1'b0) &(fdn==1'b1)) i_fd=in_fd;
                else i_fd=im_fd;

                //ルンゲクッタ用
                rl_iin=i_pd1-i_fd;
                rl_b1_iin=b1*rl_iin;
                rst_rk <= 1'b1;
        end

        //Low Pass Filter
        always #(cycle) begin
                $calc_my_runge_kutta();
                vout<=vout_BK;
                rst_rk<=rst_rk_BK;
        end
対応するVPIのソースです。C++のクラスにまとめたいところですが、Cから来ている人が見やすいようにStatic変数で書いています。
//STATIC 変数
  //  int cycle;//ルンゲクッタ処理の実行頻度の逆数。
        int h_n, h_n0;//ルンゲクッタ計算でのスキップ数、スキップ数の初期値。
        int cnt_dwn_rk;//ルンゲクッタ計算におけるスキップ数のカウンタ。
        int hit_min_h;//ルンゲクッタ計算における最小時間刻みの使用頻度(CP出力更新時を除く)。
        
        //double ip_pd1, in_pd1, ip_fd, im_fd, in_fd;
        //double i_pd1, i_fd;
        //double rl_iin;
        double k1a, k1b, k2a=0.0, k2b=0.0, k3a, k3b;

        double a11,a12, a21, a22;
        double rl_b1_iin, rl_x1, rl_x2 ;
        
        double rl_relerr, rl_abserr;
        
        double K_RTOI;
        unsigned tmp32b_lmtH,tmp32b_lmtL;

        int cal_rk;
        int good_err;
        



//読み込み用ハンドル
    vpiHandle rst_handle=0;
        vpiHandle rst_rk_handle=0;
        vpiHandle rl_b1_iin_handle=0;

//書き込み用ハンドル
        vpiHandle vout_BK_handle=0;
        vpiHandle hit_min_h_handle=0;
        vpiHandle rst_rk_BK_handle=0;

//初期値設定及び、毎回計算用の変数ハンドルを得る
//initial で一回だけ呼び出す
static int init_my_runge_kutta_calltf(char* name)
{
  vpiHandle systfref = vpi_handle(vpiSysTfCall, NULL); /* get system function that invoked C routine */
  vpiHandle argsiter = vpi_iterate(vpiArgument, systfref);/* get iterator (list) of passed arguments */
                 
  vpiHandle argh=0;             
  s_vpi_value value;
  vpi_printf("Initializing parameter of Rungekutta.\n");
  while(argh = vpi_scan(argsiter)){
                string str=vpi_get_str(vpiName,argh);
                if (!str.compare("rl_relerr")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        rl_relerr=value.value.real;
                        vpi_printf("r1_relerr=%g\n",rl_relerr);
                        
                }else if (!str.compare("rl_abserr")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        rl_abserr=value.value.real;
                        vpi_printf("rl_abserr=%g\n",rl_abserr);
                }else if (!str.compare("a11")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        a11=value.value.real;
                        vpi_printf("a11=%g\n",a11);
                }else if (!str.compare("a12")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        a12=value.value.real;
                        vpi_printf("a12=%g\n",a12);
                }else if (!str.compare("a21")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        a21=value.value.real;
                        vpi_printf("a21=%g\n",a21);
                }else if (!str.compare("a22")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        a22=value.value.real;
                        vpi_printf("a22=%g\n",a22);
                }else if (!str.compare("rl_x1")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        rl_x1=value.value.real;
                        vpi_printf("rl_x1=%g\n",rl_x1);
                }else if (!str.compare("rl_x2")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        rl_x2=value.value.real;
                        vpi_printf("rl_x2=%g\n",rl_x2);         
                }else if (!str.compare("tmp32b_lmtH")) {
                        value.format=vpiIntVal;
                        vpi_get_value(argh, &value);
                        tmp32b_lmtH=value.value.integer;
                        vpi_printf("tmp32b_lmtH=%d\n",tmp32b_lmtH);     
                }else if (!str.compare("tmp32b_lmtL")) {
                        value.format=vpiIntVal;
                        vpi_get_value(argh, &value);
                        tmp32b_lmtL=value.value.integer;
                        vpi_printf("tmp32b_lmtL=%d\n",tmp32b_lmtL);     
                }else if (!str.compare("h_n")) {
                        value.format=vpiIntVal;
                        vpi_get_value(argh, &value);
                        h_n=value.value.integer;
                        vpi_printf("h_n=%d\n",h_n);     
                }else if (!str.compare("h_n0")) {
                        value.format=vpiIntVal;
                        vpi_get_value(argh, &value);
                        h_n0=value.value.integer;
                        vpi_printf("h_n0=%d\n",h_n0);   
                }else if (!str.compare("rst")) {
                        rst_handle=argh;
                }else if (!str.compare("rst_rk")) {
                        rst_rk_handle=argh;
                }else if (!str.compare("vout_BK")) {
                        vout_BK_handle=argh;
                }else if (!str.compare("hit_min_h")) {
                        hit_min_h_handle=argh;
                        value.format=vpiIntVal;
                        vpi_get_value(argh, &value);
                        hit_min_h=value.value.integer;
                        vpi_printf("hit_min_h=%d\n",hit_min_h); 
                }else if (!str.compare("rst_rk_BK")) {
                        rst_rk_BK_handle=argh;
                        vpi_printf("rst_rk_BK_handle=%x",rst_rk_BK_handle);
                }else if (!str.compare("K_RTOI_val")) {
                        value.format=vpiRealVal;
                        vpi_get_value(argh, &value);
                        K_RTOI=value.value.real;
                        vpi_printf("K_RTOI=%g\n",K_RTOI);       
                }else if (!str.compare("rl_b1_iin")) {
                        rl_b1_iin_handle=argh;

                }
   }
  vpi_printf("Initialization Done.\n\n");
  return 0;
}

 static int calc_my_runge_kutta_calltf(char*  name)
{

//Verilog インターフェース変数
        unsigned rst;
        unsigned rst_rk_BK;
        unsigned vout_BK;
        unsigned rst_rk;

//ローカル変数  
    double  tmp_r1, tmp_r2, tmp_r3, tmp_r4;
        double dif_x1, dif_x2, err_x1, err_x2;
        
        int tmp_i1, tmp_i2;
        unsigned  tmp32b_1;
        
//Verilogの値を読む     
    s_vpi_value value;
        if (!rst_handle ||
                !rst_rk_handle ||
                !rl_b1_iin_handle) {
                
                vpi_printf("VPI Programming Error. Handle Error.%x %x %x\n",rst_handle,rst_rk_handle,rl_b1_iin_handle);
                vpi_sim_control(vpiFinish, 1);                  
                return 0;

        }
 //rst
        value.format = vpiScalarVal;
        vpi_get_value(rst_handle, &value);
        rst=value.value.scalar & 0x01;
 //rst_rk
        value.format = vpiScalarVal;
        vpi_get_value(rst_rk_handle, &value);
        rst_rk=value.value.scalar & 0x01;
 //rl_b1_iin
        value.format=vpiRealVal;
        vpi_get_value(rl_b1_iin_handle, &value);
        rl_b1_iin=value.value.real;
//読み込み終了

//本体始まり
        //vpi_printf("rst==%d \n",rst); 
        if(rst==1) {
                        rl_x1=0;
                        rl_x2=0;

                        //ルンゲクッタ用
                        rl_b1_iin=0.0;
                        rst_rk_BK /*<*/= 1;//TODO       
                        value.format=vpiScalarVal;
                        value.value.scalar=rst_rk_BK;
                        //vpi_printf("rst_rt_BK_handle=%x\n",rst_rk_BK_handle);
                        vpi_put_value(rst_rk_BK_handle, &value, NULL, vpiNoDelay);//;
                        
                        h_n=h_n0;
                //      vpi_printf("h_n=%d h_n0=%d\n",h_n,h_n0);
        } else {
                //ルンゲクッタ: 2次と3次の埋め込み式
                if(rst_rk==1) {
                                tmp_i1=h_n-cnt_dwn_rk;
                                tmp_i2=(tmp_i1 < 1)? 1: tmp_i1;
                        
                                tmp_r1=h_n;
                                tmp_r2=tmp_i2;
                                tmp_r3=tmp_r2/tmp_r1;
                        //      vpi_printf(" k2a=%g  tmp_r1=%g h_n=%d\n",k2a,tmp_r1,h_n);
                                rl_x1 = rl_x1 +tmp_r3*k2a;//状態値の更新。
                                rl_x2 = rl_x2 +tmp_r3*k2b;//状態値の更新。

                                h_n=h_n0;
                                cnt_dwn_rk=h_n0;
                                cal_rk=1;
                } else {
                        if(cnt_dwn_rk==0) {
                                        rl_x1 = rl_x1 +k2a;//状態値の更新。
                                        rl_x2 = rl_x2 +k2b;//状態値の更新。

                                        h_n = (h_n << 2);
                                        cnt_dwn_rk=h_n;
                                        cal_rk=1;
                        }else
                                        cal_rk=0;
                }
                
                        if(cal_rk==1) { 
                                while (cal_rk==1) {
                                //ルンゲクッタ計算のコア部。
                                        tmp_r3=h_n; 
                                //      vpi_printf(" tmp_r3=%g  rl_b1_iin=%g rl_x2=%g\n",tmp_r3,rl_b1_iin,rl_x2);       
                                        
                                        
                                        k1a=(a11*rl_x1 +a12*rl_x2 +rl_b1_iin)*tmp_r3;
                                        k1b=(a21*rl_x1 +a22*rl_x2)*tmp_r3;
                                        
                                //vpi_sim_control(vpiStop, 1);
                                        tmp_r1=rl_x1 +k1a*0.5;
                                        tmp_r2=rl_x2 +k1b*0.5;
                                        k2a=(a11*tmp_r1 +a12*tmp_r2 +rl_b1_iin)*tmp_r3;
                                        k2b=(a21*tmp_r1 +a22*tmp_r2)*tmp_r3;

                                        tmp_r1=rl_x1 -k1a +k2a*2;
                                        tmp_r2=rl_x2 -k1b +k2b*2;
                                        k3a=(a11*tmp_r1 +a12*tmp_r2 +rl_b1_iin)*tmp_r3;
                                        k3b=(a21*tmp_r1 +a22*tmp_r2)*tmp_r3;

                                        dif_x1=(-k1a +k2a*2.0 -k3a)/6.0;
                                        dif_x2=(-k1b +k2b*2.0 -k3b)/6.0;
                                        tmp_r3 = rl_x1 +(k1a +k2a*4 +k3a)/6.0;
                                        tmp_r4 = rl_x2 +(k1b +k2b*4 +k3b)/6.0;

                                        tmp_r1=(tmp_r3 > 0)? tmp_r3:-tmp_r3;
                                        tmp_r2=(tmp_r4 > 0)? tmp_r4:-tmp_r4;
                                        err_x1=tmp_r1*rl_relerr + rl_abserr;
                                        err_x2=tmp_r2*rl_relerr + rl_abserr;
                                        tmp_r1=(dif_x1 > 0)? dif_x1:-dif_x1;
                                        tmp_r2=(dif_x2 > 0)? dif_x2:-dif_x2;

                                        good_err=((err_x1 >= tmp_r1) && (err_x2 >= tmp_r2))? 1:0;
                                        
                                        if((good_err==1 ) || (h_n==1)) {
                                                rst_rk_BK /*<*/= 0;//TODO 初期化を解除。
                                                value.format=vpiScalarVal;
                                                value.value.scalar=rst_rk_BK;
                                                //vpi_printf("rst_rt_BK_handle=%x\n",rst_rk_BK_handle);
                                                vpi_put_value(rst_rk_BK_handle, &value, NULL, vpiNoDelay);//;
                                                cal_rk = 0;//ルンゲクッタ計算ループから出る。
                                        } else {
                                                tmp_i1 = h_n >> 2;
                                                h_n=(tmp_i1 < 1)? 1: tmp_i1;
                                                hit_min_h=(tmp_i1 < 1)? (hit_min_h+1) : hit_min_h;
                                                value.format=vpiIntVal;
                                                value.value.integer=hit_min_h;
                                                vpi_put_value(hit_min_h_handle, &value, NULL, vpiNoDelay);//


                                                cnt_dwn_rk=h_n;
                                        }
                                }
                        }
                        cnt_dwn_rk=cnt_dwn_rk-1;//ダウンカウンタのデクリメント。
                }

                tmp_r3=rl_x1*K_RTOI;
                tmp32b_1=/*$rtoi( */tmp_r3;
                if(tmp32b_1 >>31 /*[31]*/==0) //TODO
                        vout_BK /*<*/= (tmp32b_1 > tmp32b_lmtH)? tmp32b_lmtH /*[23:0]*/ : tmp32b_1 /*[23:0] */ ; //リミッタ。
                else
                        vout_BK /*<*/ = (tmp32b_1 < tmp32b_lmtL)? tmp32b_lmtL/*[23:0] */ : tmp32b_1 /*[23:0] */;//リミッタ。

//本体終わり
//Verilogに値を戻す。ブロッキングアサインになるので、ノンブロッキングアサインは、Verilogで行う
        


        value.format=vpiIntVal;
        value.value.integer=vout_BK;
        vpi_put_value(vout_BK_handle, &value, NULL, vpiNoDelay);//

        

   return 0;
 }


以上で、主要なルーチンは終わりです。$で呼び出す関数の登録をする部分です。

extern "C" void vco_entry()//VPI call/callback register routine
{
      s_vpi_systf_data tf_data;

      tf_data.type      = vpiSysTask;//
      tf_data.tfname    = "$init_my_runge_kutta";
      tf_data.user_data = 0;
      tf_data.calltf    = init_my_runge_kutta_calltf;
      tf_data.compiletf = 0;
      vpi_register_systf(&tf_data);

          tf_data.type      = vpiSysTask;//
      tf_data.tfname    = "$calc_my_runge_kutta";
      tf_data.user_data = 0;
      tf_data.calltf    = calc_my_runge_kutta_calltf;
      tf_data.compiletf = 0;
      vpi_register_systf(&tf_data);


          tf_data.type      = vpiSysTask;//
      tf_data.tfname    = "$init_my_vco";
      tf_data.user_data = 0;
      tf_data.calltf    = init_my_vco_calltf;
      tf_data.compiletf = 0;
      vpi_register_systf(&tf_data);

          tf_data.type      = vpiSysTask;//
      tf_data.tfname    = "$calc_my_vco";
      tf_data.user_data = 0;
      tf_data.calltf    = calc_my_vco_calltf;
      tf_data.compiletf = 0;
      vpi_register_systf(&tf_data);
}


最後にこのエントリ関数をスタートアップ部で呼び出す形にして完成です。

# include  "vpi_user.h"



extern void vco_entry(void);


__declspec(dllexport) void (*vlog_startup_routines[])() = {
     
                vco_entry,


      0
};




結果

VPIを用いた場合の比較結果です。

波形なしでは、18倍速になりました。(マシンはAthlon3800+DUAL 2GB Memory、Veritak.3.21B) 5分かかっていた処理が19secになりました。
C++への書き換えとデバッグ作業に約12時間程かかりましたが、一度雛形ができると、類似のプロジェクトでも利用できるために、利用価値は高いと言えます。
VPIは、Verilog-2001の規格ですが、SystemVerilogにおいても同じです。従い、折角書いたルーチンが無駄になる心配はありません。また、特にVendor毎に記述を変える必要はありません。

アーカイブ
筆者の方から許可をいただきました。この場を借りて厚く御礼申し上げます。

オリジナルソースとVPI適用後Verilog HDLソース/Veritak プロジェクト、 Project,VC++ソース/プロジェクトのアーカイブです。VPI有り・無し二つのプロジェクトが入っているので、簡単に差を体感できます。

vco.vtakprj     VPIを使用しないVertak Projectです。
vco_tak.vtakprj   VPIを使用したVeritak Projectです。(高速化Version)