FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

Vivado Simulatorを使ってUVMに入門する (14. UVM Testbench Example 2を試す)

前回はこちら:msyksphinz.hatenablog.com

UVMのさらなるテストベンチを試行するために、以下のウェブサイトのサンプルを試してみることにした。

www.chipverify.com

今回は以下のスイッチ。アドレスに対してどちらかの出力に入力を転送する。この時に1サイクルのDelayが入る。

  • switch.sv
module switch
  #(
    parameter ADDR_WIDTH = 8,
    parameter DATA_WIDTH = 16,
    parameter ADDR_DIV   = 8'h3f
    )
(
 input logic clk,
 input logic rstn,
 input logic vld,

 input logic [ADDR_WIDTH-1: 0] addr,
 input logic [DATA_WIDTH-1: 0] data,

 output logic [ADDR_WIDTH-1: 0] addr_a,
 output logic [DATA_WIDTH-1: 0] data_a,

 output logic [ADDR_WIDTH-1: 0] addr_b,
 output logic [DATA_WIDTH-1: 0] data_b
 );

このDUTに対してテストベンチを接続するためのインタフェース switch_if を以下のように定義する。

  • switch_if.sv
interface switch_if (input bit clk);

  logic rstn;
  logic vld;

  logic [ 7: 0] addr;
  logic [15: 0] data;

  logic [ 7: 0] addr_a;
  logic [15: 0] data_a;

  logic [ 7: 0] addr_b;
  logic [15: 0] data_b;

endinterface // switch_if

sequence item

DUTの入出力を接続するためのシーケンス・アイテム。 スコアボードへの容易な接続を可能にする。 rand bit addr, data はランダム生成されることが前提となる。

  • switch_item.sv
class switch_item extends uvm_sequence_item;
rand bit [ 7: 0] addr;
rand bit [15: 0] data;
bit [ 7: 0]      addr_a;
bit [15: 0]      data_a;
bit [ 7: 0]      addr_b;
bit [15: 0]      data_b;

  // Use utility macros to implement standard functions
  // like print, copy, clone, etc
  `uvm_object_utils_begin(switch_item)
    `uvm_field_int (addr, UVM_DEFAULT)
    `uvm_field_int (data, UVM_DEFAULT)
    `uvm_field_int (addr_a, UVM_DEFAULT)
    `uvm_field_int (data_a, UVM_DEFAULT)
    `uvm_field_int (addr_b, UVM_DEFAULT)
    `uvm_field_int (data_b, UVM_DEFAULT)
  `uvm_object_utils_end

  function new (string name = "switch_item");
    super.new(name);
  endfunction // new

endclass // switch_item

Driver

DUTをドライブするためのインタフェース。 build_phase()によってDUTのswitch_vifのインタフェースを受け取る。 run_phase()ではswitch_item を生成して新しいシーケンス・アイテムを受け取り、これをdrive_itemで駆動する。 drive_item()ではvifを駆動してDUTを動作させる。

  • driver.sv
class driver extends uvm_driver #(switch_item);
  `uvm_component_utils(driver)
  function new(string name = "driver", uvm_component parent=null);
    super.new(name, parent);
  endfunction // new

virtual switch_if vif;

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db #(virtual switch_if)::get (this, "", "switch_vif", vif)) begin
      `uvm_fatal ("DRV", "Could not get vif")
    end
  endfunction // build_phase

  virtual task run_phase (uvm_phase phase);
    super.run_phase (phase);
    forever begin
      switch_item m_item;
      `uvm_info ("DRV", $sformatf ("Wait for item from sequencer"), UVM_LOW)
      seq_item_port.get_next_item(m_item);
      drive_item(m_item);
      seq_item_port.item_done();
    end
  endtask // run_phase

  virtual task drive_item (switch_item m_item);
    vif.vld <= 1'b1;
    vif.addr <= m_item.addr;
    vif.data <= m_item.data;

    @ (posedge vif.clk);
    vif.vld <= 1'b0;
  endtask // drive_time

endclass // driver

Monitor

Virtual Interfaceをモニタリングするためのモニタ。 DUTのピンの駆動をモニタリングして、switch_itemに格納し、analysis portに転送する。

build_phase()では、DUTのswitch_vifを取得する。 run_phase()では、2つのタスクを動かしている。sample_port("Thread0")sample_port("Thread1") である。 sample_port()では、vifが駆動した記録をswitch_itemに格納し、mon_analysis_portに書き込む。

これは2段階に分かれており、最初は入力のaddr/dataを読み込み、次のサイクルにaddr_a/data_a/addr_b/data_bを格納する。 この2サイクルの処理をThread0とThread1が順番に行うために、セマフォsema4を使ってsample_port()を交互に動かしていく。

  • monitor.sv
class monitor extends uvm_monitor;
  `uvm_component_utils(monitor)

  function new (string name="monitor", uvm_component parent=null);
    super.new(name, parent);
  endfunction // now

  uvm_analysis_port #(switch_item) mon_analysis_port;
virtual switch_if vif;
semaphore sema4;

  virtual function void build_phase (uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db #(virtual switch_if)::get(this, "", "switch_vif", vif)) begin
      `uvm_fatal("MON", "Could not get vif")
    end
    sema4 = new(1);
    mon_analysis_port = new("mon_analysis_port", this);
  endfunction // build_phase

  virtual task run_phase (uvm_phase phase);
    super.run_phase(phase);
    fork
      sample_port("Thread0");
      sample_port("Thread1");
    join
  endtask // run_phase

  virtual task sample_port (string tag="");
    // This task monitors the intereface for a complete
    // transaction and pushes into the mailbox when the
    // transaction is complete
    forever begin
      @(posedge vif.clk);
      if (vif.rstn & vif.vld) begin
        switch_item item = new;
        sema4.get();
        item.addr = vif.addr;
        item.data = vif.data;
        `uvm_info("MON", $sformatf("T=%0t [Monitor] %s First part over",
                                   $time, tag), UVM_LOW);
        @(posedge vif.clk);
        sema4.put();
        item.addr_a = vif.addr_a;
        item.data_a = vif.data_a;
        item.addr_b = vif.addr_b;
        item.data_b = vif.data_b;
        mon_analysis_port.write(item);
        `uvm_info("MON", $sformatf("T=%0t [Monitor] %s Second part over, item:",
                                   $time, tag), UVM_LOW);
        item.print();
      end // if (vif.rstn & vif.vld)
    end // forever begin
  endtask // sample_port
endclass // monitor

Agent

Agentはドライバ・モニタ・シーケンサをまとめるためのクラス。 d0(Driver)のs0(Sequencer)との接続を行う。

  • agent.sv
class agent extends uvm_agent;
  `uvm_component_utils(agent)
  function new(string name="agent", uvm_component parent=null);
    super.new(name, parent);
  endfunction // new

  driver   d0;                       // Driver handle
  monitor  m0;                       // Monitor handle
  uvm_sequencer #(switch_item) s0;   // Sequencer Handle

  virtual function void build_phase (uvm_phase phase);
    super.build_phase (phase);
    s0 = uvm_sequencer #(switch_item)::type_id::create("s0", this);
    d0 = driver::type_id::create("d0", this);
    m0 = monitor::type_id::create("m0", this);
  endfunction // build_phase

  virtual function void connect_phase (uvm_phase phase);
    super.connect_phase(phase);
    d0.seq_item_port.connect(s0.seq_item_export);
  endfunction // connect_phase

endclass // agent

Scoreboard

uvm_analysis_imp を経由してスコアボードを更新する。 スコアボードに対して書き込みが行われると、switch_item itemを検査し、入力とその出力が想定通りなのかをチェックする。

  • scoreboard.sv
class scoreboard extends uvm_scoreboard;
  `uvm_component_utils (scoreboard)

  function new(string name="scoreboard", uvm_component parent=null);
    super.new(name, parent);
  endfunction // new

  uvm_analysis_imp #(switch_item, scoreboard) m_analysis_imp;

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    m_analysis_imp = new("m_analysis_imp", this);
  endfunction // build_phase

  virtual function write(switch_item item);
    if (item.addr inside {[0:'h3f]}) begin
      if (item.addr_a != item.addr | item.data_a != item.data) begin
        `uvm_error("SCBD", $sformatf ("ERROR! Mismatch addr=0x%0h data=0x%0h addr_a=0x%0h data_a=0x%0h",
                                      item.addr, item.data, item.addr_a, item.data_a));
      end else begin
        `uvm_info("SCBD", $sformatf ("PASS! Match addr=0x%0h data=0x%0h addr_a=0x%0h data_a=0x%0h",
                                     item.addr, item.data, item.addr_a, item.data_a), UVM_LOW);
      end
    end else begin
      if (item.addr_b != item.addr | item.data_b != item.data) begin
        `uvm_error("SCBD", $sformatf ("ERROR! Mismatch addr=0x%0h data=0x%0h addr_b=0x%0h data_b=0x%0h",
                                      item.addr, item.data, item.addr_b, item.data_b));
      end else begin
        `uvm_info("SCBD", $sformatf ("PASS! Match addr=0x%0h data=0x%0h addr_a=0x%0h data_a=0x%0h",
                                     item.addr, item.data, item.addr_b, item.data_b), UVM_LOW);
      end
    end // else: !if(item.addr inside {[0:'h3f]})
  endfunction // write

endclass // scoreboard