Day 07 / 25
← Day 06 Day 08 →
HomeVerificationDay 7 — UVM Testbench Architecture
Track 2 — UVM

UVM Testbench Architecture

A UVM environment is a hierarchy of components — test at the top, env below it containing agents, each agent wrapping a driver, sequencer, and monitor, with scoreboards and coverage tied in via analysis ports. Understanding how these pieces connect is the foundation of all UVM work.

⏱ 30 min read📖 Day 7 of 25🎯 Agent · TLM · Scoreboard
Contents
  1. TB Block Diagram
  2. TLM Connections Overview
  3. uvm_agent — Active vs Passive
  4. uvm_env
  5. uvm_test
  6. Complete Code Examples
  7. How Analysis Port Broadcasts
  8. File and Class Naming

1. UVM Testbench Block Diagram

uvm_test uvm_env uvm_agent (active) uvm_driver uvm_sequencer uvm_monitor analysis port (ap.write) uvm_scoreboard Coverage Model DUT (design under test) vif

2. TLM Connections Overview

TLM (Transaction Level Modeling) ports provide type-safe, direction-enforced connections between components. The main TLM channels in UVM:

Port TypeDirectionUse Case
uvm_analysis_portProducer → 0..N consumersMonitor broadcasts observed transactions
uvm_analysis_impConsumer sideScoreboard/coverage receives from analysis_port
uvm_seq_item_pull_portDriver → SequencerDriver pulls items from sequencer
uvm_seq_item_pull_exportSequencer sideSequencer export connected to driver port
uvm_tlm_analysis_fifoBuffered FIFODecouples monitor write speed from scoreboard read speed

3. uvm_agent — Active vs Passive

class my_agent extends uvm_agent;
  `uvm_component_utils(my_agent)

  // Components — only created in active mode
  my_driver    drv;
  my_sequencer sqr;
  my_monitor   mon;

  // Analysis port — forwards monitor output to env
  uvm_analysis_port #(my_item) ap;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    // Always create monitor (passive and active mode)
    mon = my_monitor::type_id::create("mon", this);

    // Only create driver+sequencer in ACTIVE mode
    if (get_is_active() == UVM_ACTIVE) begin
      drv = my_driver::type_id::create("drv", this);
      sqr = my_sequencer::type_id::create("sqr", this);
    end

    ap = new("ap", this);
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    // Connect monitor ap to agent ap (agent re-exports it)
    mon.ap.connect(ap);

    // Connect driver pull port to sequencer export
    if (get_is_active() == UVM_ACTIVE)
      drv.seq_item_port.connect(sqr.seq_item_export);
  endfunction
endclass

4. uvm_env

class my_env extends uvm_env;
  `uvm_component_utils(my_env)

  my_agent    agent;
  my_scoreboard scb;
  my_coverage   cov;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    agent = my_agent::type_id::create("agent", this);
    scb   = my_scoreboard::type_id::create("scb",   this);
    cov   = my_coverage::type_id::create("cov",   this);
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    // Monitor's analysis port feeds scoreboard AND coverage
    agent.ap.connect(scb.analysis_export);
    agent.ap.connect(cov.analysis_export);
  endfunction
endclass

5. uvm_test

class base_test extends uvm_test;
  `uvm_component_utils(base_test)
  my_env env;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);

    // Pass virtual interface to agent driver and monitor
    uvm_config_db #(virtual my_if)::set(this, "env.agent.*", "vif", vif);

    env = my_env::type_id::create("env", this);
  endfunction

  virtual task run_phase(uvm_phase phase);
    my_sequence seq;
    phase.raise_objection(this);

    seq = my_sequence::type_id::create("seq");
    seq.start(env.agent.sqr);

    phase.drop_objection(this);
  endtask
endclass

6. Complete Component Code — Driver, Monitor, Scoreboard

// Minimal driver skeleton
class my_driver extends uvm_driver #(my_item);
  `uvm_component_utils(my_driver)
  virtual my_if vif;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    if (!uvm_config_db #(virtual my_if)::get(this, "", "vif", vif))
      `uvm_fatal("DRV", "No virtual interface found")
  endfunction

  virtual task run_phase(uvm_phase phase);
    my_item item;
    forever begin
      seq_item_port.get_next_item(item);
      drive_item(item);
      seq_item_port.item_done();
    end
  endtask

  task drive_item(my_item item);
    @(vif.cb); vif.cb.valid <= 1; vif.cb.addr <= item.addr;
    @(vif.cb); vif.cb.valid <= 0;
  endtask
endclass

// Minimal monitor skeleton
class my_monitor extends uvm_monitor;
  `uvm_component_utils(my_monitor)
  virtual my_if vif;
  uvm_analysis_port #(my_item) ap;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    ap = new("ap", this);
    if (!uvm_config_db #(virtual my_if)::get(this, "", "vif", vif))
      `uvm_fatal("MON", "No virtual interface found")
  endfunction

  virtual task run_phase(uvm_phase phase);
    my_item item;
    forever begin
      @(vif.cb);
      if (vif.cb.valid) begin
        item = my_item::type_id::create("item");
        item.addr = vif.cb.addr;
        ap.write(item);   // broadcast to all subscribers
      end
    end
  endtask
endclass

7. How analysis_port Broadcasts

// In scoreboard — implements uvm_analysis_imp
class my_scoreboard extends uvm_scoreboard;
  `uvm_component_utils(my_scoreboard)

  // analysis_imp implements write() — called by analysis_port
  uvm_analysis_imp #(my_item, my_scoreboard) analysis_export;

  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction

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

  // Called automatically each time monitor calls ap.write()
  virtual function void write(my_item item);
    `uvm_info("SCB", $sformatf("Received: addr=%0h", item.addr), UVM_HIGH)
    // compare against expected model here
  endfunction
endclass

// Using TLM FIFO to buffer between monitor and scoreboard
// In env connect_phase:
uvm_tlm_analysis_fifo #(my_item) fifo;
// build:   fifo = new("fifo", this);
// connect: agent.ap.connect(fifo.analysis_export);
//          scb.get_port.connect(fifo.get_export);

8. File and Class Naming Convention

// One class per file, file name matches class name:
// my_item.sv          → class my_item extends uvm_sequence_item
// my_sequence.sv      → class my_sequence extends uvm_sequence
// my_sequencer.sv     → typedef uvm_sequencer #(my_item) my_sequencer;
// my_driver.sv        → class my_driver extends uvm_driver
// my_monitor.sv       → class my_monitor extends uvm_monitor
// my_agent.sv         → class my_agent extends uvm_agent
// my_scoreboard.sv    → class my_scoreboard extends uvm_scoreboard
// my_env.sv           → class my_env extends uvm_env
// my_test.sv          → class my_test extends uvm_test

// Package file — include all in order (items before users):
package my_tb_pkg;
  `include "uvm_macros.svh"
  import uvm_pkg::*;
  `include "my_item.sv"
  `include "my_sequence.sv"
  `include "my_sequencer.sv"
  `include "my_driver.sv"
  `include "my_monitor.sv"
  `include "my_agent.sv"
  `include "my_scoreboard.sv"
  `include "my_env.sv"
  `include "my_test.sv"
endpackage

Key Takeaways — Day 7

Next → Day 08
UVM Sequences and seq_item
uvm_sequence_item, uvm_sequence body, `uvm_do macros, virtual sequences, p_sequencer, sequencer arbitration.