UVM RALとDPI-CでC/C++からレジスタ名でアクセス

以前UVMでDPI-Cを使ってC/C++のテストシーケンスを作る記事を書いた。

https://qiita.com/vega77/items/37b967ca6d2812fc2e83

この例ではレジスタ のアドレス値をC/C++からUVM側に渡してバスにアクセスをしていた。しかし、アドレスの値ではなくて、レジスタ名でのアクセスをしたい。C/C++側でアドレスマッピングが定義されたヘッダファイルが用意されていて、それを利用できれば特に問題ないが、そうでない場合にはUVMのRAL(Register Abstraction Layer)を利用する事でC/C++からもレジスタ名でアクセスできるようになる。
UVMのRALにはレジスタ名やフィールド名をで文字列で検索してレジスタオブジェクトやフィールドオブジェクトを返してくれるAPI functionが用意されている。なので、RALのこの機能を利用してC/C++からレジスタ名、フィールド名でRAL経由でレジスタにアクセスする例を紹介する。

## RAL向けUVM sequenceの拡張

UVMにはuvm_reg_sequenceと言うRALを使う上での便利なシーケンスが既に用意されていて、RALのRegister Blockへのハンドルであるmodelやレジスタアクセスの為のAPIタスクのwrite_reg(), read_reg()をメンバーとして持っている。詳細は以下参照。

https://verificationacademy.com/verification-methodology-reference/uvm/docs_1.2/html/files/reg/uvm_reg_sequence-svh.html

なので、レジスタにアクセスを行うUVMテストシーケンスは通常はこれを継承して実装すればよい。しかし、文字列でレジスタ名・フィールド名にアクセスするようなタスクは用意されていない。そこで、まずはこのシーケンスを拡張してそのようなタスクをメンバに持つシーケンスを作る。
以下、my_uvm_pkgにmy_ral_sequene_baseと言うuvm_reg_sequenceを継承したレジスタ名でのレジスタアクセスを可能にするシーケンス作ってみた。

```verilog:my_uvm_pkg.sv
`ifndef MY_UVM_PKG
`define MY_UVM_PKG

`include "uvm_macros.svh"

package my_uvm_pkg;
    import uvm_pkg::*;

    virtual class my_ral_sequence_base#(type BASE=uvm_sequence#(uvm_reg_item)) extends uvm_reg_sequence#(BASE);

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

        //--------- To Start This Sequence By Passing RAL Instead Of Sequencer ------
        virtual task start_on_ral(uvm_reg_block ral_model, uvm_sequence_base parent=null);
            this.model = ral_model;
            this.start(ral_model.default_map.get_sequencer(), parent);
        endtask
        //---------------------------------------------------------------------------

        string ral_name     = "ral_model";
        bit    auto_predict = 1;

        virtual function void check_model();
            if (this.model == null) begin
                if(!uvm_config_db#(uvm_reg_block)::get(this.get_sequencer(), this.get_sequence_path(), ral_name, this.model) )
                    `uvm_fatal(get_name(), {ral_name, " was not gotten!!!"})
            end
        endfunction

        virtual task pre_body();
            super.pre_body();
            check_model();
            this.model.default_map.set_auto_predict (auto_predict);
          //this.model.default_map.set_check_on_read(1);
        endtask

        pure virtual task body();

        uvm_status_e status;

        virtual task reg_write(input string reg_name, int unsigned data);
          //this.write_reg(this.model.get_reg_by_name(reg_name), status, data);
            this.model.write_reg_by_name( .status(status) , .name(reg_name), .data(data), .parent(this));
            if (status != UVM_IS_OK)
               `uvm_error(get_name(), {reg_name, " is not found!!"})
        endtask

        virtual task reg_read(input string reg_name, output int unsigned data);
          //this.read_reg(this.model.get_reg_by_name(reg_name), status, data);
            this.model.read_reg_by_name( .status(status) , .name(reg_name), .data(data), .parent(this));
            if (status != UVM_IS_OK)
               `uvm_error(get_name(), {reg_name, " is not found!!"})
        endtask

        virtual function void reg_ralset(input string reg_name, int unsigned data);
            uvm_reg l_reg = this.model.get_reg_by_name(reg_name);
            if (l_reg == null) begin
               `uvm_error(get_name(), {reg_name, " is not found!!"})
                return;
            end
            l_reg.set(data);
        endfunction

        virtual function int unsigned reg_ralget(input string reg_name);
            uvm_reg l_reg = this.model.get_reg_by_name(reg_name);
            if (l_reg == null) begin
               `uvm_error(get_name(), {reg_name, " is not found!!"})
                return 0;
            end
            return(l_reg.get_mirrored_value(reg_name));
        endfunction

        virtual function uvm_reg_field find_fld (input string name, delimiter=".");
            uvm_reg         l_reg;
            uvm_reg_field   l_fld;
            string reg_name;
            string fld_name;
            string split_q[$];
            uvm_pkg::uvm_split_string(name, delimiter.getc(0), split_q);
            if (split_q.size() != 2 )
                `uvm_error(get_name(), {"Illegal Register Field Name, ", name})
            reg_name = split_q[0];
            fld_name = split_q[1];

            l_reg = this.model.get_reg_by_name(reg_name);
            if (l_reg == null) begin
               `uvm_error(get_name(), {reg_name, " is not found!!"})
                return (null);
            end

            l_fld = l_reg.get_field_by_name(fld_name);
            if (l_fld == null) begin
                `uvm_error(get_name(), {fld_name, " is not found!!"})
                return (null);
            end

            return (l_fld);

        endfunction

        virtual function void fld_ralset(input string name, int unsigned data);
            uvm_reg_field l_fld = find_fld(name);
            if (l_fld == null) return;
            l_fld.set(data);
        endfunction

        virtual function int unsigned fld_ralget(input string name);
            uvm_reg_field l_fld = find_fld(name);
            if (l_fld == null) return 0;
            return(l_fld.get_mirrored_value());
        endfunction

        virtual task reg_update(input string reg_name);
           this.update_reg(this.model.get_reg_by_name(reg_name), status);
           if (status != UVM_IS_OK)
              `uvm_error(get_name(), {reg_name, " is not found!!"})
        endtask

        virtual task reg_mirror(input string reg_name);
           this.mirror_reg(this.model.get_reg_by_name(reg_name), status);
           if (status != UVM_IS_OK)
              `uvm_error(get_name(), {reg_name, " is not found!!"})
        endtask

    endclass
    
endpackage
`endif
```

このシーケンスクラスでは
```reg_write  (input string reg_name, int unsigned data);```
```reg_read   (input string reg_name, output int unsigned data);```
```reg_ralset (input string reg_name, int unsigned data);```
```reg_ralget (input string reg_name);```
```fld_ralset (input string name, int unsigned data);```
```fld_ralget (input string name);```
```reg_update (input string reg_name);```
```reg_mirror (input string reg_name);```
と言うtask/functionを用意した。
reg_write/readはそのままstringのレジスタ名でレジスタにアクセスするtask。reg/fld_ralset/getは同じくstringのレジスタ名およびフィールド名でRALにアクセスするfunction。実際のレジスタではなくRALへのアクセスなのでタスクではなくファンクション。実際のレジスタへのアクセスはreg_update/mirrorで行う。updateはwrite、mirrorはreadに対応。writeはRALにsetしてからupdate。readは逆にmirrorしてからRALからget。
uvm_reg_blockには
```write_reg_by_name()```
```read_reg_by_name()```
```get_reg_by_name()```
uvm_regには
```get_field_by_name()```
と言うstringのレジスタ名・フィールド名で検索およびアクセスするAPIが用意されている。これらを利用する事により名前でのレジスタおよびフィールドへのアクセスを可能にしている。
これで通常のUVMシーケンスでstringのレジスタ名・フィールド名でレジスタアクセスできるようになった。

以下はmy_ral_sequence_baseを使って、レジスタフィールド名でWriteアクセスをするシーケンスの例。

```verilog:RAL_Access_Example
class example_ral_sequence extends my_uvm_pkg::my_ral_sequence_base;
  `uvm_object_utils(example_ral_sequence)
  .
  .
  .
  virtual task body()
    //this.reg_write("CONFIG_AAA",   100);
      this.fld_ralset("CONFIG_AAA.param_0",  9);
      this.fld_ralset("CONFIG_AAA.param_1",  9);
      this.fld_ralset("CONFIG_AAA.param_2",  1);
      this.fld_ralset("CONFIG_AAA.param_3",  2);
      this.fld_ralset("CONFIG_AAA.param_4",  2);
      this.fld_ralset("CONFIG_AAA.param_5",  2);
      this.fld_ralset("CONFIG_AAA.param_6",  2);
      this.reg_update("CONFIG_AAA");
  endtask
  .
  .
  .
endclass
```


## DPI-Cでレジスタアクセスtask/functionのコール

次にC/C++からこれらを利用できるようにする。その為には当然DPI-Cを用いてこれらのtask/functionにアクセスする**staticな**task/functionを用意する。DPI-Cでimport/exportするtask/functionはstaticである。なのでそのままではdynamic objectであるmy_ral_sequence_baseのautomaticなtask/functionを直接コールできない。
そこでまずシーケンスをインスタンス化して、そのハンドルをstaticなfunction/taskと同じmy_uvm_pkgに置いて、そのハンドル経由でアクセスするようにする。
以下、my_uvm_pkgにそのようなtask/functionを追加してimport/exportでDPI-CでC/C++からアクセスできるようにした。

```verilog:my_uvm_pkg.sv
`ifndef MY_UVM_PKG
`define MY_UVM_PKG

`include "uvm_macros.svh"

package my_uvm_pkg;
    import uvm_pkg::*;

    virtual class my_ral_sequence_base#(type BASE=uvm_sequence#(uvm_reg_item)) extends uvm_reg_sequence#(BASE);
        .
        .
        .
        virtual task start_c_sequence(int id=0);
            if (m_ral_seq != null)
                `uvm_fatal("MY_UVM_PKG", $sformatf("m_ral_seq is not empty. <%s>",m_ral_seq.get_full_name()))
            m_ral_seq = this; 
            `uvm_info(get_name(), $sformatf("start_c_sequence %s %0d",this.get_name(), id), UVM_MEDIUM)
            C_reg_sequence(this.get_name(), id);
          //C_reg_sequence(id);
            m_ral_seq = null;
        endtask

    endclass

    //---------- For C_Program ---------------
  //import "DPI-C" context task C_reg_sequence(input int id); //input string p_name, input int id);
    import "DPI-C" context task C_reg_sequence(input string p_name, input int id);

    export "DPI-C" ral_reg_write  = task     reg_write ;
    export "DPI-C" ral_reg_set    = function reg_ralset;
    export "DPI-C" ral_fld_set    = function fld_ralset;
    export "DPI-C" ral_reg_update = task     reg_update;

    export "DPI-C" ral_reg_read   = task     reg_read  ;
    export "DPI-C" ral_reg_mirror = task     reg_mirror;
    export "DPI-C" ral_reg_get    = function reg_ralget;
    export "DPI-C" ral_fld_get    = function fld_ralget;

    export "DPI-C" sv_print       = function print_msg;

    function void print_msg(input string msg);
        $display(msg);
    endfunction

    my_ral_sequence_base m_ral_seq;

    task reg_write(input string reg_name, input int data);
        m_ral_seq.reg_write(reg_name, data);
    endtask

    task reg_read (input string reg_name, output int data);
        m_ral_seq.reg_read(reg_name, data);
    endtask
    
    function void reg_ralset (input string reg_name, input int data);
        m_ral_seq.reg_ralset (reg_name, data);
    endfunction

    function int reg_ralget (input string reg_name);
        return (m_ral_seq.reg_ralget(reg_name));
    endfunction

    task reg_update(input string reg_name);
        m_ral_seq.reg_update(reg_name);
    endtask

    task reg_mirror(input string reg_name);
        m_ral_seq.reg_mirror(reg_name);
    endtask

    function void fld_ralset (input string reg_fld_name, input int data);
        m_ral_seq.fld_ralset (reg_fld_name, data);
    endfunction

    function int fld_ralget (input string reg_fld_name);
        return (m_ral_seq.fld_ralget(reg_fld_name));
    endfunction
    

    //---------------------------------------------------
    class my_c_ral_sequence extends my_ral_sequence_base;
        `uvm_object_utils(my_c_ral_sequence)

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

        int id;

        virtual task pre_body();
            super.pre_body();
            if (m_ral_seq != null)
                `uvm_fatal(get_type_name(), $sformatf("m_ral_seq is not empty. <%s>",m_ral_seq.get_full_name()))
            m_ral_seq = this;
        endtask

        virtual task body();
          //C_reg_sequence(this.id);
            C_reg_sequence(this.get_name(), this.id);
        endtask

        virtual task post_body();
            super.post_body();
            m_ral_seq = null;
        endtask

    endclass
    //---------------------------------------------------
    
endpackage
`endif
```

インスタンス化したシーケンスのハンドルはmy_umv_pkg内の
    my_ral_sequence_base m_ral_seq;
への代入を想定している。このシーケンスハンドルにアクセスするstaticなtask/functionは以下である。
```reg_write ;```
```reg_ralset;```
```fld_ralset;```
```reg_update;```

```reg_read  ;```
```reg_mirror;```
```reg_ralget;```
```fld_ralget;```
これらがexportされC/C++側からコールされる。
そして、これらをコールするC/C++を走らせる必要がある。また前述のようにインスタンス化したシーケンスのハンドルをmy_uvm_pkg::m_ral_seqに割り当てる必要がある。それをやっているのがmy_ral_sequence_baseを継承したmy_c_ral_sequenceと言うシーケンスクラスである。
my_c_ral_sequenceではまずpre_body()で自身のクラスハンドルであるthisをmy_uvm_pkg::my_ral_seqに代入している。そしてbody()で、DPI-CでimportされたC/C++のfunctionがであるC_reg_sequenceをコールしている。
なのでこのUVMシーケンスであるmy_c_ral_sequenceをインスタンス化し、start()させればC/C++からRAL経由で名前でのレジスタアクセスが可能となる。
また、ついでに、my_ral_sequence_baseにも自身のクラスハンドルのmy_uvm_pkg::my_ral_seqへの代入と、C_reg_sequenceのコールを行うタスクをstart_c_sequence()としても追加した。なのでmy_ral_sequence_baseの継承クラスのbody()からstart_c_sequence()をコールしても同様の事が可能である。どちらの方法を使っても良いが、my_c_ral_sequenceを使う場合には、どのシーケンス或いはtest階層からでもC/C++を走らせる事ができる一方、my_ral_sequence_baseのstart_c_sequence()の場合には、その継承クラスシーケンスクラスからしか利用できないと言う違いがある。そもそもmy_c_ral_sequenceがmy_ral_sequence_baseの継承クラスではあるが。。。
ちなみにDPI-CでimportするC/C++のタスクはstaticnなので、競合を避ける為に、どちらも開始時にmy_uvm_pkg::my_ral_seq==nullである事をチェックし、修了時にmy_uvm_pkg::my_ral_seq=nullを代入して、自身のハンドルの代入を解除している。

以下はC/C++からのレジスターフィールド名でのWriteアクセスの例。

```cpp:C_Example
extern "C" void sv_print      (const char *);

extern "C" void ral_reg_write (const char *, int );
extern "C" void ral_reg_set   (const char *, int );
extern "C" void ral_fld_set   (const char *, int );
extern "C" void ral_reg_update(const char *);

extern "C" void ral_reg_read  (const char *, int *);
extern "C" void ral_reg_mirror(const char *);
extern "C" int  ral_reg_get   (const char *);
extern "C" int  ral_fld_get   (const char *);

extern "C" int  C_reg_sequence(const char * p_name, int id);

int C_reg_sequence(const char * p_name, int id)
{
  sv_print("-------------------------------------");
  sv_print("  CONFIG_AAA Write Access");
  sv_print("-------------------------------------");
  ral_fld_set   ("CONFIG_AAA.param_0" ,255); //8bit
  ral_fld_set   ("CONFIG_AAA.param_1", 254); //8bit
  ral_fld_set   ("CONFIG_AAA.param_2",  1); //4bit
  ral_fld_set   ("CONFIG_AAA.param_3", 15); //4bit
  ral_fld_set   ("CONFIG_AAA.param_4",  3); //2bit
  ral_fld_set   ("CONFIG_AAA.param_5",  3); //2bit
  ral_fld_set   ("CONFIG_AAA.param_6",  2); //2bit
  ral_fld_set   ("CONFIG_AAA.param_7",  2); //2bit
  ral_reg_update("CONFIG_AAA");
  sv_print("CONFIG_AAA Was Updated.");
}
```

以上ここで紹介したmy_uvm_pkgの内容は特にアプリケーションに特化している訳ではなく、一般的にどのアプリケーションにも適用可能である。なので、この様なUVMに不足している機能をいろいろと拡張したカスタムuvm_pkgライブラリを用意して、会社や部署単位で運用をする事をお勧めする。今回紹介したmy_uvm_pkgなんかそのままコピペして使ってもらって構わない。

## 動作サンプル

以下に今回紹介した方法で実際に実行可能な例を置いておく。Vivado Simulator (xsim)で実行可能なようにしてある。

https://github.com/AlphaLyrae0/UVM_RAL_DPI_C_Example

```shell
git clone https://github.com/AlphaLyrae0/UVM_RAL_DPI_C_Example.git
```

でディレクトリクローン出来ると思う。多分。

```
> cd UVM_RAL_DPI_C_Example
> make all
```

とすればコンパイル以下３つのテストの実行をする。

<dl>
  <dt>example_ral_test</dt>  <dd>DPI-Cを使わない通常ののUVM RALシーケンスを使ったテスト。レジスタ名およびフィールド名でアクセスする。</dd>
  <dt>c_ral_test</dt>        <dd>C/C++のシーケンスが走るテスト。レジスタ名およびフィールド名でアクセスする。</dd>
  <dt>unite_test</dt>   <dd>上記２つのシーケンスが続けて走るテスト。最後にもう一回start_c_sequence()を使ってC/C++のレジスタアクセスシーケンスが走る。</dd>
</dl>


個別のテストを実行をするには

```
> make run TEST_NAME=<Test Name>
```

或いは

```
> make run_<Test Name>
```

とする。

因みに、DPI-CでC/C++を使うには、シミュレーション実行時に-sv_libでShared Objectを指定するようにSVのLRMでも決められていて、通常のシミュレータであればそうなっている。しかしVivado Simulator (xsim)の ~~アホな~~ 独自の仕様では、xelabでSVコンパイル時に指定する必要がある。DPI-Cを使う理由の一つにSVコンパイルとC/C++コンパイルをそれぞれ独立にして、分けたいと言うのがあるが、SVコンパイルにC/C++コンパイルが必要だと、せっかくのその独立性が失われてしまう。幸いなことにSVコンパイル後にC/C++のファイルを編集してC/C++だけを再コンパイルして*.soを作り直しても正常に動作するようある。なのでMakefileではそうなるようにしてある。C/C++ファイルを編集して再度テストをrunさせるとC/C++だけコンパイルされてテストの動作が変わるのを確かめてみて下さい。