Verilogでアナログシュミレート?

状態方程式の一般形式は


です。制御系の技術者にはおなじみの式ですね。これを4次のルンゲクッタで計算させています。
呼び出し形式は

$runge_kutta("ファイル名”、モード、Input_RealArray、Output_RealArray);

 それでは、使い方のデモソースです。
Initialの$runge_kuttaで、遷移行列ファイルの指定、モードの指定、入出力のREALアレーを定義します。
次にAlways文で、毎クロック呼び出します。

`timescale 1ns / 1ns

//Apr.18.2005 rungekutta IF changed.
module runge_kutta_test;
        parameter freq =900e3;
        parameter dac_width=10;

        real read_array[0:10];
        real write_array[0:10];
        real read_array2[14:24];
        real write_array2[10:24];
        real t,amp,ar;
        reg clock=0;
        reg [15:0] filt_out1,filt_out2;

        reg [dac_width-1:0] dac_input;
        real range;
        reg Reset;
        wire  dac_out;
        real ar;
        always #1 clock=~clock;

        always @(posedge clock) begin
            
              t=$time()*1e-9;
              amp=($sin(2*$M_PI()*t*freq)+1)*range;
              dac_input=$rtoi(amp);     
              
              read_array[0]=dac_out*6283185.3;//cut off 1e6Hz degree2
                //ar=$sin(2*$M_PI()*t*freq);
              read_array2[14]=dac_out*6283185.3;//cur off 1e6Hz degree10

              $runge_kutta("Ae6_2.mat",1);
              $runge_kutta("Ae6.mat",1);
                ar=dac_out*6283185.3;
        //      filt_out1=write_array[1]*range;//2degree 0+(2-1)=1
           //   filt_out2=write_array2[19]*range;//10degree 10+(10-1)=19
                filt_out1=write_array[0]*range;//Apr.18.2005 2degree 0+(2-1)=1
              filt_out2=write_array2[10]*range;//Apr.18.2005 10degree 10+(10-1)=19
                //$display("filt_out2=%e %e",ar,write_array2[11]);
        
        end
        
         sigma_delta  #(dac_width)  dac1(dac_out, dac_input,clock, Reset);


        initial begin
                Reset=1;
                range=((1 << dac_width)-2)/2;

        

                $runge_kutta("Ae6_2.mat",0,read_array,write_array);
                $runge_kutta("Ae6.mat",0,read_array2,write_array2);

                #100 Reset=0;
                #100000 $finish;

        end             


endmodule



module sigma_delta(out, DACin, Clk, Reset);
parameter dac_with=10;
input [dac_with-1:0] DACin; //
input Clk;
input Reset;
output out;  

 reg  [dac_with-1+2:0] R1; 
 wire out=R1[dac_with-1+2];

 wire [dac_with-1+2:0]  delta=   {R1[dac_with-1+2], R1[dac_with-1+2], {dac_with{1'b0}}};
 wire [dac_with-1+2:0]  adder1 = {2'b00,DACin} + delta;
 wire  [dac_with-1+2:0] adder2 = adder1 + R1;

always @(posedge Clk or posedge Reset)
begin
        if(Reset) R1 <= {1'b1, 11'h000};
        else      R1 <=adder2;
end                     
                
endmodule

本例では、一次のシグマデルタDAコンバータでDA変換しています。シグマデルタコンバータの出力は1ビットのビットストリームになります。これを2次、および10次のアナログバターワースLPFでアナログに戻してみます。

10次フィルタの行列は次で与えられます。
ファイルとして読み込むのは、Aマトリクスだけです。残りは、Verilogソース内でプログラムします。ルンゲクッタの計算結果xはwrite_array2に入ります。write_array2は、LEFT_ADDRESS10から始まり10次の行列ですので出力は19にはいることになります。

A =

  1.0e+008 *

   -1.2412   -0.6283         0         0         0         0         0         0         0         0
    0.6283         0         0         0         0         0         0         0         0         0
         0    0.6283   -1.1197   -0.6283         0         0         0         0         0         0
         0         0    0.6283         0         0         0         0         0         0         0
         0         0         0    0.6283   -0.8886   -0.6283         0         0         0         0
         0         0         0         0    0.6283         0         0         0         0         0
         0         0         0         0         0    0.6283   -0.5705   -0.6283         0         0
         0         0         0         0         0         0    0.6283         0         0         0
         0         0         0         0         0         0         0    0.6283   -0.1966   -0.6283
         0         0         0         0         0         0         0         0    0.6283         0


B =

  1.0e+007 *

    6.2832
         0
         0
         0
         0
         0
         0
         0
         0
         0


C =

         0         0         0         0         0         0         0         0         0    1.0000


D =

     0

下がシミュレーション波形です。DACの1ビット出力DAC_OUTがPPM変調されている様子がわかります。これをフィルタに通すと、filt_out1,filt_out2になります。2次と10次の違いはあまりわかりませんが、とりあえず、10次の方が次数が大きい分位相Delayが大きいことがわかります。


      
      

最後は、VPI ソースリストです。


// Mar.19.2004 www.sugawara-systems.com
// Linear System Simulator on Verilog2001 VPI
// Apr.18.2005 Bug fix address to only 1 output, change output port0
// 
#include "runge_kutta.h"
#include "math.h"
# include  "..\..\vpi\vpi\vpi_user.h"
#include <cassert>
#include <map>
#include <string>

extern int reset_lexor(const char * path);
extern bool parse_mat_data();
extern vector< vector<double*> *> *doubles_types;

//static runge_kutta *gloval_ptr=0;
static map <string , runge_kutta*> file_map;
runge_kutta::runge_kutta()
{
        doubles_list=0;
        continue_runge_kutta=false;
        old_time=0;
        zh=0;
        mode=INITIAL;
} 

runge_kutta::~runge_kutta()
{

}

bool runge_kutta::file_read(string file_name)
{
  bool success=reset_lexor(file_name.c_str());
           if(success){
                   success=parse_mat_data();
                   if (success) doubles_list=doubles_types;
                           else {
                                        vpi_mcd_printf(1,"$runge_kutta: can not read coefficient file.\n");
                           }    
           }else {
                        vpi_mcd_printf(1,"$runge_kutta:can not open coefficient file.\n");
           }    
           return success;
        
        
}

bool runge_kutta::check_and_dump_double_list()
{
        vpi_mcd_printf(1,"coefficient file has read.\n");
        rows=doubles_list->size();
        assert(rows);
        columns= (*doubles_list)[0]->size();
        bool error=false;
        if (rows ==columns){
                vpi_mcd_printf(1,"Matrix is [%d][%d]\n",rows,columns);
                for (unsigned i=0; i< rows;i++) {
                        if ((*doubles_list)[i]->size() !=columns) error=true; 
                        vector <double> a;
                        for (unsigned m=0; m < (*doubles_list)[i]->size();m++){
                                double temp=*(*(*doubles_list)[i])[m];
                                a.push_back(temp);
                                vpi_mcd_printf(1,"A Matrix[%d][%d]=%f \n",i,m,temp);
                        }
                        A.push_back(a);
                }
                if (columns !=rows) error =true;
                if (!error) {
             //Initialize array          
                        for (unsigned i=0; i<rows;i++){
                                zx.push_back(0.0);
                                zdx.push_back(0.0);
                                zq.push_back(0.0);
                        }
                        for (unsigned i=0; i< rows;i++) {
                                if (i==0)       B.push_back(1.0);//Jan.5.2005
                                else            B.push_back(0.0);//Jan.5.2005
                                if (i==rows-1) C.push_back(1.0);//Jan.5.2005
                                else               C.push_back(0.0);//Jan.5.2005
                                
                        }
                        D=0.0;//Jan.5.2005
                }else {
                        vpi_mcd_printf(1,"$runge_kutta:can not read coefficient file.\n");
                }
                return !error;
        }else { 
                // x=A  nxn   B nx1 
                // y=C  1xn   D 1x1 
                // n+n+1+1
                if (rows ==2*columns+2){
                        //vpi_mcd_printf(1,"Matrix is/*行列は*/[%d][%d]./*行列です。*/\n",rows,columns);
                        for (unsigned i=0; i< columns;i++) {
                                if ((*doubles_list)[i]->size() !=columns) error=true; 
                                vector <double> a;
                                for (unsigned m=0; m < columns;m++){
                                        double temp=*(*(*doubles_list)[i])[m];
                                        a.push_back(temp);
                                        vpi_mcd_printf(1,"A Matrix[%d][%d]=%f \n",i,m,temp);
                                }
                                A.push_back(a);
                        }
                        //B
                        for (unsigned i=0; i< columns;i++) {
                                double temp=*(*(*doubles_list)[i+columns])[0];
                                vpi_mcd_printf(1,"B Matrix[%d][0]=%f \n",i,temp);       
                                B.push_back(temp);
                                
                        }
                        //C
                        for (unsigned i=0; i< columns;i++) {
                                double temp=*(*(*doubles_list)[2*columns])[i];
                                vpi_mcd_printf(1,"C Matrix[0][%d]=%f \n",i,temp);       
                                C.push_back(temp);
                        }
                        //D
                        D=*(*(*doubles_list)[2*columns+1])[0];
                        vpi_mcd_printf(1,"D Matrix[0][0]=%f \n",D);     

                        
                        if (!error) {
                //Initialize array               
                                for (unsigned i=0; i< columns;i++){
                                        zx.push_back(0.0);
                                        zdx.push_back(0.0);
                                        zq.push_back(0.0);
                                }
                        }else {
                                vpi_mcd_printf(1,"$runge_kutta:can not read coefficient.\n");
                        }
                        return !error;
                }else {
                                vpi_mcd_printf(1,"$runge_kutta:format of coefficient file should be (row ==2*coulumn+2).\n");
                }
        }               
}
static vpiHandle get_module_handle(vpiHandle call_handle)
{
                vpiHandle obj=call_handle;
      while (vpi_get(vpiType, obj) != vpiModule) {
            obj = vpi_handle(vpiScope, obj);
          }

      return obj;
}
void runge_kutta::get_delta_time(vpiHandle call_handle)
{
          s_vpi_time  now;
      now.type=vpiScaledRealTime;
      vpi_get_time(0, &now);

          double  current_time=now.real;
         

      now.type = vpiSimTime;
      vpi_get_time(0, &now);

      int units = vpi_get(vpiTimeUnit,get_module_handle(call_handle));
                  if (old_time !=current_time){
                                  double scale=pow((double)10,(double)units);//キャストがないと駄目
                          zh=(current_time-old_time)*scale;//
                                  old_time=current_time;
                          }
}

void runge_kutta::do_integ()

{
    
    double dk,dy;
    vector <double > inputs;
    s_vpi_value value;
        value.format=vpiRealVal;
        
  //  unsigned no_of_int=rows;
    unsigned no_of_int=columns;//Dec.20.2004;

//現在のVerilogのインプットアレーを読み出す。 
    for (unsigned i=0; i< no_of_int;i++){//Read Current Input Array from Verilog
        vpiHandle word_index = vpi_handle_by_index(m_read_item,read_mem_left_address+i); 
                vpi_get_value(word_index, &value);
                double temp=value.value.real;
                inputs.push_back(temp);
        }
        
    if (zh==0.0) return;//zhが未設定ならなにもしない。
//Matrix 乗算
        for (unsigned mmode=1; mmode <=4;mmode++){
                
                for (unsigned ix=0;ix< no_of_int;ix++){//行
                        double sum=0;
                        for (unsigned iy=0; iy< no_of_int;iy++) {//列
                
                                //double coeff=*(*(*doubles_list)[ix])[iy];
                                double coeff=A[ix][iy];//Dec.20.2004
                                sum +=zx[iy]*coeff;
                        }
                        double temp=inputs[ix];
                        //zdx[ix]=sum+inputs[ix];//行
                        zdx[ix]=sum+B[ix]*inputs[ix];//Dec.20.2004
                } 
                

                
//積分処理
        for (unsigned m=0;m<no_of_int ;m++ ){

            switch (mmode) {
                case 1 :
                          
                        dk=zdx[m]*zh;
                        dy=0.5*(dk-2.0*zq[m]);
                        zx[m]=zx[m]+dy;
                        zq[m]=zq[m]+3.0*dy-0.5*dk;

                        break;
                case 2 :
                        dk=zh*zdx[m];
                        dy=cc1*(dk-zq[m]);
                        zx[m]=zx[m]+dy;
                        zq[m]=zq[m]+3.0*dy-cc1*dk;
                        break;
                case 3 :
                        dk=zh*zdx[m];
                        dy=cc2*(dk-zq[m]);
                        zx[m]=zx[m]+dy;
                        zq[m]=zq[m]+3.0*dy-cc2*dk;
                        break;
               case 4 :
                        dk=zdx[m]*zh;
                        dy=(dk-2.0*zq[m])/6.0;
                        zx[m]=zx[m]+dy;
                        zq[m]=zq[m]+3.0*dy-dk/2.0;

                        break;
               }     /* endswitch */
        }//end for
      }//end for 
//結果をVerilogのアレーに書き込む
//Apr.18.2005
vpiHandle word_index = vpi_handle_by_index(m_write_item,write_mem_left_address+0);//Apr.18.2005 no_of_int-1); 
 //Write the result to Write Array in Verilog
double temp=0;
for (unsigned i=0; i< no_of_int;i++){//Read Current Input Array from Verilog
        
                //double temp=zx[i];
                 temp +=zx[i]*C[i]+inputs[i]*D;//Dec.20.2004
        
  }
value.value.real=temp;
vpi_put_value(word_index, &value,0, vpiNoDelay);//Apr.18.2005
/*
  for (unsigned i=0; i< no_of_int;i++){//Read Current Input Array from Verilog
        vpiHandle word_index = vpi_handle_by_index(m_write_item,write_mem_left_address+i); 
                //double temp=zx[i];
                  double temp=zx[i]*C[i]+inputs[i]*D;//Dec.20.2004
                value.value.real=temp;
                vpi_put_value(word_index, &value,0, vpiNoDelay);
  }
  */    
           
}

//Verilog IFに接続されているアレー、その他のチェック
//一度だけ読み出しアレーについては、ハンドル情報をSaveしておく
bool runge_kutta::get_property_on_verilog_if()
{
     
 
      s_vpi_value value;
      
 
     
      vpiHandle left_range;
      vpiHandle right_range;




      if (m_read_item == 0 || m_write_item==0) {
                vpi_mcd_printf(1,"$runge_kutta: invalid parameter.\n");
            return false;
      }


      if (vpi_get(vpiType, m_read_item) != vpiMemory) {
                vpi_mcd_printf(1,"$runge_kutta:3rd parameter should be real array.\n");
          
            return false;
      }
          if (vpi_get(vpiType, m_write_item) != vpiMemory) {
                vpi_mcd_printf(1,"$runge_kutta:4th parameter should be real array.\n");
        
            return false;
      }
      
     
     
          value.format = vpiIntVal;
      vpi_get_value(mode_item, &value);
          if (value.value.integer !=(int)(INITIAL)){
                vpi_mcd_printf(1,"$runge_kutta:First call should be mode0.\n");
        
            return false;
                
          }             



//Read Memory Input for runge_kutta      
      left_range = vpi_handle(vpiLeftRange, m_read_item);
      value.format = vpiIntVal;
      vpi_get_value(left_range, &value);
      read_mem_left_address = value.value.integer;

      right_range = vpi_handle(vpiRightRange, m_read_item);
      value.format = vpiIntVal;
      vpi_get_value(right_range, &value);
      read_mem_right_address = value.value.integer;


           if (read_mem_left_address > read_mem_right_address){
                vpi_mcd_printf(1,"$runge_kutta: memory range must be Left<Right\n");
        
            return false;
          
          }     
          if ((read_mem_right_address-read_mem_left_address+1) < columns){//Dec.10.2004
                vpi_mcd_printf(1,"$runge_kutta: invalid memory range.\n");
        
            return false;
          
          }     
          
//Write Memory Output for runge_kutta     
      left_range = vpi_handle(vpiLeftRange, m_write_item);
      value.format = vpiIntVal;
      vpi_get_value(left_range, &value);
      write_mem_left_address = value.value.integer;

      right_range = vpi_handle(vpiRightRange, m_write_item);
      value.format = vpiIntVal;
      vpi_get_value(right_range, &value);
      write_mem_right_address = value.value.integer;


           if (write_mem_left_address > write_mem_right_address){
                vpi_mcd_printf(1,"$runge_kutta:memory range should be  LEFT<RIGHT.\n");
                    return false;
          
          }     
          if ((write_mem_right_address-write_mem_left_address+1) < columns){//Dec.20.2004
                vpi_mcd_printf(1,"$runge_kutta:invalid memory range.\n");
                    return false;
          
          }     
      vpiHandle words = vpi_iterate(vpiMemoryWord, m_read_item);
      vpiHandle item = vpi_scan(words);
      unsigned wwid = vpi_get(vpiSize, item);
          if (wwid !=64){ 
                  vpi_mcd_printf(1,"$runge_kutta:memory should be real type of array.\n");
              return false;
          }
                
          words = vpi_iterate(vpiMemoryWord, m_write_item);
      item = vpi_scan(words);
      wwid = vpi_get(vpiSize, item);
          if (wwid !=64){ 
                  vpi_mcd_printf(1,"$runge_kutta:memory should be real type of array.\n");
              return false;
          }     
     
//Finally,Initial Check all done.
         return true;
}       

//毎クロック呼び出される。
static int sys_runge_kutta_calltf(char*name)
{
        runge_kutta* local_ptr=0;
        s_vpi_value value;

        vpiHandle sys = vpi_handle(vpiSysTfCall, 0);
        vpiHandle argv = vpi_iterate(vpiArgument, sys);
    vpiHandle file_handle=vpi_scan(argv);//First Parameter

//First parameter       
        if (!file_handle) {
                  vpi_mcd_printf(1,"$runge_kutta:missing parameter.\n");
                  //Mar.25.2004 vpi_free_object(argv);
                  return 0;
        }       
        //ファイル名取得チェック
        if (vpi_get(vpiType, file_handle)!=vpiConstant &&
        vpi_get(vpiType, file_handle)!=vpiParameter){
                  vpi_mcd_printf(1,"$runge_kutta:file name should be string type.\n");
                  vpi_free_object(argv);
                  return 0;
        }
        if (vpi_get(vpiConstType,file_handle) != vpiStringConst) {
                  vpi_mcd_printf(1,"$runge_kutta:file name should be string type.\n");
                  vpi_free_object(argv);
                  return 0;
        }
        //ファイル名取得        
        value.format = vpiStringVal;
        vpi_get_value(file_handle, &value);
        string file_name=value.value.str;

//2nd parameter         
         vpiHandle mode_item = vpi_scan(argv);
   if (mode_item ==0){
                        vpi_mcd_printf(1,"$runge_kutta:missing mode parameter.\n");
                //Mar.25.2004vpi_free_object(argv);
                return 0;
         }

        
        map<string ,runge_kutta* > ::const_iterator im=file_map.find(file_name);
        if (im ==file_map.end()){//新しいファイル処理
                local_ptr=new runge_kutta();
                file_map[file_name]=local_ptr;
                bool success=local_ptr->file_read(file_name);
                if (success) { 
                        bool success=local_ptr->check_and_dump_double_list();
                        if (success){
                                //3rd/4th parameter      
                                local_ptr->m_read_item=vpi_scan(argv);//READ ARRAY
                        if(! local_ptr->m_read_item) return 0;//argv はdelete は、Verilogでデリート済み
                                local_ptr->m_write_item=vpi_scan(argv);//WRITE ARRAY
                                if(! local_ptr->m_write_item) return 0;//argv はdelete は、Verilogでデリート済み
                                local_ptr->mode_item=mode_item;
                                if(local_ptr->get_property_on_verilog_if()) local_ptr->go_on();
                        } 
                }
        }else {//既にMapに登録されている。
                local_ptr=file_map[file_name];  
        }               

                                
        if(local_ptr->go_()){//初期化でエラーなかったらModeパラメータで制御
  
   
                 local_ptr->get_delta_time(sys);

             
                 s_vpi_value value;

                 value.format = vpiIntVal;//モードパラメータを取得
         vpi_get_value(mode_item, &value);
                 unsigned mode=value.value.integer;
                 if (mode==0) ;
                 else if(mode==1) local_ptr->do_integ();                                         
                 
                 
        }
        vpi_free_object(argv);
        return 0;
        
}

//ユーザシステムタスクを登録
extern  void sys_ruge_kutta_register()
{
      s_vpi_systf_data tf_data;

      tf_data.type      = vpiSysTask;
      tf_data.tfname    = "$runge_kutta";
      tf_data.calltf    = sys_runge_kutta_calltf;
      tf_data.compiletf = 0;
      tf_data.sizetf    = 0;
      tf_data.user_data = 0;
      vpi_register_systf(&tf_data);

     

}