02. 簡単な仕様のモデルの作り方と実行

構成を理解する

BFM(バスファンクショナルモデル)の、UVMでのclass構成を示します。

以下に示したclassをextendsして構成します。

  • class uvm_env
    • class uvm_agent
      • class uvm_driver
      • class uvm_sequencer
      • class uvm_monitor

他に

  • class uvm_sequence_item

が必要になりますが、これだけ別だしなのは、uvm_env以下にインスタンスして使わないからです。

個々のclassを簡単に説明します。

  • uvm_driver
    • バスの動きを表現するclassになります。DUT(論理回路)の信号をドライブする唯一のclassです。DUTとは、interface経由で接続します。
  • uvm_sequencer
    • uvm_sequence(テストベクタ、テストシナリオ)をuvm_driverに渡す役目をします。通常の構成では、「決め打ち」記述ですみます。
  • uvm_sequence_item
    • 「トランザクション」情報を、uvm_sequenceからuvm_driverへ渡すのに使います。
  • uvm_monitor
    • バスモニタです。
  • uvm_env
    • モデルの最上位になります。この下にuvm_agentをインスタンスします。

シンプルなモデル

  • 以下の様な仕様のモデルを作ってみたいと思います。
rstz    ____|~~~~~~~~~~~~~~~~~~~~~~~~~~~~
clk     ~~~~~|___|~~~|___|~~~|___|~~~|___
valid   _________|~~~~~~~|_______________
addr    XXXXXXXXXX addr  XXXXXXXXXXXXXXXX
data    XXXXXXXXXX data  XXXXXXXXXXXXXXXX

各classのサンプル記述

  • 記述のひな形は、UVMクラスライブラリのexamples/integrated 以下にありますが、そこから肉を削ぎ落として書き換えたものを示します。

uvm_sequence_item

class sample_seq_item extends uvm_sequence_item;
  logic [7:0] addr, data;
  `uvm_object_utils_begin(sample_seq_item)

`uvm_field_int (addr, UVM_DEFAULT)

`uvm_field_int (data, UVM_DEFAULT)

  `uvm_object_utils_end
  function new (string name = "sample_seq_item_inst");
    super.new(name);
  endfunction : new
endclass
  • uvm_sequence_itemには、「driverを制御するのに使う」メンバを定義します。
  • 今回の場合は、validはaddrとdataをドライブするときに 1 にする仕様なので、validは不要です。
  • validをプロトコル違反させたいときは、validを加えてもいいです。

uvm_sequencer

class sample_sequencer extends uvm_sequencer #(sample_seq_item);
  `uvm_component_utils(sample_sequencer)
  `uvm_new_func
endclass
  • `uvm_component_utilsマクロは、「UVMファクトリーにこのclassを登録する」と覚えてください。
  • `uvm_new_funcは、以下のコードです。
  • 1行目の「sample_seq_item」は、トランザクション情報を持つuvm_sequence_itemをextendsしたclassです。
----- ./src/macros/uvm_object_defines.svh, line=396
`define uvm_new_func \
  function new (string name, uvm_component parent); \
    super.new(name, parent); \
  endfunction

uvm_driver

class sample_driver extends uvm_driver #(sample_seq_item);
  `uvm_component_utils(sample_driver)
  virtual sample_if vif;
  function new (string name="sample_driver", uvm_component parent);
    super.new(name, parent);
  endfunction
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if(!uvm_config_db#(virtual sample_if)::get(this, "", "vif", vif))
       `uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
  endfunction: build_phase
  task run_phase(uvm_phase phase);
    sample_seq_item trans_item;
    // while reset
    vif.valid = 1'b0;
    @(posedge vif.rstz);// wait negate reset
    forever begin
      seq_item_port.get_next_item(trans_item);
      // get several value from trans_item and drive
      // signals, receive signals via virtual interface
      @(posedge vif.clk);
      vif.valid <= 1'b1;
      vif.addr  <= trans_item.addr;
      vif.data  <= trans_item.data;
      @(posedge vif.clk) vif.valid <= 1'b0;
      seq_item_port.item_done();
    end
  endtask
endclass
  • virtual sample_if vif; は、interface定義です。「virtual」をつけます。
interface sample_if(input logic clk, rstz);
  logic [7:0] addr, data;
  logic valid;
endinterface
  • build_phaseがややこしい記述になっていますが、これは「ほぼコピペ」でいけます。変えるところは、"vif" です。後述する、module定義のテストベンチトップと関係があります。
  • task run_phaseは、Simulation実行時に自動で呼び出されます。中身は
    • valid信号値をリセットして
    • リセット解除を待って
    • 無限ループを作って(forever)
    • seq_item_port.get_next_itemで、sequence(テストベクタ・テストシナリオ)からの命令を「待ちます」
    • 受け取ったら、クロック同期させて、vifに値をセットします。
    • validを 0 にしてプロトコルを完了させたら、
    • seq_item_port.item_done(); を実行します。これを実行するまでは、テストベクタ・テストシナリオは待たされます。

uvm_monitor

class sample_monitor extends uvm_monitor;
  `uvm_component_utils(sample_monitor)
  virtual sample_if vif;
  function new (string name="sample_monitor", uvm_component parent);
    super.new(name, parent);
  endfunction
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if(!uvm_config_db#(virtual sample_if)::get(this, "", "vif", vif))
       `uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
  endfunction: build_phase
  task run_phase(uvm_phase phase);
    fork
      //check_clock;
      check_trans;
    join
  endtask
  task check_trans;
    forever begin
      @(posedge vif.valid) uvm_report_info("MON", $sformatf("addr=%02Xh, data=%02Xh", vif.addr, vif.data));
    end
  endtask
  task check_clock;
    forever begin
      wait(vif.clk===1'b0);
      uvm_report_info("MON", "fall clock");
      wait(vif.clk===1'b1);
      uvm_report_info("MON", "rise clock");
    end
  endtask
endclass
  • virtualでinterfaceをインスタンスするのは、driverと同じです。
  • build_phaseもdriverと同じです。
  • task run_phaseの中で、モニタの動作を記述します。

uvm_agent

class sample_agent extends uvm_agent;
  `uvm_component_utils(sample_agent)
  `uvm_new_func
  sample_driver    driver;
  sample_sequencer sequencer;
  sample_monitor   monitor;
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `build_component(sample_driver,driver)
    `build_component(sample_sequencer,sequencer)
    `build_component(sample_monitor,monitor)
  endfunction
  function void connect_phase(uvm_phase phase);
    driver.seq_item_port.connect(sequencer.seq_item_export);
  endfunction
endclass
  • driver, sequencer, monitorをインスタンスします。
  • build_phaseで、`build_componentマクロを実行して、各コンポーネントを生成します。このマクロはぼくのオリジナルです。
`define build_component(NAME,INST) \
  INST = NAME::type_id::create(`"INST`", this);
  • connect_phaseで、sequencerとdriverをつなぎます。

uvm_env

class sample_env extends uvm_env;
  `uvm_component_utils(sample_env)
  `uvm_new_func
  sample_agent agent;
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `build_component(sample_agent,agent)
  endfunction
endclass
  • agentをインスタンスします。
  • build_phaseでagentを生成します。

uvm_test

class test extends uvm_test;
  `uvm_component_utils(test)
  `uvm_new_func
  sample_env env;
  virtual function void build_phase(uvm_phase phase);
    uvm_config_db#(uvm_object_wrapper)::set(this,
        "env.agent.sequencer.run_phase","default_sequence",
        issue_one_trans_seq::type_id::get());
    `build_component(sample_env,env)
    super.build_phase(phase);
  endfunction
endclass
  • envをインスタンスします。
  • build_phaseで、
    • sequencerの「default_sequence」に、動かしたいsequence(テストベクタ・テストシナリオ)を指定します。
    • envを生成します。

sequence(テストベクタ・テストシナリオ)

virtual class sample_base_sequence extends uvm_sequence #(sample_seq_item);
  function new(string name="sample_base_seq");
    super.new(name);
  endfunction
  virtual task pre_body();
    if (starting_phase!=null) begin
       `uvm_info(get_type_name(),
                 $sformatf("%s pre_body() raising %s objection",
                           get_sequence_path(),
                           starting_phase.get_name()), UVM_MEDIUM);
       starting_phase.raise_objection(this);
    end
  endtask
  // Drop the objection in the post_body so the objection is removed when
  // the root sequence is complete.
  virtual task post_body();
    if (starting_phase!=null) begin
       `uvm_info(get_type_name(),
                 $sformatf("%s post_body() dropping %s objection",
                           get_sequence_path(),
                           starting_phase.get_name()), UVM_MEDIUM);
    starting_phase.drop_objection(this);
    end
  endtask
endclass
///////////////////////////////////////////////
class issue_one_trans_seq extends sample_base_sequence;
  `seq_head(issue_one_trans_seq)
  virtual task body();
    sample_seq_item trans_item;
    $display("I am issue_one_trans_seq");
    `uvm_create(trans_item)
    trans_item.addr = 8'h00;
    trans_item.data = 8'h10;
    `uvm_send(trans_item)
    #1000ns;
  endtask
endclass
  • sample_base_sequenceを定義し、pre_body、post_bodyにオブジェクションの記述を入れておきます。
  • sample_base_sequenceをextendsし、
    • `seq_headを記述します。このマクロはぼくのオリジナルです。
`define seq_head(NAME) \
  function new(string name=`"NAME`"); \
    super.new(name); \
  endfunction \
\
  `uvm_object_utils(NAME)
  • task bodyを定義します。uvm_testクラスの、default_sequence定義のところに指定されたsequenceは、Simulation実行時にbodyが呼び出されます。
    • このサイトでは、無料のmodelsimを使っているため、uvm_sequence_itemを`uvm_createし、その中に値を書き込んで`uvm_sendしています。

テストベンチトップ

`timescale 1ps/1ps
module tb_top;
// UVM ////////////////////////////////////////////////
  `include "./uvm-1.1d/src/uvm_macros.svh"
  import uvm_pkg::*;
  // model
  `include "sample.svh"
  `include "test.sv"
//////////////////////////////////////////////////////
  logic rstz, clk;
//
// rstz   _________|^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// clk    ^^^^^^^^^^^^^^|__|^^|__|^^|__|^^|__|^^|__|^^
//
  sample_if sif(clk, rstz);// interface
  initial begin
    fork
      begin
        clk = 1'b1;
        #100ns;
        forever #50ns clk = ~clk;
      end
      begin
        rstz = 1'b0;
        #100ns; 
        rstz = 1'b1;
      end
    join
  end
  initial begin
    uvm_config_db#(virtual sample_if)::set(uvm_root::get(), "*", "vif", sif);
    run_test();
  end
endmodule
  • `include "sample.svh"は、上記UVMモデルの各ファイルを`includeしてまとめたものです。
  • initial文の中で、
    • uvm_config_dbで、このmoduleにインスタンスしているinterfaceの情報を書き込みます。uvm_driverとuvm_monitorは、build_phase内のuvm_config_dbで「書き込まれた情報を取得」します。取得 に失敗すると、Simulation実行時にError表示されます。コンパイル、エラボレーションは通ってしまいます。
    • run_test()で、UVMテストベンチを走らせます。

コンパイル(modelsim)

  • 例:vlog -sv tb_top.sv +incdir+./uvm-1.1d/src
    • module tb_top内でincludeするファイルのあるパスを、適時「+incdir+」などで付け加えます。

実行(modelsim)

  • vsim -c tb_top +UVM_TESTNAME=test -do "run -all;quit"
  • 「+UVM_TESTNAME」というコマンドラインオプションを使って、uvm_testのクラス名を指定します。