[SystemVerilog]既存のモジュールを無理矢理UVMのフローに載せてみる。

##はじめに
[前回は](http://qiita.com/tethys_seesaa/items/67438fa56501c42169c2)Hollow Worldを出すまで到達しました。今回は実際にDUT(検証対象)を入れていきます。
UVMの解説だと、TLMがぁシーケンサーがぁドライバがぁ…とかありますが、それはまず置いておいて、ここでは普通のSystemVerilogのモデル、DUTをUVMの検証環境に載せてみます。
ここでは、APBバス通信をサンプルにやってみます。

##コード
###共通コード
UVMライブラリのインポートと、APBのアドレス/データ幅を決めます。

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

parameter MEM_DEPTH = 256;
parameter ADR_WIDTH = 32;
parameter DAT_WIDTH = 32;

typedef logic [ADR_WIDTH-1:0] addr_t;
typedef logic [DAT_WIDTH-1:0] data_t;
```

###DUT
中にレジスタアレイを持つAPBスレーブモジュールです。

```sv:apb_slave.sv
module apb_slave #(
  parameter OFFSET = 32'h0000_0000,
  parameter MW     = 32
  )(
  input  logic   rst_n, clk,
  input  logic   sel,
  input  logic   enable,
  input  logic   write,
  input  addr_t  addr,
  input  data_t  wdata,
  output data_t  rdata
  );

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

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

  always_ff @(negedge rst_n, posedge clk)
    if(! rst_n)
      mem <= '0;
    else
      if(write && sel && enable && (addr_d>=OFFSET) && (addr_d< OFFSET + MW) )
        mem[addr_d - OFFSET ] <= wdata;

  always_comb begin
    if(sel && (! write) && enable && (addr_d>=OFFSET) && (addr_d < OFFSET + MW) )
      rdata = mem[addr_d - OFFSET];
    else
      rdata = '0;
  end

endmodule
```

###モデル
リードライト機能を持つAPBマスターモデルです。

```sv:apb_master_model.sv
module abp_master(
  input  logic   rst_n, clk,
  output logic   sel,
  output logic   enable,
  output logic   write,
  output addr_t  addr,
  output data_t  wdata,
  input  data_t  rdata
  );

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

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

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

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

    @(posedge clk)

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

  task do_read(int addr_i, output data_t rdata_o);

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

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

    @(posedge clk)
    rdata_o = rdata;

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

  initial
    init();

endmodule
```

###テスト
UVMのフローに沿って記述します。DUT、モデルのインスタンスとクロック発生を実施しています。テスト内容は、ランダムデータの書き込み読み出しと値の確認を行っています。UVMのクラス部分は後述します。

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

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

  apb_slave  #(.OFFSET(32'h0001_0000), .MW(MEM_DEPTH))
    dut (.*);

  abp_master tu(.*);

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

  class test_base extends uvm_test;
    `uvm_component_utils(test_base)

    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_test", "Reset Start", UVM_MEDIUM)

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

      `uvm_info("base_test", "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 test0 extends test_base;
    `uvm_component_utils(test0)

    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  rdata, wdata;
      data_t  mem_t [int];

      phase.raise_objection(this);

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

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

        tu.do_write(addr, wdata);  // Write data to DUT
        mem_t[addr] = wdata;        // Write data to TB

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

      for (int i = 0; i < 256; i=i+4) begin
        addr = base_addr + i;
        tu.do_read(addr, rdata);

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

      end

      phase.drop_objection(this);
    endtask

  endclass

  initial
    fork
      clk_gen();
      run_test();
    join_none

endmodule
```

##テストについて
UVMのクラスでは、uvm_testクラスから継承したtest_baseクラスと、さらにそれを継承したtest0クラスがあります。uvm_component_utilsへのクラス名の登録と、function newは前回と同様のおまじないです。

function void build_phaseもおまじないと思って下さい。

で、taskの説明の前に、UVMのフロー(フェイジング)についてちょっと説明しておきます。

UVMではテストフローが決められており、それぞれのフローに相当するクラスを継承してコーディングすれば、UVMのフローでテストが実行されます。UVMは以下のフローを持ちます。
https://verificationacademy.com/cookbook/phasing
より引用。
![uvm_flow.png](https://qiita-image-store.s3.amazonaws.com/0/32925/803c79d5-629c-ba65-1ee8-f03951eff68e.png)

先に書いたfunction void build_phaseが図の最初にある「Build Phases」に相当します。
タスクの部分は、「Run Phases」に相当します。Run Phasesは図のように細々としたステップがありますが、特に記述しなければ、UVMのデフォルトのステップで実行されます。

さてコードに戻り、taskではreset_phaseとmain_phaseをチョイスして、テストシナリオを記述しています。test_baseクラスではreset_phaseにてリセットの実行、main_phaseではメッセージを出すだけとなっています。

test0クラスではmain_phaseでテスト内容を記述しています。

###テストの実行
+UVM_TESTNAME=test_baseでテスト実行するとこんな感じでFATALを出します。

```

UVM_FATAL tb.sv(53) @ 95: uvm_test_top [test_base] No test
```

これは、test_baseクラスではreset_phaseでDUTのリセット後、main_phaseではテストを実行していないため、uvm_fatalがコールされ、FATALとなります。

+UVM_TESTNAME=test0で実施すると、テストが正常実行されます。test0クラスはtest_baseクラスを継承しているため、reset_phaseが親クラスで実行されたあと、main_phaseでテストが実行されるためです。

##おわりに
UVMはテスト手順が決まっていて、そこを拡張してピースを当てはめると、一応それらしいUVMテストが実施できます。
次はいつになるかわかりませんが、モデルをUVMドライバ化して、DUTとInterfaceで接続していくところをやってみようかと思います。