Skip to content

Instantly share code, notes, and snippets.

@edcote
Last active September 14, 2018 17:06
Show Gist options
  • Save edcote/7d028e7a9bdd00821707fd8a67fe926a to your computer and use it in GitHub Desktop.
Save edcote/7d028e7a9bdd00821707fd8a67fe926a to your computer and use it in GitHub Desktop.
UVM Cookbook

UVM Basics

UVM employs a layered, object-oriented approach to testbench development.

uvm_sequence_item is a uvm_object that contains data fields to implement protocols and communicate with with DUT. uvm_driver is responsible for converted the sequence item(s) into "pin wiggles". The sequence_item(s) are provided by one uvm_sequence objects that define stimulus at the transaction level and execute on the agent's uvm_sequencer component. The sequencer is responsible for executing the sequences, arbitrating between them, and routing sequence items between the driver and the sequence.

UVM agents have a configuration object that allows the test writer to control how the testbench is assembled and executed.

Components

uvm_component class inherits from uvm_report_class. The reporting process uses the component static hierarchy to add scope to the component.

Factory

uvm_components are registered with the UVM factory. When a component is created uding the build phase, the factory is used to contruct the component object. The factory enables a component to be swapped for another derived type using a factory override.

  • Factory registration macros and construction default
class my_component extends uvm_component;
`uvm_component_utils(my_component)
function new(string name="my_component", uvm_component parent=null);
  super.new(name, parent);
endfunction
//[..]

class my_param_component #(int ADDR_WIDTH=20, int DATA_WIDTH=23) extends uvm_component;
typedef my_param_component #(ADDR_WIDTH, DATA_WIDTH) this_t;
`uvm_component_utils(this_t)
//[..]

class my_item extends uvm_sequence_item;
`uvm_object_utils(my_item)
function new(string name="my_item");
  super.new(name);
endfunction
//[..]
  • Factory registration macros and construction default
class env extends uvm_env;
my_component m_my_component;
my_param_component #(.ADDR_WIDTH(32), .DATA_WIDTH(32)) m_my_p_component;

function void build_phase(uvm_phase phase);
  m_my_component = my_component::type_id::create("m_my_component", this);
  m_my_p_component = my_p_component#(32, 32)::type_id::create("m_my_p_component", this);
endfunction : build_phase

task run_phase(uvm_phase phase);
  my_seq test_seq;
  my_param_seq #(.ADDR_WIDTH(32),.DATA_WIDTH(32)) p_test_seq;
  
  test_seq = my_seq::type_id::create("test_seq");
  p_test_seq = my_param_seq#(32, 32)::type_id::create("p_test_seq");
  // [..]
endtask : run_phase

Phasing

Three groups of phases, executed in the following order:

  • build phases
    • build: construct compnents, bottom->top
    • connect : connect components, via TLM connections, top->bottom
    • end_of_elaboration : final adjustments, bottom->top
    • start_of_simulation
  • run phases
    • run
      • pre_reset: waiting for a power-good signal to be active
      • reset: generate a reset and put interface into its default state
      • post_reset: do training or rate negotiation
      • pre_configure:
      • configure: to program registers or memories
      • post_configure: wait for effects of configuration to propagate through dut
      • pre_main
      • main: stimulus is applied to dut, completes when all stimulus exhausted to timeout occurs
      • post_main
      • pre_shutdown
      • shutdown: to ensure the effects of stimulus have propagated through the dut, all results drained
      • post_shutdown
  • cleanup phases
    • extract: retrived and process info from scoreboard, calcucate statistics, top->bottom
    • check: check that dut behaved correctly and identify errors
    • report: report on any errors
    • final: *

Driver

Responsible for all communications with DUT. Driver gets sequence item from sequencer and drives to DUT. In certain applications it may receive a response, convert to sequence item, and send back to the sequencer.

Monitor

Interesting how this experiment adds a BFM layer implemented in a SystemVerilog interface rather than in the driver itself. I like that.

Example:

class wb_bus_monitor extends uvm_monitor;
  `uvm_component_utils(wb_bus_monitor)

  uvm_analysis_port #(wb_txn) wb_mon_ap;
  virtual wb_bus_monitor_bfm m_bfm;
  wb_config m_config;
  
  function new(string name, uvm_component parent)
    super.new(name, parent);
  endfunction
  
  function void build_phase(uvm_phase phase)
    super.build_phase(phase);
    wb_mon_ap = new("wb_mon_ap", this);
    m_config = wb_config::get_config(this); // get configuration object
    m_bfm = m_config.wb_mon_bfm;
    m_bfm.proxy = this;
  endfunction
  
  task run_phase(uvm_phase phase)
    m_bfm.run(); // interesting ...
  endtask
  
  function void notify_transaction(wb_txn item); // used by BFM to return txns
    wb_mon_ap.write(item)
  endfunction
  
endclass

interface wb_bus_monitor_bfm(wishbone_bus_if wb_bus_if);
import wishbone_pkg::*;

wb_bus_monitor proxy;

task run();
  wb_txn txn;
  forever @(posedge wb_bus_if.clk)
    // FIXME: control protocol activity here
    proxy.notify_transaction(txn);
  end
endtask

endinterface
  • Copy-on-write policy

Objects in SystemVerilog are handle based. When a monitor writes a transaction handle out of its analysis port, only the handle gets copied and broadcast to subscribers. To prevent overwriting the same object memory in the next iteration of the loop, the handle that is broadcast should point to a separate copy of the transaction object that the monitor created. This can be accomplished in two ways:

  • Create a new transaction object in each iteration of the loop
  • Reuse the same transaction objecct in each iteration, but clone the object immediately prior to broadcast.

Agent

  • Configuration object

The configuration objects contains all virtual interfaces, active/passive, agent configuration settings (i.e., has_scoreboard), id information (i.e., to tell monitor which bit to look at on the bus).

  • Connect phase

Connections go from export to port.

Example:

class apb_agent extends uvm_component;
`uvm_component_utils(apb_agent)

apb_agent_config m_cfg;
uvm_analysis_port #(apb_seq_item) ap;
apb_monitor m_monitor;
abp_sequence m_sequencer;
abp_driver m_driver;
abp_coverage_monitor m_fcov_monitor;

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

function void build_phase(uvm_phase phase);
  if (m_cfg == null)
    if (!uvm_config_db#(apb_agent_config)::get(this, "", "apb_agent_config", m_cfg) `uvm_fatal(//[..]

  m_monitor = apb_monitor::type_id::create("m_monitor", this);
  if (m_cfg.active) begin
    m_driver = apb_driver::type_id::create("m_driver", this);
    m_sequencer = apb_sequencer::type_id::create("m_sequencer", this);
  end
  if (m_cfg.has_functional_coverage)
    m_fcov_monitor = apb_coverage_monitor::type_id::create("m_fcov_monitor", this);
endfunction

function void connect_phase(uvm_phase phase);
  ap = m_monitor.ap;
  if (m_cfg.active == UVM_ACTIVE)
    m_driver.seq_item_port.connect(m_sequencer.seq_item_export); // export -> port
  if (m_cfg.has_functional_coverage)
    m_monitor.ap.connect(m_fcov_monitor.analysis_export);
endfunction
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment