Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here4
  • UVM Foundations14
    • What Is UVM?
      • Why UVM Exists: History and Goals
      • What UVM Standardizes (and What You Own)
      • Components vs Objects: Hierarchy, Lifetime, and Phases
      • Objects That Copy, Compare and Print
      • The Stimulus, Check, and Coverage Triangle
      • Your First UVM Testbench
      • The DMA Bench: Full Source, Part 1
      • The DMA Bench: Full Source, Part 2
      • The DMA Bench: Full Source, Part 3
    • Testbench Architecture and Environment Composition
  • UVM Phases & Simulation Lifecycle26
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹What Is UVM?
  1. Topics
  2. UVM Verification
  3. UVM Foundations
  4. What Is UVM?
  5. Components vs Objects: Hierarchy, Lifetime, and Phases

Part 1 · Foundations · Intermediate

Components vs Objects: Hierarchy, Lifetime, and Phases

The uvm_component and uvm_object fork in the DMA bench: constructors, lifetimes, the topology, and the errors you get for crossing it.

On this page

    A candidate is asked to snapshot every transaction a monitor sees, so a later check can compare against it. They write snap = mon.clone();, meaning to copy the monitor's last item. The interviewer asks what that line returns. The answer is null, with an ILLCLN error in the log, because a monitor is not data and cannot be cloned. One line, and the interviewer knows whether you have the most basic split in UVM straight. What is that split, and what else follows from it?

    The idea: furniture and the things that move through the room

    Every UVM class sits on one side of a single fork. Components (uvm_component and everything below it) are the furniture of the testbench: built once at the start, each with a fixed place in a tree, taking part in every phase, and staying until the simulation ends. Objects (uvm_object and the classes that are not components) are what moves through the room: transactions, sequences and configuration bundles, created and discarded by the thousand, with no place in the tree and no phases.

    Almost every rule you will meet follows from which side a class is on. Components get two-argument constructors, phase methods and a full path that config_db and instance overrides key on. Objects get one-argument constructors, are randomized and copied, and are reclaimed by garbage collection when nothing refers to them any more.

    The picture: where the classes hang

    Class tree: uvm_object branches into transactions, items, sequences and config objects on one side and uvm_report_object then uvm_component, with drivers, monitors, agents, scoreboards, envs and tests, on the other.
    One root, one fork. Components descend through uvm_report_object, so every component can report messages; objects such as items, sequences and config classes do not sit in the tree.

    Two placements surprise people. A sequence is an object: it descends from uvm_sequence_item, so it has no phases and no tree slot, even though it runs for a long time. A register model (uvm_reg_block) is an object too, which is why the env builds it with create but never sees it in the topology print.

    Build it: the fork in the DMA bench

    The constructor tells you which side you are on

    Here are an object and a component from the bench, side by side. The item's constructor takes only a name, with a default. The monitor's takes a name and a parent, with no defaults, because a component cannot exist without a place in the tree.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // snippet: from the course DMA bench
    class apb_item extends uvm_sequence_item;
      rand bit [7:0]  addr;
      rand bit        write;
      rand bit [31:0] data;     // write data
      bit      [31:0] rdata;    // filled by the driver on reads
      bit             slverr;   // filled by the driver
      constraint word_aligned_c { addr[1:0] == 2'b00; }
    
      `uvm_object_utils(apb_item)
      function new(string name = "apb_item");
        super.new(name);
      endfunction
    
      function string convert2string();
        return write ? $sformatf("WR addr=%02h data=%08h%s", addr, data, slverr ? " SLVERR" : "")
                     : $sformatf("RD addr=%02h rdata=%08h%s", addr, rdata, slverr ? " SLVERR" : "");
      endfunction
    
      function void do_copy(uvm_object rhs);
        apb_item r;
        super.do_copy(rhs);
        $cast(r, rhs);
        addr = r.addr; write = r.write; data = r.data; rdata = r.rdata; slverr = r.slverr;
      endfunction
    endclass
    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // snippet: from the course DMA bench
    class apb_monitor extends uvm_monitor;
      `uvm_component_utils(apb_monitor)
      virtual apb_if vif;
      uvm_analysis_port #(apb_item) ap;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
        ap = new("ap", this);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if (!uvm_config_db#(virtual apb_if)::get(this, "", "vif", vif))
          `uvm_fatal("NOVIF", "apb_if not set for this monitor")
      endfunction
    
      // A transfer completes on the edge where PSEL, PENABLE and PREADY are all 1.
      task run_phase(uvm_phase phase);
        forever begin
          @(vif.mon_cb);
          if (vif.mon_cb.psel && vif.mon_cb.penable && vif.mon_cb.pready) begin
            apb_item it = apb_item::type_id::create("it");   // a fresh object per transfer
            it.addr   = vif.mon_cb.paddr;
            it.write  = vif.mon_cb.pwrite;
            it.data   = vif.mon_cb.pwdata;
            it.rdata  = vif.mon_cb.prdata;
            it.slverr = vif.mon_cb.pslverr;
            `uvm_info("APB_MON", it.convert2string(), UVM_HIGH)
            ap.write(it);
          end
        end
      endtask
    endclass
    • The item registers with uvm_object_utils, carries random fields, and is created fresh for every transfer. Its do_copy and convert2string are the subject of the next lesson.

    • The monitor registers with uvm_component_utils, overrides build_phase and run_phase, and lives for the whole simulation. Its full path, uvm_test_top.env.apb.mon, is what the top module's config_db path matches.

    • Inside the monitor, apb_item::type_id::create("it") creates an object with one argument. Objects can be given a parent context for instance overrides, but they never get a tree slot.

    A configuration object is data, too

    Knob bundles are objects. The bench's dma_job_cfg describes one DMA job; a test randomizes it or fetches one from config_db, and nothing about it needs phases.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // snippet: from the course DMA bench
    // One DMA job as a randomizable object. A test can randomize it, or put one in
    // config_db so a test further down the hierarchy picks it up.
    class dma_job_cfg extends uvm_object;
      rand bit [31:0]   key;
      rand int unsigned len;
      rand bit          irq_en;
      constraint len_c { len inside {[1:16]}; }
      constraint irq_c { soft irq_en == 1; }
    
      `uvm_object_utils(dma_job_cfg)
      function new(string name = "dma_job_cfg");
        super.new(name);
      endfunction
    
      function string convert2string();
        return $sformatf("key=%08h len=%0d irq_en=%0b", key, len, irq_en);
      endfunction
    endclass

    The two errors you get for crossing the fork

    You try to

    What happens

    Why

    clone() a component

    ILLCLN error, returns null

    A copy would need a second place in the tree with the same name

    Call create() on a component instance

    ILLCRT error, returns null

    Components are built through type_id::create(name, parent)

    Create a component after build_phase has ended

    ILLCRT fatal

    The tree is frozen; connect and run phases assume it is complete

    Give an object a phase method

    It compiles and is never called

    Only components are visited by the phase scheduler

    Watch it run: the tree has only components

    In the topology the base test prints (shown in What UVM standardizes), every row is a component or a port: env, agents, drivers, monitors, sequencers, the scoreboard. No apb_item, no axis_pkt_seq, no dma_job_cfg appears, even though thousands of items are created during the run. That absence is the fork made visible. The run's own log also tells you when the objects existed: the [JOB] lines from dma_rand_test each print a fresh dma_job_cfg, created, randomized and dropped inside one loop iteration.

    Diagram
    UVM_INFO dma_tb.sv(731) @ 0: uvm_test_top [JOB] key=90fdd8fd len=10 irq_en=1
    UVM_INFO dma_tb.sv(731) @ 645000: uvm_test_top [JOB] key=0e564b57 len=1 irq_en=1
    UVM_INFO dma_tb.sv(731) @ 885000: uvm_test_top [JOB] key=5a1793f4 len=3 irq_en=1

    The values are random and differ by simulator and seed. The reporter is uvm_test_top, a component: objects that need to print usually do it through the component that owns them, or through uvm_info with the global reporter.

    What breaks: the monitor that shared one handle

    This bug shows up in nearly every team's first UVM bench, because it is the fork in disguise. A monitor author decides that creating a new item per transfer is wasteful, creates one item in the constructor, and refills its fields on every transfer before calling ap.write(it). The scoreboard pushes each received handle onto a queue. Every compare then fails in the same strange way: all the queued "expected" entries hold the values of the latest transfer.

    The root cause is treating an object as if it were furniture. There was only ever one object, and the queue held many references to it. The fix is what the bench's monitors do, a fresh create for every transfer, or a clone before storing. The deeper rule is that a component may live forever, but the objects it hands out must be safe to keep: once you broadcast an object, assume someone stores it.

    Interview

    "What is the difference between uvm_object and uvm_component?"

    A weak answer: "Components have phases and objects don't." True, but it is one consequence, not the idea.

    A strong answer starts from the idea and lists what follows: "Components are the static tree: built once in build_phase, each with a parent and a full path, visited by every phase, and living until the end. Objects are data moving through the tree: items, sequences, configs. So components take name and parent in their constructor, objects take only a name; config_db paths and instance overrides key on component paths; objects get randomized, copied and cloned, while cloning a component is an error. And a monitor must hand out a new object per transaction, because subscribers keep them."

    Expect the follow-ups: Is a sequence a component? (no, it is an object with no phases, started on a sequencer) and Can you create a component in run_phase? (no, it is an ILLCRT fatal once build has ended).

    Check yourself Is a register model (uvm_reg_block) a component or an object, and what follows from the answer?

    An object. It has no phases and no tree slot, so it is built with create and then explicitly build()-ed and locked by the env, and it does not appear in the topology print. Components that use it (the predictor, sequences) get a handle to it, usually through the env or config_db.

    Check yourself A test needs a slightly different scoreboard for one scenario. Does it clone the env's scoreboard and edit the copy?

    No. A component cannot be cloned (ILLCLN), and a copy would need its own place in the tree anyway. The test registers a factory override before the env builds, so the env creates the derived scoreboard in its place, exactly as dma_drain_test does with dma_scoreboard_eot.

    Check yourself Two scoreboards both store items from the same monitor. One of them modifies a field on the stored item. Is the other one affected?

    Yes, if they store the same handle: an analysis port passes a reference, not a copy, so both scoreboards hold the same object. A subscriber that intends to modify what it receives should clone it first. The monitor's job is to send a fresh object per transaction; it cannot protect subscribers from each other.

    Next: if objects are copied, compared and printed constantly, how should those methods be written? Objects that copy, compare and print compares field macros with hand-written methods.

    Related topics

    • Objects that copy, compare and print

    • Factory overview

    • Inheritance and polymorphism

    Previous

    7 / 280

    Next: Objects That Copy, Compare and Print

    Up next

    Up nextObjects That Copy, Compare and Print

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Why UVM Exists: History and Goals
    • The Stimulus, Check, and Coverage Triangle
    • Your First UVM Testbench