[SystemVerilog]uvm_driverを作成する。

##はじめに
[前回](http://qiita.com/tethys_seesaa/items/3f41d4780bdbf4a6f6b3)はuvm_driverクラスを継承し、モデルのインスタンスパスから直接taskをコールするなんちゃってUVMドライバを作成しました。
今回は、APBマスターモデルをuvm_driverクラスを継承したクラス化に変えようと思います。
[前回のコード](http://qiita.com/tethys_seesaa/items/3f41d4780bdbf4a6f6b3#3-4)は、uvm_testとuvm_driverをごちゃ混ぜにしたコードになってしまいました。ここでは、 **uvm_testはテストシナリオ、uvm_driverは検証対象(DUT)とのインターフェイス** と、クラスの役割を分離します（当たり前だ）。

##その前に！
UVMに関するいろんな資料を眺めていると、検証道具一覧紹介みたいな印象を持ち、いつもお腹いっぱいで常に敬遠し続けてきました。
よって、今回はトラディショナルなピンレベルの検証環境から小刻みにUVMライクに抽象化していく方向で進んでいます。
ほとんど我流で進めていますんで、公開したコードの中でもっとシンプルに書けるよ的なご指摘があると助かります。

##コード
###共通コード、interface、DUT
[前回](http://qiita.com/tethys_seesaa/items/3f41d4780bdbf4a6f6b3#2-2)と同じです。

###テスト
マスターモデルが消えて、マスターモデルのメソッドはuvm_driverクラスを継承したapb_drvに押し込みました。class部分の詳細は後述します。

```sv:tb.sv
module tb();

  apb_t apb_bus();

  apb_slave  #(.OFFSET(32'h0001_0000)) dut (.apb_slv(apb_bus), .*);

/*****************Deleted*******************
  abp_master tu(.apb_mst(apb_bus), .*);
*****************Deleted*******************/

  task clk_gen();
    apb_bus.clk = 0;
    forever
      #5 apb_bus.clk = ~ apb_bus.clk;
  endtask

  class apb_drv extends uvm_driver;
    `uvm_component_utils(apb_drv)

    virtual apb_t v_apb_bus;

    function new(string name, uvm_component parent);
      super.new(name, parent);
    endfunction

    function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      if(! uvm_config_db#(virtual apb_t)::get(this, "", "v_apb_bus", v_apb_bus))
       `uvm_fatal("VIF instance error", {"virtual interface cannot get", get_full_name(), ".v_apb_bus"} );
    endfunction

    virtual task reset_phase(uvm_phase phase);

      phase.raise_objection(this);

      `uvm_info("apb_drv", "Reset Start", UVM_MEDIUM)

      this.init();

      v_apb_bus.rst_n = '0;
      repeat(5) @(posedge v_apb_bus.clk);
      v_apb_bus.rst_n = '1;
      repeat(5) @(posedge v_apb_bus.clk);

      `uvm_info("apb_drv", "Reset End", UVM_MEDIUM)

      phase.drop_objection(this);

    endtask

    virtual task init();
      v_apb_bus.addr   = '0;
      v_apb_bus.sel    = '0;
      v_apb_bus.enable = '0;
      v_apb_bus.write  = '0;
      v_apb_bus.wdata  = 'z;
    endtask

    virtual task do_write(int addr_i, data_t wdata_i);
      @(posedge v_apb_bus.clk);

      v_apb_bus.addr   <= #1 addr_i;
      v_apb_bus.sel    <= #1 1'b1;
      v_apb_bus.enable <= #1 1'b0;
      v_apb_bus.write  <= #1 1'b1;
      v_apb_bus.wdata  <= #1 wdata_i;

      @(posedge v_apb_bus.clk)
      v_apb_bus.enable <= #1 1'b1;

      @(posedge v_apb_bus.clk)

      v_apb_bus.addr   <= #1 '0;
      v_apb_bus.sel    <= #1 '0;
      v_apb_bus.enable <= #1 '0;
      v_apb_bus.write  <= #1 '0;
      v_apb_bus.wdata  <= #1 'z;
    endtask

    virtual task do_read(int addr_i, output data_t rdata_o);

      @(posedge v_apb_bus.clk)
      v_apb_bus.addr   <= #1 addr_i;
      v_apb_bus.sel    <= #1 1'b1;
      v_apb_bus.enable <= #1 1'b0;
      v_apb_bus.write  <= #1 1'b0;

      @(posedge v_apb_bus.clk)
      v_apb_bus.enable <= #1 1'b1;

      @(posedge v_apb_bus.clk)
      rdata_o = v_apb_bus.rdata;

      v_apb_bus.addr   <= #1 '0;
      v_apb_bus.sel    <= #1 '0;
      v_apb_bus.enable <= #1 '0;
      v_apb_bus.write  <= #1 '0;
    endtask


  endclass

  class base_test extends uvm_test;
    `uvm_component_utils(base_test)
    apb_drv drv;


    int  base_addr = 'h1_0000;

    function new(string name, uvm_component parent);
      super.new(name, parent);
    endfunction

    function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      drv = apb_drv::type_id::create("drv", this);
      uvm_config_db #(virtual apb_t)::set(this, "*", "v_apb_bus", apb_bus);
    endfunction

    virtual task main_phase(uvm_phase phase);
      data_t  wdata;
      data_t  mem_t [int];

      phase.raise_objection(this);

      `uvm_info("base_test", "Start", UVM_MEDIUM);

      for (int i = 0; i < 'h100; i += 4) begin
        apb_bus.addr = base_addr + i;
        wdata = $urandom_range(32'hFFFF, 0);

        drv.do_write(apb_bus.addr, wdata);
        mem_t[apb_bus.addr] = wdata;

        `uvm_info("base_test",
          $sformatf("Write addr == 0x%8h, wdata == 0x%8h", apb_bus.addr, wdata),
          UVM_MEDIUM);
      end

      for (int i = 0; i < 'h100; i += 4) begin
        apb_bus.addr = base_addr + i;
        drv.do_read(apb_bus.addr, apb_bus.rdata);

         if(apb_bus.rdata != mem_t[apb_bus.addr]) begin
            `uvm_error("Conpare Error",
               $sformatf("Read addr == 0x%8h, exdata == 0x%8h, rdata == 0x%8h", apb_bus.addr, mem_t[apb_bus.addr], apb_bus.rdata));
         end
         else begin
            `uvm_info("Compare Success",
               $sformatf("Read addr == 0x%8h, exdata == 0x%8h, rdata == 0x%8h", apb_bus.addr, mem_t[apb_bus.addr], apb_bus.rdata),
               UVM_MEDIUM);
         end

      end

      phase.drop_objection(this);
    endtask

  endclass


  initial
    fork
      clk_gen();
      run_test();
    join_none

endmodule
```

##テストについて
###virtual interface
クラス化されたマスターモデル(UVMドライバ)は、virtual interfaceを通じて接続します。
https://verificationacademy.com/sessions/uvm-connecting-env-dut/rte/japanese-pdf
より引用。
![vif.png](https://qiita-image-store.s3.amazonaws.com/0/32925/effc6cc6-b9aa-f6e2-ab4c-049df743e4ea.png)

この図では、uvm_driverは無くuvm_test以下にuvm_envとuvm_agentがありますが、コードではuvm_testの以下にuvm_driverがあると思って下さい。

virtual interfaceは、インスタンスパスにかかわらず、どのクラスからも呼び出すことのできる機能です。ただし、virtualの名の通り、クラスで接続するにはそれなりの手続きが必要です。

まず、uvm_driverを継承したapb_drvの最初にvirtual interfaceを宣言します。

```sv
virtual apb_t v_apb_bus;
```

インスタンスは、build_phaseで、コンフィギュレーションデータベース(uvm_config_db)からgetすることで行われます。getできなかった時のために、uvm_fatalでキャッチしておくといいでしょう。

```sv
function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  if(! uvm_config_db#(virtual apb_t)::get(this, "", "v_apb_bus", v_apb_bus))
   `uvm_fatal("VIF instance error", {"virtual interface cannot get", get_full_name(), ".v_apb_bus"} );
endfunction
```

コンフィギュレーションデータベースとは、UVMで宣言されたりインスタンス化された変数やオブジェクトを、検証環境で登録しておくためのデータベースです。つまり、クラスを飛び越えて検証全体で管理します。

getということは、putやsetか何かでコンフィギュレーションデータベースにあらかじめ登録しておかないとuvm_fatalがコールされてしまいます。じゃあ、どこで登録されているか？それはuvm_testクラスのbuild_phaseになります。

```sv
function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  drv = apb_drv::type_id::create("drv", this);
  uvm_config_db #(virtual apb_t)::set(this, "*", "v_apb_bus", apb_bus);
endfunction
```

はい、setというのが出てきましたね。この辺は以下の図イメージになります。
https://verificationacademy.com/sessions/uvm-connecting-env-dut/rte/japanese-pdf
より引用。

![setget.png](https://qiita-image-store.s3.amazonaws.com/0/32925/68956a0b-9e6b-9eae-9f03-1b25fe4b0a5b.png)

uvm_envとuvm_agentは先と同様uvm_driverと思って下さい。
このset/getのやり取りでインスタンスが行われます。このset/get関数の引数はあまりよく調べてませんが、二番目の引数がクラスのスコープ範囲を表すっぽいです。つまり、uvm_testでsetを"*"とすると、トップダウンで全体を見渡せるようです。

uvm_driverもuvm_testもuvm_componentを継承したクラスなのに、一方で、こういった階層も意識しなくてはいけません。[オブジェクト指向を恐れるなかれ！](http://lsifrontend.blog100.fc2.com/blog-entry-389.html)ってレベルじゃねーぞ。

##おわりに
以上で、マスターモデルをクラス化し、virtual interfaceを介して、DUTと接続することができました。接続方法が普通のモジュールインスタンスとは全く異なるので、この辺にハードルを感じます。

次回は資料によくあるようなuvmクラスを継承したクラスのpackage化を試みてみます。