[SystemVerilog]packageを作る。

##はじめに
[前回](http://qiita.com/tethys_seesaa/items/2620f8b635bc351f6fbb)で、UVMドライバを使ったテストまで行きました。
テスト階層がいろいろ長くなってきたので、今回はclass毎にファイルを作り、パッケージ化を行います。
ファイルで分離化されたことで、クラスのスコープ範囲を意識してコードを作らなくてはいけません。 **前回よりも今回の方がストレスがかかりました。**

##コード
###共通コード、DUT、Interface
[これまで](http://qiita.com/tethys_seesaa/items/3f41d4780bdbf4a6f6b3#2-2)と同様です。

###package
コードをimportしたりincludeして、apb_pkgというパッケージを作成します。uvmと共通コード、後述するドライバとテストを入れています。
拡張子の解釈がいろいろあると思いますが、ここではclassのみ書かれているコードを.svhとしました。

```sv:apb_pkg.sv
package apb_pkg;

  import uvm_pkg::*;
  `include "uvm_macros.svh"

  `include "common.sv"
  `include "apb_drv.svh"
  `include "test_lib.svh"

endpackage
```

###ドライバ
[前回のtb.sv](http://qiita.com/tethys_seesaa/items/2620f8b635bc351f6fbb#3-2)から、apb_drvクラスを移します。

```sv:apb_drv.svh
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
```

###テスト
base_testクラスを移して、追記・削除を一部分行っています。

まず、削除したのは、build_phaseにあるコンフィギュレーションデータベース登録(set)です。これは、tb.svに移すことにしました。

追加したのは、uvm_table_printerクラスをインスタンスし、end_of_elaboration_phaseメソッド(おなじない)で、クラス階層を表示させるようにしました。このメソッド名で推定する限り、テストモジュールがbuildし終わり、テストが走る前のフェイズで実行されると思われます。表示内容は後述します。

また、apb_drvクラスが別ファイルに移動したので、apb_drvクラスで呼び出しているinterface部分の信号名には、drv.とアタマをつけます。

```sv:test_lib.svh
class base_test extends uvm_test;
  `uvm_component_utils(base_test)

  apb_drv             drv;
  uvm_table_printer  printer;

  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);
  endfunction

  function void end_of_elaboration_phase(uvm_phase phase);
    `uvm_info(get_type_name(), this.sprint(printer), UVM_MEDIUM);
  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
      drv.v_apb_bus.addr = base_addr + i;
      wdata = $urandom_range(32'hFFFF, 0);

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

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

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

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

    end

    phase.drop_objection(this);
  endtask

endclass
```

###tb.sv(テストトップ)
apb_pkgをimportし、base_testクラスで削除したコンフィギュレーションデータベースの登録をinital部分で実施しています。setの第一引数(uvm_top)はUVM環境でのテストトップ階層を表すおまじないです。
 **いかにもUVMらしい少ない行数になりましたね。**

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

  import uvm_pkg::*;
  import apb_pkg::*;

  apb_t apb_bus();

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

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

  initial begin

    uvm_config_db #(virtual apb_t)::set(uvm_top, "*", "v_apb_bus", apb_bus);

    fork
      clk_gen();
      run_test();
    join_none

  end

endmodule
```

##コンフィギュレーションデータベースについて
コンフィギュレーションデータベースのメソッドについて自分なりに整理しておきます。

```sv
uvm_config_db #(virtual apb_t)::set(uvm_top, "*", "v_apb_bus", apb_bus);
uvm_config_db#(virtual apb_t)::get(this, "", "v_apb_bus", v_apb_bus);
```

コンフィギュレーションデータベースはsetで設定し、getで取得するのは前回も述べました。

第三引数はフィールド名、第四引数は信号名を表します。フィールド名は検証環境全体で、コンフィギュレーションデータベースと紐付けるために利用されるので、文字列になっています。よって、"*apb_bus"や"v_*"といったワイルドカードでマッチさせることができます。

第四引数はそのクラス・モジュール内で使われている信号名となります。

つまり、フィールド名で紐つけて階層をまたがったコンフィグレーションのマッチングを行っているようです。 **第一、第二引数はまだよくわかっていません。** この辺はもう少し環境を組み上げてから理解しようかと思っています。

##テスト実行
シミュレーションがRunする前に、base_testクラスで記述したend_of_elaboration_phaseメソッドが走り、コンソールに以下のような感じでクラス構造が表示されます。

```
Name               Type                    Size  Value
------------------------------------------------------
uvm_test_top       base_test               -     @462 
  drv              apb_drv                 -     @470 
    rsp_port       uvm_analysis_port       -     @487 
    seq_item_port  uvm_seq_item_pull_port  -     @478 
------------------------------------------------------
```

なるほど… **全くわからん。**

##シミュレーションコマンド
`includeが出てきたので、+incdirを使ってこんな感じでやれば動くと思います。common.svは検証対象(DUT)でも使うので、再度指定します。

###VCSの場合
```sh:vcs.sh
vcs \
    -R \
    -full64 \
    -sverilog \
    -ntb_opts uvm \
    +incdir+./ \
    apb_pkg.sv common.sv apbif.sv apb_slave.sv tb.sv \
    +UVM_TESTNAME=base_test
```

###Incisiveの場合

```sh:irun.sh
irun \
     -64 \
     -uvm \
     +incdir+./ \
     apb_pkg.sv common.sv apbif.sv apb_slave.sv tb.sv \
     +UVM_TESTNAME=base_test
```

###QuestaSimの場合
```sh:que.sh
vlib work
vlog +incdir+./ apb_pkg.sv common.sv apbif.sv apb_slave.sv tb.sv
vsim -64 -c tb -do "run -all" +UVM_TESTNAME=base_test 
```

ISim？知らない子ですね…。

##おわりに
今回、SystemVerilogのOOP部分のスコープやUVM独特のフローを身につけていかないと結構厳しいところがありました。 ~~こんなもん本当に役に立つのかはさておき、~~ オープンライブラリですので、元のコードと突き合わせつつ、理解を深める必要がありそうです。
次回は、シーケンサーを登場させて、ドライバとつなげてみようかと思います。