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 Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
    • Classes, Objects & Handles
    • Inheritance and Polymorphism
    • Static, Encapsulation & Parameterized Classes
    • Virtual Interfaces: the Class-to-Pins Bridge
    • Events, Semaphores, Mailboxes and Processes
    • OOP Patterns for Testbenches
      • The Transaction Base Class
      • The Factory Pattern from Scratch
      • Singletons and Config Objects
      • Callbacks and Hooks
      • Building a Component Hierarchy by Hand
  • Constraint Randomization — Interview-Critical Deep Dive36
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹OOP Patterns for Testbenches
  1. Topics
  2. SystemVerilog
  3. Object-Oriented SystemVerilog for Verification
  4. OOP Patterns for Testbenches
  5. Building a Component Hierarchy by Hand

Part 2 · OOP for Verification · Intermediate

Building a Component Hierarchy by Hand

A parent-child component tree with build, connect and run waves, the UVM phases by convention.

On this page

    Why this matters

    A monitor's constructor tried to connect to a scoreboard that had not been built yet. Building everything first and wiring it afterwards removes the ordering problem.

    After this lesson you can: build a named component tree with parent handles, and run build, connect and run as separate waves.

    A tree with names

    An environment owns one agent per interface plus the scoreboard. Each agent owns its driver and monitor, and every component stores a name and a parent handle.

    full_name() walks the parent chain. The dotted path it returns labels every log line with its source component.

    Environment owning an agent and a scoreboard, agent owning driver and monitor.
    The minimal tree in the testbench, with dotted full names.

    Build, then connect, then run

    Construction runs top-down, with parents building children. Connection runs after everything exists, so sibling wiring always finds its endpoints.

    Running starts last, once every connection is in place. The split exists because a constructor cannot see siblings that are built later.

    Hand-built convention

    UVM equivalent

    Component base with build, connect, run

    uvm_component with phase methods

    Name plus parent handle

    new(name, parent) and get_full_name()

    Build wave, top-down

    build_phase, auto-recursed top-down

    Connect wave for sibling wiring

    connect_phase with TLM ports

    Run wave with daemons

    run_phase with objections for end of test

    Manual waves from the top

    run_test(), which walks every phase

    UVM keeps the same waves and adds a scheduler. Objections replace the crude end-of-test delay, and TLM ports replace raw handle wiring.

    Common mistakes

    Common pitfall

    Wiring siblings inside constructors breaks the moment the tree grows. The peer a constructor needs may not exist yet, so wiring waits for the connect wave.

    Interview

    Likely questions: “why do build and connect stay separate”, “what is the parent argument for”, and “how to grow this to two interfaces”.

    • Separation: connections need every endpoint to exist first.

    • Parent: tree membership plus free hierarchical debug paths.

    • Growth: add a second agent in the environment and compare both streams in one scoreboard.

    Check yourself Connect runs before build on a fresh tree. How does the bench know?

    Every connect() checks its built flag first. Unbuilt components set a sticky order flag, which the testbench reads.

    Check yourself Why do build and connect stay separate instead of wiring inside constructors?

    Construction order is top-down, so a constructor cannot see siblings built later. By connect time every component exists, so sibling wiring always finds its endpoints.

    Check yourself Where does env0.agt0.drv come from?

    Each component stores its name and parent handle. full_name() walks the parent chain, joining names with dots.

    Testbench

    A minimal tree that proves wave order with flags and resolves every hierarchical name.

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    class component;
      string name;
      component parent;
      bit built;
      bit connected;
      bit ran;
      bit order_bad;
    
      function new(string cname, component p);
        name = cname;
        parent = p;
      endfunction
    
      function string full_name();
        if (parent == null) return name;
        return {parent.full_name(), ".", name};
      endfunction
    
      virtual function void build();
        built = 1;
      endfunction
    
      virtual function void connect();
        if (built == 0) order_bad = 1;
        connected = 1;
      endfunction
    
      virtual task run();
        if (connected == 0) order_bad = 1;
        ran = 1;
      endtask
    endclass
    
    class leaf extends component;
      function new(string lname, component p);
        super.new(lname, p);
      endfunction
    endclass
    
    class agent extends component;
      leaf drv;
      leaf mon;
    
      function new(string aname, component p);
        super.new(aname, p);
        drv = new("drv", this);
        mon = new("mon", this);
      endfunction
    
      virtual function void build();
        super.build();
        drv.build();
        mon.build();
      endfunction
    
      virtual function void connect();
        super.connect();
        drv.connect();
        mon.connect();
      endfunction
    
      virtual task run();
        super.run();
        drv.run();
        mon.run();
      endtask
    endclass
    
    class env extends component;
      agent agt;
      leaf sb;
    
      function new(string ename);
        super.new(ename, null);
        agt = new("agt0", this);
        sb = new("sb", this);
      endfunction
    
      virtual function void build();
        super.build();
        agt.build();
        sb.build();
      endfunction
    
      virtual function void connect();
        super.connect();
        agt.connect();
        sb.connect();
      endfunction
    
      virtual task run();
        super.run();
        agt.run();
        sb.run();
      endtask
    endclass
    
    Terminal
    PASS: build then connect then run, every name resolves

    Remember

    • Build the whole tree top-down before any wiring.

    • Connect only after every component exists.

    • Each component knows its name and parent, so logs say where they came from.

    Rule of thumb: build everything, then connect everything, then run.

    How this was checked

    Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. These class benches were run on Verilator only.

    Related topics

    • Next part: Randomization

    Previous

    64 / 259

    Next: Constraint Randomization — Interview-Critical Deep Dive

    Up next

    Up nextConstraint Randomization — Interview-Critical Deep Dive

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • The Transaction Base Class
    • The Factory Pattern from Scratch
    • Singletons and Config Objects