UVMでのC/C++のテストシーケンス

UVMでC/C++のテストシーケンスを走らせる場合のアイディアを紹介しておこうと思う。
Dynamic Objectであるclass内ではDPI-CでC/C++をimportする事ができない。なので、UVM sequence class内ではC/C++のプログラムを直接importして使う事はできない。ではどうすれば良いのかと言うと、staticな領域であるpackage内でC/C++のfunctionをimportしてそれを呼ぶようにする。その際に直接呼ぶのではなく、そのpackageに別途SVのtaskを用意して、そのtaskからC/C++のfunctionをコールするようにする。そのSVのtaskの引数としてシーケンサーと親シーケンスのハンドルを指定して、それらをそのpackageに渡すようにする。C側から呼ぶSVのレジスタRead/WriteのSVタスクもpackage内に用意し、その中でではAgentのRead/WriteのAPIシーケンスをスタートさせる。その際に、先ほどのpackageに渡ったシーケンサーと親シーケンスのハンドルを引数にして.start()を実行する。

```verilog:dpi_seq_pkg.sv
package dpi_seq_pkg;
  import "DPI-C" context task C_Program();
  export "DPI-C" task sv_write;
  export "DPI-C" task sv_read ;
 
  import uvm_pkg::*;
 
  uvm_sequencer_base m_sqr;
  uvm_sequence_base  parent_seq;

  // Called by a UVM sequence
  task start(uvm_sequencer_base sequencer, uvm_sequence_base parent_sequence);
    m_sqr       = sequencer;
    parent_seq  = parent_sequence;
    C_Program();  // C/C++ Program Call
  endtask
 
  // Called from C side
  task sv_write (input int addr, data);
    agent_pkg::write_sequence  api_wr_seq = agent_pkg::write_sequence::type_id::create("api_wr_seq");
    api_wr_seq.Val_A = addr;
    api_wr_seq.Val_D = data;
    api_wr_seq.start( .sequencer(m_sequencer), .parent_seuence(parent_seq) );  // API Sequence Start with Sequencer Handle and Parent Sequencer Handle
  endtask : sv_write

  // Called from C side
  task sv_read (input int addr, output int data);
    agent_pkg::read_sequence  api_rd_seq = agent_pkg::read_sequence::type_id::create("api_rd_seq");
    api_rd_seq.Val_A = addr;
    api_rd_seq.start( .sequencer(m_sequencer), .parent_sequence(parent_seq) ); // API Sequence Start with Sequencer Handle and Parent Sequencer Handle
    data = api_rd_seq.Val_D;
  endtask : sv_read

endpackage
```
```verilog:C_sequence.sv
task C_sequence::body()
    // C Program
    dpi_seq_pkg::start( .sequencer(m_sequencer), .parent_sequence(this) );
```
バリエーションとしてstart() taskの代わりにdpi_seq_pkgの中にUVM Sequenceを定義してそれをstatさせるでもよい。その場合にはbody() taskの中でstart() taskと同様にsequnecerとparent sequenceのハンドルをpackageに渡すのとC_program()をコールするようにする。

```verilog:dpi_seq_pkg.sv
package dpi_seq_pkg;
  import "DPI-C" context task C_Program();
  export "DPI-C" task sv_write;
  export "DPI-C" task sv_read ;
 
  import uvm_pkg::*;
 
  uvm_sequencer_base m_sqr;
  uvm_sequence_base  parent_seq;

  // UVM Sequence Class instead of start task
  class dpi_c_sequence extends uvm_sequence;

    `uvm_object_utils(dpi_c_sequence)

    function new (string name="");
      super.new(name);
    endfunction

    virtual task body();
      m_sqr       = this.get_sequencer();       // Pass sequencer handle
      parent_seq  = this.get_parent_sequence(); // Pass parent sequence handle
      C_Program();                              // C/C++ Program Call
    endtask

  endclass
 
  // Called from C side
  task sv_write (input int addr, data);
    agent_pkg::write_sequence  api_wr_seq = agent_pkg::write_sequence::type_id::create("api_wr_seq");
    api_wr_seq.Val_A = addr;
    api_wr_seq.Val_D = data;
    api_wr_seq.start( .sequencer(m_sqr), .parent_seuence(parent_seq) );  // API Sequence Start with Sequencer Handle and Parent Sequencer Handle
  endtask : sv_write

  // Called from C side
  task sv_read (input int addr, output int data);
    agent_pkg::read_sequence  api_rd_seq = agent_pkg::read_sequence::type_id::create("api_rd_seq");
    api_rd_seq.Val_A = addr;
    api_rd_seq.start( .sequencer(m_sqr), .parent_sequence(parent_seq) ); // API Sequence Start with Sequencer Handle and Parent Sequencer Handle
    data = api_rd_seq.Val_D;
  endtask : sv_read

endpackage
```
```verilog:C_sequence.sv
task C_sequence::body()
    // Start DPI C Sequence
    dpi_seq_pkg::dpi_c_sequence dpi_c_seq = dpi_seq_pkg::dpi_c_sequence::type_id::create("dpi_c_seq");
    dpi_c_seeq.start(this.m_sequencer, this);
```

上記の例ではdpi_seq_pkgとして別packageを用意したが、もちろんテストsequenceが入っているpackageや他のテストコンポーネントが入っているpackageに同様の記述をしても良い。Agentのpackageをいじれるならこの記述自体をAgentのpackageに入れてC/C++を走らせるAgent API Sequenceとしてしまっても良いかもしれない。

package内でDPI-Cを使うことは許されているが、以前とある商用シミュレータでは実行時にオプションを設定しないとできなかった。うまくできない場合は各ベンダーのサポートに聞いてみよう。


また、[Verification Academy](https://verificationacademy.com)では以下のようなRAL(Register Abstraction Layer)を使った方法が提案されているが、ここまで複雑な事をせずとも、殆どの場合上記の様な方法で事足りるのではないだろうか。特にこの為だけにわざわざRAL(Register Abstraction Layer)を作るなんていうのは馬鹿げてる。

[C-Based Stimulus for UVM (On Demand Seminars)](https://verificationacademy.com/seminars/uvm-recipe-c-based-stimulus-for-uvm)
[CBasedStimulus (Cookbook)](https://verificationacademy.com/cookbook/cbasedstimulus)

更に同じく[Verification Academy](https://verificationacademy.com)内に以下の様なpapaerも見つけた。

[UVM and C Tests - Perfect Together](https://verificationacademy.com/resources/technical-papers/uvm-and-c-tests-perfect-together)
[Technical Paper: UVM and C Tests - Perfect Together](https://verificationacademy.com/resource/7dd84112-3d39-3ed7-825d-0a7e76110943)

この例ではpackageではなく同じくstaticであるinterfaceを介してDPI-CでC/C++をコールしている。そしてinterface内で、Agent API Sequenceを介してではなく、Agentのsequencerを直接参照してsequenceを流しているようである。"the simplest approach"とか自称しているが、わざわざsequencerをいじったりしなくてはならず、とても"the simplest"には思えない。

因みに[Verification Academy](https://verificationacademy.com)の情報は確かに有用ではあるのだが、時々トンチンカンな事を言ったりしているのも見受けられるので、少し注意が必要である。何でもそうなのだが、自分で何も考えずに妄信したりしてはいけない。

---
以下、今回紹介した方法の実際のサンプル環境をGit Hub公開したので参考にしてみてください。

https://github.com/AlphaLyrae0/UVM_DPI_Example.git

フリーでUVMが動く[DSim Desktop](https://www.metrics.ca/)及び[Vivado Simulator (xsim)](https://japan.xilinx.com/support/download/index.html/content/xilinx/ja/downloadNav/vivado-design-tools.html)で動作するようにしてあります。

```shell
> git clone https://github.com/AlphaLyrae0/UVM_DPI_Example.git
```

でダウンロードできます。多分。

```shell
> cd UVM_DPI_Example
> make all
```

で全てコンパイルされて
<dl>
  <dt>simple_test</dt>  <dd>DPI-Cを使わない普通のUVMシーケンスが走るテスト</dd>
  <dt>dpi_test1</dt>    <dd>DPI-Cを使ったC/C++のシーケンスが走るテスト</dd>
  <dt>dpi_test2</dt>    <dd>DPI-Cを使ってC/C++から値を取って来て普通のUVMシーケンスが走るテスト</dd>
  <dt>unite_test</dt>   <dd>上記３つのシーケンスが連続で続けて走るテスト</dd>
</dl>

の４つテストが全て実行されます。
個別のテストを実行したい場合には以下でで実行されます。


```shell
> make <Test Name>
```


ファイルをいじればテスト実行時に必要なコンパイルがされるので是非自分でいろいろいじって実行してみてください。C/C++ファイルだけいじればC/C++のコンパイルだけがされます。