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)