Functional Verification & UVM

Verification is 60–70% of chip effort. Learn the verification plan, directed vs constrained-random testing, testbench architecture (sequencer, driver, monitor, scoreboard, agent, environment), the UVM class library and phases, transactions and sequences, functional and code coverage, the coverage-closure loop, reference models and regression.

A chip bug found after tape-out can cost millions and months. Verification engineers build testbenches that generate stimulus, check the design's responses automatically and measure how thoroughly the design has been exercised (coverage).

The Universal Verification Methodology (UVM, IEEE 1800.2) is a SystemVerilog class library that standardises how these testbenches are built so that components are reusable across projects and teams.

An analogy

A UVM testbench is like an automated car-test facility: robots (drivers) operate the car, sensors (monitors) record what happens, an inspector (scoreboard) compares with the specification, and a checklist (coverage) shows which manoeuvres have been tried.

From directed tests to self-checking testbenches

A directed test applies hand-written stimulus and checks specific outputs. It is easy to write but only finds bugs you already imagined. Self-checking testbenches compare outputs automatically against a model, so thousands of random tests can run overnight in regression.

Constrained-random and coverage

Random stimulus explores corner cases humans miss, and constraints keep it legal. Because random tests are not planned individually, coverage measures progress: code coverage (lines, branches, toggles, FSM states) shows what RTL executed; functional coverage (covergroups, cover properties) shows which features and scenarios from the verification plan occurred.

UVM architecture

A UVM environment is a hierarchy of reusable components:

Sign-off and scaling up

Verification closure means all plan items covered, all assertions and checks passing, bug rate flat. Large SoCs add register models (uvm_reg / RAL), verification IP for standard protocols (PCIe, AXI, USB), emulation and FPGA prototyping to run software, portable stimulus (PSS) for SoC scenarios, and formal verification for control logic. Python-based cocotb is a popular open-source alternative for block-level testbenches.

Formulas

Worked example: Cross coverage size

Given: cp_kind has 2 bins, cp_addr has 3 bins

Find: Cross bins.

Answer: 6 cross bins

Worked example: Constraint distribution

Given: write dist {1 := 60, 0 := 40}, 200 items

Find: Expected writes.

Answer: ≈ 120 writes, 80 reads

Worked example: Regression budget

Given: Each test takes 4 min; 50 licences; 2000 tests

Find: Regression time.

Answer: ≈ 2.7 hours per nightly regression

Transaction, sequence and driver for a simple bus (illustrative)

class bus_item extends uvm_sequence_item;
  rand bit        write;
  rand bit [7:0]  addr;
  rand bit [31:0] data;
  constraint c_addr { addr inside {[8'h00:8'h3F]}; }      // legal register range
  constraint c_mix  { write dist {1 := 60, 0 := 40}; }    // 60% writes
  `uvm_object_utils(bus_item)
  function new(string name = "bus_item"); super.new(name); endfunction
endclass

class rand_seq extends uvm_sequence #(bus_item);
  `uvm_object_utils(rand_seq)
  function new(string name = "rand_seq"); super.new(name); endfunction
  task body();
    repeat (200) begin
      bus_item it = bus_item::type_id::create("it");
      start_item(it);
      if (!it.randomize()) `uvm_error("SEQ", "randomize failed")
      finish_item(it);
    end
  endtask
endclass

class bus_driver extends uvm_driver #(bus_item);
  `uvm_component_utils(bus_driver)
  virtual bus_if vif;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  task run_phase(uvm_phase phase);
    forever begin
      seq_item_port.get_next_item(req);
      @(posedge vif.clk);
      vif.valid <= 1; vif.write <= req.write;
      vif.addr  <= req.addr; vif.wdata <= req.data;
      @(posedge vif.clk iff vif.ready);
      vif.valid <= 0;
      seq_item_port.item_done();
    end
  endtask
endclass

Illustrative code showing the standard structure — it needs a UVM-capable simulator (Questa, VCS, Xcelium, or open-source Verilator 5 with UVM support) and was not simulated here. Constraints make random stimulus legal and weighted; the driver converts abstract items into pin wiggles.

Monitor, scoreboard and functional coverage (illustrative)

class bus_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(bus_scoreboard)
  uvm_analysis_imp #(bus_item, bus_scoreboard) item_imp;
  bit [31:0] model [bit [7:0]];            // reference model: associative-array memory
  function new(string name, uvm_component parent);
    super.new(name, parent);
    item_imp = new("item_imp", this);
  endfunction
  function void write(bus_item t);          // called by the monitor's analysis port
    if (t.write) model[t.addr] = t.data;
    else if (model.exists(t.addr) && model[t.addr] !== t.data)
      `uvm_error("SB", $sformatf("addr %0h: exp %0h got %0h", t.addr, model[t.addr], t.data))
  endfunction
endclass

covergroup bus_cg with function sample(bus_item t);
  cp_kind : coverpoint t.write { bins rd = {0}; bins wr = {1}; }
  cp_addr : coverpoint t.addr  { bins low = {[0:15]}; bins mid = {[16:47]}; bins high = {[48:63]}; }
  kind_x_addr : cross cp_kind, cp_addr;     // every access type in every region
endgroup

The scoreboard checks every read against a reference model — no human inspects waveforms. The covergroup's cross makes sure reads and writes have hit every address region; coverage holes guide new constraints or directed tests.

Common mistakes

Applications

Key takeaways

Going further