DPI-C で C言語と SystemVerilog をインターフェース【Windows無償ツールで実現】

#目次
* [1. はじめに](#1はじめに)

* [2.環境](#2環境)
* [3.サンプル・コード](#3サンプルコード)
 * [3-1. ① SystemVerilog コードからC言語を呼び出す](#3-1--systemverilog-コードからc言語を呼び出す)
 *  - [3-1-1. SystemVerilog コード](#3-1-1systemverilog-コード)
 *  - [3-1-2. C コード](#3-1-2--c-コード)
 *  - [3-1-3. Tcl スクリプト](#3-1-3-tcl-スクリプト)
 *  - [3-1-4. 実行手順](#3-1-4-実行手順)
 *  - [3-1-8 シミュレーション結果](#3-1-8-シミュレーション結果)
 * [3-2. ② C 言語からSystemVerilogコード(ファンクションを使用)を呼び出す](#3-2--c-言語からsystemverilogコードファンクションを使用を呼び出す)
 *  - [3-2-1. C コード](#3-2-1-c-コード)
 *  - [3-2-2. SystemVerilog コード](#3-2-2-systemverilog-コード)
 *  - [3-2-3. Tclスクリプト](#3-2-3-tclスクリプト)
 *  - [3-2-4. 実行手順](#3-2-4-実行手順)
 *  - [3-2-8 シミュレーション結果](#3-2-8-シミュレーション結果)
 * [3-3. ③ C 言語からSystemVerilogコード(タスクを使用)を呼び出す](#3-3--c-言語からsystemverilogコードタスクを使用を呼び出す)
 *  - [3-3-1. C コード](#3-3-1-c-コード)
 *  - [3-3-2. SystemVerilog コード](#3-3-2-systemverilog-コード)
 *  - [3-3-3. Tcl スクリプト](#3-3-3-tcl-スクリプト)
 *  - [3-3-4. 実行手順](#3-3-4-実行手順)
 *  - [3-3-8 シミュレーション結果](#3-3-8-シミュレーション結果)
* [4. 【考察】 DPI-CとCコンパイラとの関係](#4-考察-dpi-cとcコンパイラとの関係)
* [5. まとめ](#5-まとめ)
* [6. リファレンス](#6-リファレンス)

<a id="anchor1"></a>
#1.はじめに
筆者は、Verilog HDL を拡張したSysteVerilogというHDL言語に関心を持ち始めています。<br>その中でもC言語と連携を行う DPI-C というインターフェースに着目しています。
筆者の場合、Windows環境の方に慣れ親しんでいる為、Linux環境には幾分の敷居の高さ<br>を感じています。
そのためWindowsの環境で、且つ、費用的にも敷居の低い無償のツールで動かせるか<br>調査を行い、インテル® 社が提供するFPGA開発ツールQuartus® Primeに同梱されて<br>いる無償のシミュレーション・ツールだけで実現を試みました。
とりあえず「動かす」ことに重点を置き、稚拙ながら簡単な自作コードを用いて、<br>言語間でインターフェースが行われている「動き」について投稿してみました。

:::note info
本稿の執筆にあたり、参考にした情報も文中に掲載しました。
:::


<a id="anchor2"></a>
#2.環境									
**ModelSim® インテル® FPGA Starter Edition  10.5b	（Windows版）** 
![kom_0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/ffc06cc8-e09f-7fd5-6a60-d77fca6b7cb4.png)

* このシミュレーション・ツールは、Quartus® Prime Lite Edition （無償版）の<br>バージョン17.1 ～19.1に同梱されています。
* Quartus® Prime Standard Edition （有償版）でも同梱されています。

<a id="anchor3"></a>
#3.サンプル・コード							
本稿で使用するサンプル・コードは、次の①～③を目的としています。
	
* **①	SystemVerilog コードからC言語を呼び出す**
    * 応用例：ハード・ソフトの協調検証
        * ハードウェアで受信したデータをホスト側のソフトウェアに渡して<br>高度なアルゴリズムでエラー・チェック
    
* **②	C 言語からSystemVerilog コード(ファンクションを使用)を呼び出す**
    * 応用例：ハードウェア・アクセラレーター
        * ホスト側のソフトウェアで処理時間を要する機能をハードウェアで処理
* **③	C 言語からSystemVerilog コード(タスクを使用)を呼び出す**
    * 応用例：ハード・ソフトの協調検証
        * ハードウェアのテスト・プログラムをC言語で記述

:::note info         
上記①～③では、SystemVerilogとC言語の他に Tcl スクリプトも使用します。	
:::

<a id="anchor4"></a>
##3-1. ① SystemVerilog コードからC言語を呼び出す			
SystemVerilog コードからC言語を呼び出す場合、SytemVerilogコード内に`import "DPI-C"` <br>を記述し、その後に、C言語の関数名と、その関数名に関連する情報（型や引数）を指定し<br>ます。<br>SytemVerilogコード内に記述するので、SystemVerilogの文法に従って関数を指定します。<br>`import`の場合、指定する関数は、`pure` と`context` に分類されます。<br>サンプル・コードでは、C言語からSystemVerilogコードに参照するような記述では無いので、<br>`pure` の方を使用しています。

<a id="anchor5"></a>
###3-1-1.　SystemVerilog コード							

`import "DPI-C"` 後に、`pure function int add(input int value_1, int value_2)`<br>という二つの引数を加算する関数を指定しています。<br>関数の実体はC言語側にあるので、加算部分はC言語側で記述しています。<br>引数に値を代入して、`initial`文の中で20ns後に関数が呼び出され、C言語側で処理した<br>加算結果が、戻り値として`Result`に代入しています。

						

```verilog:tb_dpi.sv
`timescale 1 ns/ 1 ps

module tb_dpi;

  import "DPI-C" pure function int add(input int value_1, int value_2);
  int Result_val = 0;
  int val_a = 13;
  int val_b = 21;

  
  initial
  begin
    $display("--- SystemVerilog to C function using 'DPI-C' ---");
    #20
	$display("Call to C function from System Verilog");
    Result_val = add(val_a, val_b);
	#20
    $display("Get Value from C function: Result_val = %d", Result_val);
	$display("Finish");
    #1000
	$finish;
  end
  
endmodule

```

個人的には、`import`という意味合いから、SystemVerilogコードからC言語に処理を分岐する<br>とよりも、SystemVerilogコードの中に、C言語の関数を組み込まれているような印象を持って<br>います。

- - - 
参考：<br>・ModelSim DEサンプル・コード
- - - 

<a id="anchor6"></a>
###3-1-2.  C コード								
本コードでは、加算処理を行い、その結果をメッセージに表示後、呼び出し側に戻り値として<br>返しています。<br> ヘッダ・ファイルもC言語で標準的に用意されているものをインクルードしています。

```c:CFunc1.c
#include <stdio.h>
#include <stdlib.h>

extern int add(int val_1, int val_2) {
  int accm = 0;
  accm = val_1 + val_2;

  printf(">>> Run C function result = %d\n", accm);
  return accm;
}

```

<a id="anchor7"></a>
###3-1-3. Tcl スクリプト								
ModelSimで利用可能なコマンドを記述しています。
もっと適切な書き方はあると感じていますが、深追いはせずに、とりあえず「動かす」ことに<br>重点を置きました。

```tcl:dpic_sv2c.tcl
vlib work
vlog -sv -dpiheader tb_dpi.sv CFunc1.c
vlog -sv tb_dpi.sv
vsim -gui work.tb_dpi
add wave -r /*
```
- - - 
参考：<br>・ModelSim DEサンプル・コード
- - - 

<a id="anchor8"></a>
###3-1-4. 実行手順									
									
###3-1-5. Modelsim の起動							
1)	半角英数のPATHによる任意のディレクトリに、以下のファイルを格納します。
<d1>
<dd>tb_dpi.sv</dd>
<dd>CFunc1.c</dd>
<dd>dpic_sv2c.tcl</dd>
</d1>

:::note info
本稿に掲載されている該当のソース・コードをコピー&ペーストして、<br>上記のファイル名（および拡張子）で保存します。
:::
								
2)	ModelSim インテル® FPGA Starter Edition を起動します。									
3)	Transcript ウィンドウで次のコマンドをタイプして、上記 1) のディレクトリに移動します。									
　　【例】　cd C:/work/test_sv2c						

カレント・ディレクトリを移動後、ls を実行して、上記のファイルがあることを確認します。	

![kom_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/748d6386-3708-be82-aa4b-398786a6320a.png)

###3-1-6. Tclスクリプトの実行								
 次のコマンドで Tclスクリプトを実行します。
<pre>do ./dpic_sv2c.tcl</pre>					
<br>Tcl スクリプトを実行後、Wave ウィンドウ内にポートや信号の全てが追加されています。
![kom_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/d7012f5a-4098-8908-79bb-cc9da5ab2bbb.png)

###3-1-7. Modelsim によるシミュレーション						
Wave ウィンドウ内にカーソルを置いて、右クリックより Add -> New Divider を選択します。
![kom_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/39218b5a-7bd0-6f7a-93bc-86c9873596a0.png)

次のように、SystemVerilog のモジュールと、C言語の関数を区分けします。
![kom_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/41a2a51f-f0df-34b6-7470-5468c67be2b7.png)

Simulation メニューから Run -> Run -All を選択します。
![kom_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/bf38371d-88f6-a77e-80b1-504db13493e7.png)

次のような画面が起動したら、[ いいえ(N) ] をクリックします。
![kom_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/e0d18f9c-bbc8-5be3-2230-ea1878131716.png)

Wave ウィンドウに波形が表示されます。
![kom7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/b90f87be-89dd-e3a2-f105-d6a1e2fa6ef6.png)

[ Zoom Full (F) ] アイコンをクリックします。
![kom_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/ce68e109-dffc-f445-16bb-388c77b86567.png)

波形の全体像が現れてきます。
![kom_9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/fcbd5278-ccb0-91ff-2999-7149de417390.png)

<a id="anchor9"></a>
###3-1-8. シミュレーション結果									
変化点をズームアップすると、SystemVerilog 側で用意した、引数 13 と 21 とが、20ns 地点でC の関数に渡された後、即座に加算結果が返される動きを把握できます。	
<img width="700" alt="kom_10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/09185fc3-0794-0927-1a34-decaf6151743.png">


このときのメッセージでも、C言語側の関数を呼び出して、加算結果 34  が返ってきたことを示しています。
![kom_11_up2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/f53f6fa6-06a0-5414-eab2-2ae90d181b21.png)


- - - 
参考：<br>・ModelSim DEサンプル・コード
・[DPI-CでSystemVerilogからC言語の関数呼び出し](https://happyfield.hatenablog.com/entry/2015/11/22/025736)
・[ModelSimASE+clangでSystemVerilog DPI-Cをシミュレーションする](https://qiita.com/ginnyu_tei/items/c6095df17bcbbcec8541)	
- - - 

<a id="anchor10"></a>
##3-2. ② C 言語からSystemVerilogコード(ファンクションを使用)を呼び出す		
CコードからSystemVerilog コードを呼び出す場合、SystemVerilogコード内に、<br>`export "DPI-C"` を記述します。`import` のようにSystemVerilog の中にC言語を取り込むの<br>とは逆の方向になるので、SystemVerilogのコードを外部に吐き出す意味合いで`export`が使われるようです。
`export "DPI-C"`の後には、C言語にエクスポートされるSystemVerilogの関数やタスクを指定します。
本サンプル・コードでは、SystemVerilogのファンクションをエクスポートするので、<br>`export "DPI-C"` の後ろに、ファンクション名と、それに関連した情報（型や引数）を使用しています。	
`export`の場合、`import`とは異なり、`pure`や `context`は不要になります。

<a id="anchor11"></a>
###3-2-1. C コード									
本コードは、C言語のmain関数では無く、`add_main()`というサブ関数になります。<br>また、その下には`add_func()`というサブ関数を定義しています。
ヘッダ・ファイルには、C言語側で標準的に用意されているライブラリに加えて、dpiheader.h<br>を明示的にインクルードして、DPI-C経由で処理を分岐できるようにしています。
サブ関数`add_func()`の引数に値を指定した後、サブ関数`add_func()`を呼び出して、<br>SystemVeriligで定義した`add_func` という`function`に加算の処理を委ねています。<br>SystemVerilog側からの加算結果は、戻り値として変数`result`に格納後、`printf()`を使用<br>してメッセージに表示できるように記述しています。
ここでは同時に、変数 `num` の値もメッセージに表示させています。<br>後術しますが、この値は`add_maion()`の引数として外部から指定されます。

```c:add_main.c	
#include <stdio.h>
#include <stdlib.h>
#include "dpiheader.h"

int add_func(int, int);

int add_main(int num){

int a = 35;
int b = 11;
int result;

result = add_func(a, b);
printf("************************************************************************\n");
printf("Mesage from C function\n");
printf(">>> Culcurate System Verilog function result = %d <- %d\n", result, num);
printf("************************************************************************\n");

return (0);
}
```
<a id="anchor12"></a>
###3-2-2. SystemVerilog コード						
本コードでは、sub_moduleというモジュール名を定義しています。実装を想定し、トップ階層よりも下位の階層にインスタンスされるサブ・モジュールを意識し、敢えてポート名を定義しています。
このモジュール内には、SystemVerilog の `function` として`add_func`を定義していますが、これがC言語側から呼び出される対象になります。SystemVerilog側から見れば、外部にエクスポートする対象となります。
`add_func`では 入力ポートとして`int` 型で宣言された`f_da`と `f_db` を仮引数とし、C言語側から与えられた引数と対応しています。

:::note info
仮引数の型をintから wireに切り替えば、assign 文で記述した接続に従い、<br>モジュール名の直後に指定した入力ポートda_iとdb_iに各々接続され、<br>function で記述した加算結果が出力ポートq_oと接続され、トップ階層と<br>接続する加算器として実装されます。
:::

:::note info
VerilogやSystemVerilogではfunctionは、組み合わせロジックの回路記述<br>に該当します。
:::

`f_da`と `f_db`を加算した結果が、`add_func`の戻り値となり、DPI-Cによって、C言語側に送られます。DPI-Cに処理をエクスポートする為、`export "DPI-C"` の後ろに `function add_func` の記述を追加しています。

また、本コードでは、`initial`文を使用して、シミュレーション波形で時間的な動きを視覚的に把握できるようにしています。
シミュレーション直後、つまり0nsで`num_val`に3を代入した後、20ns経過した後に、C言語側のサブ関数`add_main()`に引数3を与えて実行しています。組込みシステムのように、CPUのリセットを解除して、ROMからCコードをブートする動きに似ているかもしれません。
この処理によって、C言語側の`add_main()`が実行後、その中にあるサブ関数`add_func()`が実行されるフローがシミュレートされます。<br>
このフローは、結果的にSystemVerilogの`initial`文からC言語を呼び出す仕組みが無いと実現できないので、本コード内では、`import “DPI-C”`も併用しています。
SystemVerilogから呼び出されるC言語の`add_main()`の中では、サブ関数`add_func()`からSystemVerilogを参照するので、本コードでは`import “DPI-C”`の後ろに `context` を付加して、`int`型の`function` として`add_main` の呼び出せるように記述しています。

 

```verilog:sub_module.sv
`timescale 1ns/1ps

module sub_module(
	input wire	[31:0]	da_i,	// Input Data
	input wire	[31:0]	db_i,	// Input Data
	output wire	[31:0]	q_o	// Output Data
);

import "DPI-C" context function int add_main(input int num);
export "DPI-C" function add_func;

int num_val = 0;

// Function                                                   
function int add_func;
	input int f_da;
	input int f_db; 

	begin
		add_func = f_da + f_db;
	end
endfunction

// Connect
assign q_o  = add_func(da_i, db_i);

// Monitoring
	initial 
	begin
		num_val = 3;
		#20	
		add_main(num_val);
		#20 $finish;
	end

endmodule
```
- - - 
参考：<br>・ModelSim DEサンプル・コード
- - - 

<a id="anchor13"></a>
###3-2-3. Tclスクリプト
ModelSimで利用可能なコマンドを記述しています。
もっと適切な書き方はあると感じていますが、深追いはせずに、とりあえず「動かす」ことに重点を置きました。

```tcl:dpic_c2sv.tcl
vlib work
vlog -sv -dpiheader dpiheader.h sub_module.sv add_main.c -ccflags "-g"
vsim -c sub_module
add wave -r /*
```
- - - 
参考：<br>・ModelSim DEサンプル・コード
- - - 

<a id="anchor14"></a>
###3-2-4. 実行手順									

###3-2-5. Modelsim の起動
1)	半角英数のPATHによる任意のディレクトリに、以下のファイルを格納します。
<d1>
<dd>sub_module.sv</dd>
<dd>add_main.c</dd>
<dd>dpic_c2sv.tcl</dd>
</d1>
																
:::note info
本稿に掲載されている該当のソース・コードをコピー&ペーストして、<br>上記のファイル名（および拡張子）で保存します。
:::
									
2)　ModelSim インテル® FPGA Starter Edition を起動します。
								
3)　Transcript ウィンドウで次のコマンドをタイプして上記 1) のディレクトリに移動します。
　　【例】　cd C:/work/test_c2sv									
カレント・ディレクトリを移動後、ls を実行して、上記のファイルがあることを確認します。
![kom_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/9c29e9e7-219b-9fc4-e542-2de2fbbf069b.png)

###3-2-6. Tclスクリプトの実行

 次のコマンドで Tclスクリプトを実行します。
<pre>do ./dpic_c2sv.tcl</pre>

Tcl スクリプトを実行後、Wave ウィンドウ内にポートや信号の全てが追加されています。
![kom_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/b2b29e9b-97db-63e7-60a1-8500cd4cb198.png)

###3-2-7. Modelsim によるシミュレーション

Wave ウィンドウ内にカーソルを置いて、右クリックより Add -> New Divider を選択します。
![kom_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/35a4b23a-524a-125b-3e08-fca8ade02943.png)

次のように、SystemVerilog のモジュールと、C言語の関数を区分けします。
その際、信号の順番も適宜入れ替えます。
![kom_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/291fc9ef-a2d6-4c2b-c2f3-9a1ff3129fbb.png)


Simulation メニューから Run -> Run -All を選択します。
![kom_16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/b3dcfbc4-2583-9921-9690-5f0289649837.png)

次のような画面が起動したら、[ いいえ(N) ] をクリックします。
![kom_17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/c93db2d2-ebbb-a5ba-9a6d-c3c2d968e7c1.png)

Wave ウィンドウに波形が表示されます。
![kom_18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/26b946bc-85d8-9fd5-e120-150cfebb41ed.png)

[ Zoom Full (F) ] アイコンをクリックします。
![kom_19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/95c104d8-e168-d17e-998d-875f419616f9.png)

波形の全体像が現れてきます。
![kom_20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/3dfd0beb-9a2e-018c-e01b-3ae3e0527b3d.png)

<a id="anchor15"></a>
###3-2-8. シミュレーション結果									
シミュレーターで時系列の動きを追うため、SystemVerilog コードの `initial` 文に記述した内容に従い、`nmu_val` に値3を指定してから20 ns 経過後に`num`が0から3に変化していることから、この時点でC言語側の`add_main()`が実行開始していることが把握できます。
![kom_20_up.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/996b9ec7-be50-bd3a-45d4-f885cabca68a.png)


同時に、`add_main()`関数内のサブ関数 `add_func()`が実行され、`add_func()`の実体であるSystemVerilog コードで定義した関数`add_func` で即座に加算が行われ、その結果がC言語側に返された後、次のように`printf()`によってModelSimのTranscriptウィンドウ内にメッセージとして表示されます。
![kom_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/acbdecf7-dd78-8a76-ec86-2934de0ddd21.png)

また、`add_func` はトップ階層では無く、DPI-Cとのインターフェースによって、C言語側と接続していることから、モジュール名の後ろに定義した入力ポートには何も値が表示されず、出力ポートにも加算結果が一切反映されていないこともシミュレーション波形からも把握できます。

<a id="anchor16"></a>
##3-3. ③ C 言語からSystemVerilogコード(タスクを使用)を呼び出す
CコードからSystemVerilog コードを呼び出す場合、SystemVerilogコード内に<br>`export "DPI-C"` を記述します。`import` とは異なりSystemVerilog の中にC言語を取り込む<br>のとは逆の方向になるので、SystemVerilogを外部言語に吐き出す意味合いの`export`がDPI-C<br>では使われます。
`export "DPI-C"`の後には、C言語側にエクスポートされるSystemVerilogの関数(ファンク<br>ションやタスク）を指定します。
本デザインでは、SystemVerilogのタスクをエクスポートするので、`export "DPI-C"` の<br>後ろに、タスク名と、それに関連した情報（型や引数）を使用しています。	
`export`の場合、`import`で`function`を指定するケースとは異なり、`pure`や`context`は<br>不要です。また、`import`の場合でも`task`を指定する際には必ず`context`を使用します。

<a id="anchor17"></a>
###3-3-1. C コード									
本コードは、C言語のmain関数では無く、`add_main()`というサブ関数になります。加えて、<br> その下には`add_task()`というサブ関数を定義しています。
ヘッダ・ファイルには、C言語側で標準的に用意されているライブラリに加えて、<br>`dpiheader.h`を明示的にインクルードして、DPI-C経由で処理が分岐されます。
サブ関数`add_task()`の引数に値を指定した後、`add_task()`の呼び出しを行い、<br>SystemVeriligで定義した`add_task` というタスクに加算処理を渡しています。<br>SystemVerilog側からの加算結果は、戻り値として変数`result`に格納後、`printf()`を使用<br>してメッセージに表示できるように記述しています。
ここでは同時に、変数 `num` の値もメッセージに表示させています。後術しますが、この値は<br>`add_maion()`の引数として外部から指定されます。

```c:add_main_tsk.c
#include <stdio.h>
#include <stdlib.h>
#include "dpiheader.h"

int add_task(int, int, int*);

int add_main(int num){

int a = 35;
int b = 11;
int result;

add_task(a, b, &result);
printf("************************************************************************\n");
printf("Mesage from C function\n");
printf(">>> Culcurate System Verilog task result = %d <- %d\n", result, num);
printf("************************************************************************\n");

return (0);
}
```

<a id="anchor18"></a>
###3-3-2. SystemVerilog コード
					
本コードでは、`tb_module`というモジュール名を定義しています。タスクはテストベンチで<br>利用するケースが多いので、モジュール名の後ろに定義するポート名は何も定義していません。あくまでC言語と連携した検証システムのようなものをイメージしています。
このモジュール内には、SystemVerilog のタスク`add_task`を定義していますが、これが<br>C言語側から呼び出される対象になります。<br>SystemVerilog側から見れば、外部にエクスポートする対象となります。
`add_task`では 入力ポートとして`int` 型で宣言された仮引数`t_da`と `t_db` が、C言語側の<br>引数に対応します。

`t_da`と `t_db`を加算した結果は、出力ポートとして`int`型で宣言された仮引数`t_q`に<br>送られて、`add_task`の第三引数に反映されることで、外部から参照できるようになります。
DPI-Cに処理をエクスポートする為、`export "DPI-C"` の後ろに `task add_task` の記述を<br>追加しています。
このタスクには、時間的変化を与えています。対象の`add_task`は、開始して20ns後に<br>即値99を`t_q`に出力し、さらに20ns後に加算結果が`t_q`に出力するように記述しています。
- - -
本コードでは、`initial`文を使用して、シミュレーション波形で時間的な動きを視覚的に<br>把握できるようにしています。
シミュレーション直後、つまり0nsで`num_val`に3を代入した後、20ns経過した後に、<br>C言語側のサブ関数`add_main()`に引数3を与えて実行しています。組込みシステムのように、<br>CPUのリセットを解除して、ROMからCコードをブートする動きに似ているかもしれません。<br>
この処理によって、Cコード`add_main()`が実行後、その中にあるサブ関数`add_task()`が<br>実行されるフローがシミュレートされます。<br>
このフローは、結果的にSystemVerilogの`initial`文からC言語を呼び出す仕組みが無いと<br>実現できないので、本コード内では、`import “DPI-C”`も併用しています。
SystemVerilogから呼び出されるCコード`add_main()`の中では、サブ関数`add_func()`が<br>SystemVerilogに参照するので、本コードでは`import “DPI-C”`の後ろに `context` を付加<br>して、`int`型の`function` として`add_main `を呼び出すように記述しています。


```verilog:tb_module.sv

`timescale 1ns/1ps

module tb_module();

import "DPI-C" context task add_main(input int num);
export "DPI-C" task add_task;

int num_val = 0;

// Task                                                   
task add_task;
	input int t_da;
	input int t_db; 
	output int t_q; 

	begin
		#20 t_q = 99;
		#20 t_q = t_da + t_db;
	end
endtask


// Monitoring
	initial 
	begin
		num_val = 3;
		#20	
		add_main(num_val);
		#20 $finish;
	end

endmodule
```
- - - 
参考：<br>・ModelSim DEサンプル・コード
- - - 

<a id="anchor19"></a>
###3-3-3. Tcl スクリプト
ModelSimで利用可能なコマンドを記述しています。
もっと適切な書き方はあると感じていますが、深追いはせずに、とりあえず「動かす」ことに<br>重点を置きました。

```tcl:dpic_c2sv_tsk.tcl
vlib work
vlog -sv -dpiheader dpiheader.h tb_module.sv add_main_tsk.c -ccflags "-g"
vsim -c tb_module
add wave -r /*
```
- - - 
参考：<br>・ModelSim DEサンプル・コード
- - - 

<a id="anchor20"></a>
###3-3-4. 実行手順		

###3-3-5. Modelsim の起動							
1)	半角英数のPATHによる任意のディレクトリに、以下のファイルを格納します。
<d1>
<dd>tb_module.sv</dd>
<dd>add_main_tsk.c</dd>
<dd>dpic_c2sv_tsk.tcl</dd>
</d1>

:::note info
本稿に掲載されている該当のソース・コードをコピー&ペーストして、<br>上記のファイル名（および拡張子）で保存します。
:::
									
2) ModelSim インテル® FPGA Starter Edition を起動します。									
3) Transcript ウィンドウで次のコマンドをタイプして、上記 1) のディレクトリに移動します。<br>　　
【例】　cd C:/work/test_c2sv_tsk					

カレント・ディレクトリを移動後、ls を実行して、上記のファイルがあることを確認します。
![kom_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/3778359b-bfbd-098f-3afc-50f50a9ecb16.png)


###3-3-6. Tclスクリプトの実行							
次のコマンドで Tclスクリプトを実行します。
<pre>do ./dpic_c2sv_tsk.tcl</pre>						
									
Tcl スクリプトを実行後、Wave ウィンドウ内にポートや信号の全てが追加されています。
![kom_23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/e78c14da-a45f-21bf-1be7-293bb7049376.png)

###3-3-7. ModelSim によるシミュレーション						
Wave ウィンドウ内にカーソルを置いて、右クリックより Add -> New Divider を選択します。
![kom_24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/15b0bffc-b6da-9c44-4519-c437c4d49df9.png)

次のように、SystemVerilog のモジュールと、C言語の関数を区分けします。
その際、信号の順番も適宜入れ替えます。	
![kom_25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/4629cae0-a6d5-bcc4-3c55-f3f8e35a0bea.png)

Simulation メニューから Run -> Run -All を選択します。	
![kom_26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/e899fb2c-738e-8468-1956-bed3586bb737.png)

次のような画面が起動したら、[ いいえ(N) ] をクリックします。
![kom_27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/ac714a5d-216b-e068-46cc-186b5e51d7ff.png)

Wave ウィンドウに波形が表示されます。
![kom_28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/1cfcb06d-5fc9-9290-ffc9-fdbde29179cd.png)

 [ Zoom Full (F) ] アイコンをクリックします。
![kom_29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/1e49338e-07ef-970b-3403-1ad6979034ed.png)

波形の全体像が現れてきます。
![kom_30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/0bf587e1-9d0e-060e-0b58-37509aedd28e.png)

<a id="anchor21"></a>
###3-3-8. シミュレーション結果		
シミュレーターで時系列の動きを追うため、SystemVerilog コードの `initial` 文に記述した<br>内容に従い、`nmu_val` に値3を指定してから20 ns 経過後に`num`が0から3に変化している<br>ことから、この時点でCコード`add_main()`が実行開始していることが把握できます。
![kom_30_up.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/80693408-fc0d-1e8a-98e2-d3235166f734.png)


同時に、`add_main()`関数内のサブ関数 `add_task()`が参照されて、SystemVerilog コードで<br>定義したタスク`add_task` に加算する引数が渡されていることが確認できます。<br>`add_task`内では出力に与える値を時間に変えるように記述しています。<br>その為、`add_task`が呼び出された地点から20ns後に即値99を出力し、さらに20ns後に加算が<br>行われている動きがシミュレーション波形でも確認できます。<br>加算結果がCコードに返された後、次のように`printf()`によってModelSimの<br>Transcriptウィンドウ内にメッセージとして表示されます。
![kom_31_up.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2217583/cd332be8-a770-1d57-09f2-56e158dec4f5.png)


<a id="anchor22"></a>
#4. 【考察】 DPI-CとCコンパイラとの関係
コンパイル・メッセージからの推察の範囲になりますが、ModelSimにはGCCが内包されているように見受けられます。

:::note warn
どのバージョンからGCCが内包されたかのは未調査
:::

また、コンパイル・オプションでは、次のように -dpiheaderオプションの後ろにdpiheader.hと追記しています。<br>

```
vlog -sv -dpiheader dpiheader.h (ファイル名).sv （ファイル名）.c -ccflags "-g"	
```
このときツールが自動でdpiheader.hをプロジェクト・ディレクトリに生成していました。
<br>さらに、このdpiheader.hでは、`#include "svdpi.h"`と記述されており、ファイル名から<br>推察して、svdpi.hがDPI-Cとの関連付けを行っているように感じています。<br>また、このdpiheader.hでは、DPI-Cでインポート/エクスポートを行う為の関数プロトタイプ<br>も再定義されていました。<br>その他、Load時に幾つか専用のDLLファイルも生成されていることから、シミュレーション時<br>に、シミュレーターがこれらのDLLを参照しているように思われます。

この辺りの仕組みは、シミュレーション・ツールの作りに依存するので、筆者のような<br>一般ユーザーでは、ここまでが限界と言えます。

- - - 
参考：
・[DPI-CでSystemVerilogからC言語の関数呼び出し]
(https://happyfield.hatenablog.com/entry/2015/11/22/025736)
- - - 

<a id="anchor23"></a>
#5. まとめ
* DPI-Cを使用して、SystemVerilog コードからC言語を呼び出す場合、System Verilogコード<br>には`import "DPI-C"`を記述し、その後にはSystemVerilogコード内に記述している関数<br>（ファンクションもしくはタスク）の名前を記述
 *  指定する関数は、`pure` と`context` に分類され、次のルールに基づいて関数名の<br>前に追記
 * ファンクションを呼び出す場合：<br>- C言語からSystemVerilogコードに参照しない場合、`pure` を追記<br>- C言語からSystemVerilogコードに参照する場合、`context` を追記
 * タスクを呼び出す場合：常に`context` を追記

* DPI-Cを使用して、C言語からSystemVerilogコード内に記述している関数（ファンクション<br>やタスク）を呼び出す場合、`export “DPI-C”`を記述し、その後にはSystemVerilogコード<br>に記述している関数の名前を記述
(`export`の場合、`import`とは異なり、`pure`や`context`は不要 )

* DPI-Cを使用する場合、コマンド・オプション -dpiheaderを追加してコンパイル<br>
 * dpiheader.h内にDPI-Cを使用するためのインクルード・ファイルsvdpi.hが定義<br>
 * dpiheader.hは、ModelSimが自動的に生成<br>
* ModelSimに内包されているGCCを使用

:::note warn
どのバージョンからGCCが内包されたかのは未調査
:::


<a id="anchor24"></a>
#6. リファレンス	
この記事は以下の情報を参考にして執筆しました。
・ SystemVerilog設計スタートアップ　Design Wave Magazine 編集部　編（CQ出版社）
・ [DPI-CでSystemVerilogからC言語の関数呼び出し](https://happyfield.hatenablog.com/entry/2015/11/22/025736)
・[ModelSimASE+clangでSystemVerilog DPI-Cをシミュレーションする](https://qiita.com/ginnyu_tei/items/c6095df17bcbbcec8541br)
・[16.2　DPI](http://japanese.sugawara-systems.com/systemverilog/dpi.htm)
・[無償ツールで実践する「ハード・ソフト協調検証」(1) ―― SystemVerilogのDPI-C機能]
(http://www.kumikomi.net/archives/2009/12/_1systemverilogdpi-c.php?page=1)
・[無償ツールで実践する「ハード・ソフト協調検証」(2) ―― テスト・プログラムはC言語で書く（Tech Village）]
(http://www.kumikomi.net/archives/2009/12/_2c.php?page=2)
・ModelSim DEサンプル・コード
- - -