電気回路/HDL/Verilator で DPI-C のバックアップソース(No.2)

更新

[[公開メモ]]

#contents

* DPI-C とは [#ze1b51ac]

SystemVerilog の機能で、SystemVerilog で書かれたテストベンチから
C あるいは C++ で書かれたルーチンを呼び出したり、その呼び出された
C あるいは C++ で書かれたルーチンから SystemVerilog の task や
function を呼び出したりするための規格です。

ModelSim XE を使ってやってみた結果は
[[電気回路/HDL/ModelSim XE を使った SystemVerilog DPI-C テスト]] 
にあります。

今回はこれをフリーのツールである Verilator でやろうという話。

* 関連記事 [#ab496977]

#ls2(電気回路/HDL/Verilator);
#ls2(電気回路/HDL/System);

* SystemVerilog のソース [#o22e62cc]

dpic_test.sv
 LANG:verilog(linenumber)
 `timescale 1ns / 1ps
 
 // トップモジュール
 module dpic_test;
 
    // verilog で書いた task
    task verilog_task(input int i, output int o);
        $display("Hello from verilog_task(%d)", i);
        o = 2 * i;
    endtask
 
    // C から使うためにエクスポートする
    export "DPI-C" task verilog_task;
 
    // C で書かれた関数をインポートする
    import "DPI-C" context task c_task(input int i, output int o);
 
    // C で書かれた関数を呼び出す
    int ret;
    initial begin
        c_task(1, ret);  // c_task は verilog_task を呼び出す
        $display("ret = %d", ret);
        $finish;
    end
 
 endmodule

* C++ モードでやってみる [#b03e129e]

 LANG:console
 $ ls
 dpic_test.sv
 $ verilator -cc dpic_test.sv
 $ ls
 dpic_test.sv  obj_dir/
 $ ls obj_dir/
 Vdpic_test.cpp  Vdpic_test__Dpi.cpp   Vdpic_test__Syms.h        Vdpic_test_classes.mk
 Vdpic_test.h    Vdpic_test__Dpi.h     Vdpic_test__ver.d
 Vdpic_test.mk   Vdpic_test__Syms.cpp  Vdpic_test__verFiles.dat
 $ less Vdpic_test__Dpi.h

としてみた結果は以下の通り。

obj_dir/Vdpic_test__Dpi.h
 LANG:cpp(linenumber)
 // Verilated -*- C++ -*-
 // DESCRIPTION: Verilator output: Prototypes for DPI import and export functions.
 //
 // Verilator includes this file in all generated .cpp files that use DPI functions.
 // Manually include this file where DPI .c import functions are declared to insure
 // the C functions match the expectations of the DPI imports.
 
 #ifdef __cplusplus
 extern "C" {
 #endif
     
     
     // DPI EXPORTS
     // DPI Export at dpic_test.sv:7
     extern void verilog_task (int i, int* o);
     
     // DPI IMPORTS
     // DPI Import at dpic_test.sv:16
     extern int c_task (int i, int* o);
     
 #ifdef __cplusplus
 }
 #endif

うまく行っているようです。

あとは c_task を定義して、その中から verilog_task を呼び出せばいいはず。。。

あれ?

verilog_task がモジュールのメンバー関数ではなく静的関数になっているのはなぜでしょう?

obj_dir/Vdpic_test__Dpi.cpp
 LANG:cpp(linenumber)
 // Verilated -*- C++ -*-
 // DESCRIPTION: Verilator output: Implementation of DPI export functions.
 //
 // Verilator compiles this file in when DPI functions are used.
 // If you have multiple Verilated designs with the same DPI exported
 // function names, you will get multiple definition link errors from here.
 // This is an unfortunate result of the DPI specification.
 // To solve this, either
 //    1. Call Vdpic_test::{export_function} instead,
 //       and do not even bother to compile this file
 // or 2. Compile all __Dpi.cpp files in the same compiler run,
 //       and #ifdefs already inserted here will sort everything out.
 
 #include "Vdpic_test__Dpi.h"
 #include "Vdpic_test.h"
 
 #ifndef _VL_DPIDECL_verilog_task
 #define _VL_DPIDECL_verilog_task
 void verilog_task (int i, int* o) {
     // DPI Export at dpic_test.sv:7
     return Vdpic_test::verilog_task(i, o);
 }
 #endif

あら、Vdpic_test::verilog_task も静的関数なんですね。

モジュール内の信号を参照するような task だったらどうなるのか、
興味のわくところです。

** 使ってみる [#h344b727]

上記の dpic_test を動かしてみます。

dpic_test_main.cpp
 LANG:cpp(linenumber)
 #include <verilated.h>          // Defines common routines
 #include "Vdpic_test.h"
 #include "Vdpic_test__Dpi.h"
 #include "stdio.h"
 
 // Vdpic_test からこのルーチンがコールバックされる
 extern "C" int c_task (int i, int* o)
 {
     printf("Hello from c_task(%d)\n", i);
     verilog_task(i, o); /* さらに verilog の task をコールバックする */
     return(0); /* Return success (required by tasks) */
 }
 
 unsigned int main_time = 0;     // Current simulation time
 
 double sc_time_stamp () {       // Called by $time in Verilog
     return main_time;
 }
 
 int main(int argc, const char *argv[])
 {
     Verilated::commandArgs(argc, argv);   // Remember args
 
     Vdpic_test *top = new Vdpic_test();   // Create instance
 
     while (!Verilated::gotFinish()) {
 
         top->eval();                      // Evaluate model
 
         main_time++;                      // Time passes...
 
     }
 
     top->final();               // Done simulating
 
     //    // (Though this example doesn't get here)
 }

を作っておいて、

 LANG:console
 $ verilator -cc dpic_test.sv --exe dpic_test_main.cpp
 $ cd obj_dir
 $ make -f Vdpic_test.mk
 $ ./Vdpic_test.exe
 Hello from c_task(1)
 Hello from verilog_task(          1)
 ret =           1
 - dpic_test.sv:23: Verilog $finish

Verilator から System Verilog の dpi-c を利用できそうなことが確認できました。

* task の結果が module 内の信号に依存する場合 [#k8de5d35]

上での疑問を解消しておきます。疑問に思っているのは次の点です:

- System Verilog 内の verilog_task はモジュール dpic_test に属しているため、
内部の信号に自由にアクセスできます
- ところが、これが dpi-c でエクスポートされると C++ 側からは
普通のグローバルな関数になっています。
- すなわち、C++ で定義された verilog_task はモジュールに属していない
- 複数の dpic_test モジュールが実装されているときに、どの信号を
参照したら良いのかを正しく判別できるんだろうか?

この点を確認するため、上記のコードをちょっと変えて試してみました。

dpic_test.sv
 LANG:verilog(linenumber)
 `timescale 1ns / 1ps
 
 // トップモジュール
 module dpic_test(input int i);
 
    int calculated = 2 * i;
 
    // verilog で書いた task
    task verilog_task(input int i, output int o);
        $display("Hello from verilog_task(%d)", i);
        $display("Value of calculated (%d)", calculated);
        o = 2 * i + calculated;
    endtask
 
    // C から使うためにエクスポートする
    export "DPI-C" task verilog_task;
 
    // C で書かれた関数をインポートする
    import "DPI-C" context task c_task(input int i, output int o);
 
    // C で書かれた関数を呼び出す
    int ret;
    initial begin
        c_task(1, ret);  // c_task は verilog_task を呼び出す
        $display("ret = %d", ret);
        $finish;
    end
 
 endmodule

dpic_test_main.cpp
 LANG:cpp(linenumber)
 #include <verilated.h>          // Defines common routines
 #include "Vdpic_test.h"
 #include "Vdpic_test__Dpi.h"
 #include "stdio.h"
 
 extern "C" int c_task (int i, int* o)
 {
     printf("Hello from c_task(%d)\n", i);
     verilog_task(i, o); /* Call back into Verilog */
     return(0); /* Return success (required by tasks) */
 }
 
 unsigned int main_time = 0;     // Current simulation time
 
 double sc_time_stamp () {       // Called by $time in Verilog
     return main_time;
 }
 
 int main(int argc, const char *argv[])
 {
     Verilated::commandArgs(argc, argv);   // Remember args
 
     Vdpic_test *obj1 = new Vdpic_test();   // Create instance
     Vdpic_test *obj2 = new Vdpic_test();   // Create instance
     obj1->i = 1;
     obj2->i = 2;
 
     while (!Verilated::gotFinish()) {
 
         obj1->eval();                     // Evaluate model
         obj2->eval();                     // Evaluate model
 
         main_time++;                      // Time passes...
 
     }
 
     obj1->final();               // Done simulating
     obj2->final();               // Done simulating
 
     //    // (Though this example doesn't get here)
 }

としてみました。

 LANG:console
 $ verilator -cc dpic_test.sv -exe dpic_test_main.cpp
 $ obj_dir/
 $ make -f Vdpic_test.mk
 $ ./Vdpic_test.exe
 Hello from c_task(1)
 Hello from verilog_task(          1)
 Value of calculated (          2)
 ret =           4
 - dpic_test.sv:26: Verilog $finish
 Hello from c_task(1)
 Hello from verilog_task(          1)
 Value of calculated (          4)
 ret =           6
 - dpic_test.sv:26: Verilog $finish
 - dpic_test.sv:26: Second verilog $finish, exiting

驚くべき事に、正しく動いています。

これは、DPI-C 経由で System Verilog から c_task を呼び出した際に、
そのスコープ情報をちゃんと保存してあって、
verilog_task からそのスコープ情報を参照することにより、
どのモジュールの信号を読むか判断しているおかげのようです。

Vdpic_test.cpp
 LANG:cpp
 void Vdpic_test::verilog_task(int i, int* o) {
     VL_DEBUG_IF(VL_PRINTF("    Vdpic_test::verilog_task\n"); );
     // Variables
     VL_SIG(i__Vcvt,31,0);
     VL_SIG(o__Vcvt,31,0);
     // Body
     static int __Vfuncnum = -1;
     if (VL_UNLIKELY(__Vfuncnum==-1)) { __Vfuncnum = Verilated::exportFuncNum("verilog_task"); }
     const VerilatedScope* __Vscopep = Verilated::dpiScope();
     Vdpic_test__Vcb_verilog_task_t __Vcb = (Vdpic_test__Vcb_verilog_task_t)__Vscopep->exportFind(__Vfuncnum);
     i__Vcvt = i;
     (*__Vcb)((Vdpic_test__Syms*)(__Vscopep->symsp()), i__Vcvt, o__Vcvt);
     *o = o__Vcvt;
 }

Verilated::dpiScope というのがそれですね。

ややこしいことをしているものです・・・

とはいえ、これは Verilator に限った事じゃないですね。
DPI-C の規格からすると当然の実装方法と思えてきました。

あと、当然ですが ある Verilog モジュールから export された関数を
別のモジュールに inport された C の関数から呼び出すとおかしなことになりますね。

DPI-C を使って C から verilog 関数を呼び出す使い方は、
モジュール内に閉じた利用に限られるという制約があることを理解できます。

* 問題は・・・ [#b0591aaa]

Verilator を使う限り、System Verilog 内で #123 や @(posedge signal) 
などの記法で自由に時刻を進めることができないので、
System Verilog をテストベンチのトップモジュールとするわけに
行かないのがつらいですね。

* コメント [#xfe6af9c]

#article_kcaptcha

Counter: 16285 (from 2010/06/03), today: 2, yesterday: 0