LoginSignup
6
5

More than 5 years have passed since last update.

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

Posted at

はじめに

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

コード

共通コード

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

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スレーブモジュールです。

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マスターモデルです。

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のクラス部分は後述します。

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

先に書いた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で接続していくところをやってみようかと思います。

6
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
5