[SystemVerilog]interfaceを入れてuvm_driverに似た何かを作る。

##はじめに
[前回の終わり](http://qiita.com/tethys_seesaa/items/8bc05346a2d487895834#2-4)に、モデルをUVMドライバ化と書きましたが、interfaceも含めて一気に書くと、 ~~自分がたぶん忘れる~~わかりにくくなると思いました。
ここではワンクッション置いて、interfaceを入れ、さらにuvmクラスを入れようと思います。

##コード
###共通コード
[前回](http://qiita.com/tethys_seesaa/items/8bc05346a2d487895834#3-1)と同じです。

###interface
全部の信号をinterfaceにまとめ、検証対象(DUT)のメソッドを入れてみました。

```sv:apbif.sv
interface apb_t();

  logic   rst_n, clk;
  logic   sel;
  logic   enable;
  logic   write;
  addr_t  addr;
  data_t  wdata;
  data_t  rdata;

  function logic get_sel();
    return sel;
  endfunction

  function logic get_en();
    return enable;
  endfunction

  function logic get_rw();
    return write;
  endfunction

  function data_t get_data();
    return wdata;
  endfunction

  function data_t put_data(data_t data_i);
    rdata  = data_i;
    return rdata;
  endfunction

  modport master (
    input  rst_n, clk,
    output enable, sel, write, addr, wdata,
    input  rdata
    );

  modport slave (
    import  get_en, get_rw, get_sel, get_data, put_data,
    input   rst_n, clk,
    input   enable, sel, write, addr, wdata,
    output  rdata
    );

endinterface
```

##DUT
interfaceに応じて、信号名を変えています。

```sv:apb_slave.sv
module apb_slave #(
  parameter OFFSET = 32'h0000_0000
  )(
  apb_t.slave    apb_slv
  );

  localparam MW = 256;

  addr_t  addr_d;
  data_t  [(MW -1) : 0 ] mem ;

  always_ff @(negedge apb_slv.rst_n, posedge apb_slv.clk)
    if(! apb_slv.rst_n)
      addr_d <= '0;
    else
      addr_d <= apb_slv.addr;

  always_ff @(negedge apb_slv.rst_n, posedge apb_slv.clk)
    if(! apb_slv.rst_n)
      mem <= '0;
    else
      if(apb_slv.get_en() && apb_slv.get_sel() && apb_slv.get_rw() && (addr_d>=OFFSET) && (addr_d< OFFSET + MW) )
        mem[addr_d - OFFSET ] <= apb_slv.get_data();

  always_comb begin
    if(apb_slv.get_en() && apb_slv.get_sel() && (! apb_slv.get_rw()) && (addr_d>=OFFSET) && (addr_d < OFFSET + MW) )
      apb_slv.put_data( mem [addr_d - OFFSET] );
    else
      apb_slv.rdata = '0;
  end

endmodule
```

###モデル
こちらもinterfaceに合わせます。

```sv:apb_master_model.sv
module abp_master(
  apb_t.master   apb_mst
  );

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

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

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

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

    @(posedge apb_mst.clk)

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

  task do_read(int addr_i, output data_t rdata_o);

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

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

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

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

  initial
    init();

endmodule
```

###テスト
intterfaceを入れたので、モジュール内で宣言する信号が無くなりました。後は変わりません。UVMのクラス部分は後述します。

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

  apb_t apb_bus();

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

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

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

  class base_drv extends uvm_driver;
    `uvm_component_utils(base_drv)

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

    function void build_phase(uvm_phase phase);
      super.build_phase(phase);
    endfunction

    virtual task reset_phase(uvm_phase phase);

      phase.raise_objection(this);

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

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

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

      phase.drop_objection(this);

    endtask

    virtual task main_phase(uvm_phase phase);
      phase.raise_objection(this);
      `uvm_info("test", "Start", UVM_MEDIUM);
      `uvm_fatal(get_type_name(), "No test");
      phase.drop_objection(this);
    endtask

  endclass

  class test_drv extends base_drv;
    `uvm_component_utils(test_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);
    endfunction

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

      phase.raise_objection(this);

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

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

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

        `uvm_info("test_drv",
          $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;
        tu.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

  class base_test extends uvm_test;
    `uvm_component_utils(base_test)

    test_drv drv;

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

    function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      drv = test_drv::type_id::create("drv", this);
    endfunction

  endclass

  initial
    fork
      clk_gen();
      run_test();
    join_none

endmodule
```

##テストについて
いろんな資料を見ると、uvm_driverというクラスがDUTとのピンレベルでのやり取りを行うようです。しかし、[前回](http://qiita.com/tethys_seesaa/items/8bc05346a2d487895834)まで見てきたように、これまでDUTを叩いていたのはuvm_testクラスです。UVMのソースコードを拝見すると、uvm_testクラスとuvm_driverクラスは「uvm_component」というクラスを継承しています。

https://verificationacademy.com/sessions/uvm-connecting-components/rte/japanese-pdf
より引用。

![uvm_class_layer.png](https://qiita-image-store.s3.amazonaws.com/0/32925/256c291e-2912-650f-d9d3-3018962dd24b.png)


ということで、uvm_testでもピンレベルのやり取りが可能です。 **この辺の緩さがなんとも微妙ですね。** よって、このコードでは、継承するクラスをuvm_testクラスからuvm_driverクラスに変更しただけです(名前を変えただけ)。

その代わり、uvm_driverに変更したからuvm_testクラスを持ってきて継承し、テストを記述しなくてはなりません。その辺が、「class base_test extends uvm_test...」辺りの記述になります。

このクラス内では、先のtest_drvクラスを宣言し、インスタンスします。
uvm_componentクラスを継承してきたクラスは、new関数でインスタンスするのではなく、createと呼ばれるメソッド（おなじない）でインスタンスします。書式はあんな感じです。

##おわりに
ということで、uvm_driverクラスを使って、uvm_driverに似た何かを作ってみました。APBマスターモデルをクラス化しないと、uvm_driverとは **個人的に言えるとは思えません。**

というわけで、次回はいつかAPBマスターをクラス化し、UVMドライバとしてDUTとinterfaceを介してつないでみたいと思います。