Skip to content

UVM

Component Relationships

The four ways components relate — ownership, communication, configuration, and coordination — each with its mechanism and phase, and the virtual-sequencer peer-reference pattern.

UVM Base Classes · Module 4 · Page 4.7

The Engineering Problem

You now know the base classes — uvm_object, uvm_component, and the data lineage. The last question of this module is how components relate to each other. Because a testbench isn't a pile of independent components; it's a web of relationships, and a given pair of components can be related in several different ways at once. The env owns its agent (parent–child), the monitor sends transactions to the scoreboard (communication), the test configures the agent (configuration), and a virtual sequencer coordinates the agents' sequencers (coordination by reference).

The engineering point is that these are different kinds of relationship, each with its own correct mechanism and its own correct phase — and mixing them up is a rich source of bugs. Ownership is established by create(name, parent) in build_phase; communication by connect() in connect_phase; configuration by the config database; coordination by handing one component a handle to another, after both exist. Establish a relationship the wrong way (a hard-coded reach across the hierarchy) or in the wrong phase (a peer handle grabbed before the target is built) and you get broken reuse or null-handle crashes. This capstone of Module 4 maps the relationship types so you establish each correctly.

In how many ways can two UVM components relate, what is the correct mechanism and phase for each, and why does establishing a relationship the wrong way (or in the wrong phase) break the environment?

Motivation — why naming the relationship types matters

Treating "component relationships" as one thing hides the distinctions that make a testbench correct:

  • Each relationship has a correct mechanism — using the wrong one fails. You cannot establish data flow with create, or ownership with connect. Knowing that ownership = create, communication = connect, configuration = config_db, and coordination = a handle assignment is what lets you build each relationship the way the framework expects.
  • Each has a correct phase — using the wrong one is a null bug. Build happens before connect (Module 2.6). Ownership is established in build_phase (you create children there); communication and coordination in connect_phase (after children exist). Grab a peer's handle in build_phase, before that peer is built, and the handle is null — a crash waiting to happen.
  • Coordination is the relationship newcomers miss. Parent–child, TLM, and config are familiar; the peer reference — one component holding a handle to a non-child component to coordinate it (the virtual sequencer pattern) — is less obvious and is how cross-agent stimulus is orchestrated. Naming it makes the pattern visible.
  • Establishing relationships properly is what keeps reuse intact. A relationship built by a hard-coded absolute path (reaching across the hierarchy) couples components to one structure and breaks under nesting (Module 3.5). Using the proper mechanism — config and connect, scoped relative to this — keeps components position-independent.

The motivation, in one line: two components can be related in four distinct ways, and each way has a specific correct mechanism and phase — so the skill is recognising which relationship you're establishing and building it the right way, in the right phase.

Mental Model

Hold the four relationships as four kinds of line you can draw between components:

Between any two components you can draw four kinds of line, and each is drawn with a different pen, at a different time. An ownership line (solid) goes from a parent down to a child — drawn at construction time with create(name, parent); it says "I built and contain you." A communication line (arrow) goes from a producer to a consumer — drawn at connect time with connect(); it says "I send you transactions." A configuration line (dotted, top-down) goes from a configurer to a configured component — drawn via the config database; it says "I set your parameters from above." And a coordination line (peer) goes sideways between two components that don't own each other — drawn at connect time by handing one a handle to the other; it says "I hold a reference to you so I can orchestrate you" (the virtual sequencer holding agent sequencers). The pen (mechanism) and the timing (phase) are fixed per line type: ownership at build, communication and coordination at connect, configuration top-down before the child reads it.

So for any relationship you need, ask: which of the four is this? — and that tells you the mechanism (create / connect / config_db / handle assignment) and the phase (build / connect). Drawing the line with the wrong pen or at the wrong time is the bug.

Visual Explanation — the four relationship types

The four relationships differ along two axes: their mechanism (how you establish them) and their phase (when). Laying them out makes the correct choice obvious for any relationship you need.

Ownership via create in build_phase, communication via connect in connect_phase, configuration via config_db, coordination via handle in connect_phaseFour relationships, four mechanisms, four phasesFour relationships, four mechanisms, four phasesOwnership (parent → child)create(name, parent) in build_phase — the env builds and contains the agentcreate(name, parent) in build_phase — the env builds and contains the agentCommunication (producer → consumer)connect() of TLM ports in connect_phase — the monitor sends transactions to the scoreboardconnect() of TLM ports in connect_phase — the monitor sends transactions to the scoreboardConfiguration (configurer → configured)config database, set top-down, read in build_phase — the test configures the agentconfig database, set top-down, read in build_phase — the test configures the agentCoordination (peer → peer)a handle assignment in connect_phase, after both exist — a virtual sequencer holds agent sequencersa handle assignment in connect_phase, after both exist — a virtual sequencer holds agent sequencers
Figure 1 — the four kinds of component relationship, each with its mechanism and phase. Ownership (parent–child): create(name, parent) in build_phase. Communication (producer→consumer): connect() of TLM ports in connect_phase. Configuration (configurer→configured): the config database, set top-down, read in build_phase. Coordination (peer→peer): a handle assignment in connect_phase, after both components exist. Recognise the relationship, and the mechanism and phase follow.

The four rows are a decision table. Ownership is the structural backbone — a parent constructs and contains its children, established by passing the parent to create during build_phase. Communication is data flow — TLM ports connected in connect_phase (Module 3.4). Configuration is top-down parameterisation — the config database, set by an ancestor and read by the component in its build_phase (Module 3.5). Coordination is the peer relationship — one component holding a handle to another it doesn't own, so it can orchestrate it, established by assigning that handle in connect_phase once both components have been built. Each row pairs a relationship with exactly one mechanism and one phase, and matching them correctly is the whole discipline of wiring an environment.

RTL / Simulation Perspective — the four relationships in code

A single env establishes all four relationships, and the code shows each with its mechanism in its phase — ownership and config in build_phase, communication and coordination in connect_phase.

the four component relationships, established correctly
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
class my_env extends uvm_env;
  `uvm_component_utils(my_env)
  agent a, b;  my_vseqr vseqr;  scoreboard sb;
 
  function void build_phase(uvm_phase phase);
    // OWNERSHIP: parent → child, via create(name, parent)
    a     = agent     ::type_id::create("a", this);
    b     = agent     ::type_id::create("b", this);
    vseqr = my_vseqr  ::type_id::create("vseqr", this);
    sb    = scoreboard::type_id::create("sb", this);
    // CONFIGURATION: configurer → configured, via the config database (top-down)
    uvm_config_db#(int)::set(this, "a", "mode", FAST);
  endfunction
 
  function void connect_phase(uvm_phase phase);
    // COMMUNICATION: producer → consumer, via connect() of TLM ports
    a.mon.ap.connect(sb.analysis_export);
    b.mon.ap.connect(sb.analysis_export);
    // COORDINATION: peer → peer, via a handle assignment — AFTER both are built
    vseqr.sqr_a = a.sqr;     // the virtual sequencer holds the agents' sequencers
    vseqr.sqr_b = b.sqr;
  endfunction
endclass

Each relationship appears with its correct mechanism and phase. Ownership (create("a", this)) and configuration (config_db::set) are in build_phase — ownership because that's when children are constructed, configuration because the child reads it in its build_phase (which runs after this one). Communication (a.mon.ap.connect(sb...)) is in connect_phase, wiring the monitor's analysis port to the scoreboard. Coordination (vseqr.sqr_a = a.sqr) is also in connect_phase — and it must be, because a.sqr (agent a's sequencer) only exists after agent a's own build_phase has run, which is after this env's build_phase. Assigning that handle in build_phase would grab a null. The phase isn't incidental: it's dictated by when each related component comes into existence.

Verification Perspective — coordination, the relationship by reference

Three of the four relationships are familiar (ownership, communication, configuration); the fourth — coordination — is the one that enables cross-component orchestration, and it works by reference. The classic case is the virtual sequencer holding handles to several agents' sequencers, so a virtual sequence can drive all of them in a coordinated order.

A virtual sequencer holds handles to agent A's and agent B's sequencers; a virtual sequence coordinates bothrunsonhandle → drive Ahandle → drive BitemsitemsVirtual sequencecoordinates across agentsVirtual sequencerholds agent sequencer handlesAgent A sequencerpeer (not owned by vseqr)Agent B sequencerpeer (not owned by vseqr)Driver ADriver B12
Figure 2 — coordination by reference: the virtual sequencer pattern. A virtual sequencer holds handles to the sequencers of multiple agents (assigned in connect_phase). A virtual sequence running on the virtual sequencer can then start sub-sequences on each agent's sequencer in a coordinated order — orchestrating stimulus across agents that don't own one another. The handles are the coordination relationship; they let one component drive peers it doesn't contain.

Coordination is distinct from the other three because it crosses between subtrees without ownership. The virtual sequencer does not contain the agents' sequencers (they're owned by their agents), and it does not connect to them via TLM — it simply holds handles to them, assigned in connect_phase. A virtual sequence then runs on the virtual sequencer and uses those handles to start sub-sequences on each agent's sequencer, in whatever coordinated order the scenario needs ("write on agent A, then read on agent B"). This is the relationship that orchestrates multi-interface stimulus, and it's why an SoC environment can coordinate its block agents. The handle is the relationship; the phase (connect_phase, after build) is when the targets exist to point at; and the discipline is to assign these handles through proper navigation/config rather than hard-coded absolute paths, so the coordination survives reuse.

Runtime / Execution Flow — relationships are established before they're used

All four relationships are established during the zero-time construction phases and used during the run — and the ordering (build before connect, both before run) is exactly what makes the relationships valid when used.

build_phase establishes ownership and configuration, connect_phase establishes communication and coordination, run_phase uses all fourEstablish in construction, use at runEstablish in construction, use at run1build_phase — ownership + configcreate children (ownership); read configuration. The componentscome into existence top-down.2connect_phase — communication + coordinationconnect TLM ports (communication) and assign peer handles(coordination) — now that all components exist.3run_phase — use all fourowned children run; transactions flow; config applies; coordinatedpeers are orchestrated by virtual sequences.4Order guarantees validitybuild before connect means every component exists before anyrelationship is wired — so no null targets.
Figure 3 — relationships are established in the construction phases, then used at run time. build_phase: ownership (create children) and configuration (read config). connect_phase: communication (connect TLM ports) and coordination (assign peer handles) — both after children exist. run_phase: all four relationships are now in place and used — owned children run, transactions flow over connections, configured behaviour applies, and coordinated peers are orchestrated. Establish in construction, use in run; the phase order guarantees each relationship's targets exist before it's wired.

The ordering is what makes the relationships sound. Because build_phase runs (top-down) before connect_phase, every component in the tree exists by the time connections and coordination handles are wired — so a TLM connect finds its target, and a peer handle points at a real sequencer. This is precisely why communication and coordination belong in connect_phase and not build_phase: in build_phase, the components you'd connect or reference may not have been built yet (a child's build_phase runs after its parent's). And it's why all four relationships are used only in run_phase — by then the structure is complete and wired. The construction phases establish the web of relationships; the run phase exercises it; and the build-before-connect order is the guarantee that no relationship is wired to something that doesn't exist yet.

Waveform Perspective — coordination orchestrating two agents

The coordination relationship is visible at run time as cross-agent ordering: a virtual sequence uses its handles to drive agent A, then agent B, in a coordinated sequence — two interfaces orchestrated by one virtual sequence holding both sequencers.

A virtual sequence coordinates two agents — drive A, then B, in order

10 cycles
A virtual sequence coordinates two agents — drive A, then B, in orderCoordination: the virtual sequence drives agent A via its handle to A's sequencerCoordination: the virt…then agent B via its handle to B's sequencer — coordinated cross-agent orderingthen agent B via its h…the virtual sequencer's peer handles to both sequencers enable this orchestrationthe virtual sequencer'…clka_valida_data00A0A100000000000000b_validb_data00000000B0B100000000t0t1t2t3t4t5t6t7t8t9
Figure 4 — coordination in action. A virtual sequence, running on a virtual sequencer that holds handles to both agents' sequencers, first drives agent A's interface (a_valid/a_data), then agent B's (b_valid/b_data), in a coordinated order. The two interfaces are independent (different agents, not owned by the virtual sequencer), but the coordination relationship — the handles assigned in connect_phase — lets one virtual sequence orchestrate both. This cross-agent ordering is what coordination by reference enables.

The staggered activity — agent A (cycles 1–2) then agent B (cycles 4–5) — is the coordination relationship producing a coordinated order across two independent interfaces. Neither agent owns the other, and neither is connected to the other by TLM; the only thing linking them is the virtual sequencer holding a handle to each sequencer, and a virtual sequence using those handles to drive them in sequence. This is the relationship by reference at work: it lets stimulus span multiple interfaces in a controlled order, which is exactly what SoC-level verification needs (coordinate the protocol between blocks). The waveform shows why coordination is its own relationship type — it produces cross-component behaviour that ownership, communication, and configuration cannot, and it does so purely through held handles assigned once the targets exist.

DebugLab — the virtual sequencer handle that was null

A virtual sequence crashed on a null sequencer — the handle was assigned too early

Symptom

A virtual sequence tried to start a sub-sequence on agent A's sequencer through the virtual sequencer, and the simulation crashed with a null object access — the virtual sequencer's sqr_a handle was null. Yet agent A clearly existed in the tree (it appeared in print_topology()), its sequencer was built, and the virtual sequencer was built too. Everything was present, but the reference between them was empty.

Root cause

The coordination handle was assigned in the wrong phase — in build_phase, before agent A's sequencer existed. Because build_phase runs top-down, the env's build_phase (which assigned the handle) ran before agent A's build_phase (which creates the sequencer):

why the peer handle was null
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
env.build_phase:    a = agent::create("a", this);     // agent created, but its build_phase NOT run yet
                    vseqr.sqr_a = a.sqr;               // a.sqr is still NULL here ← BUG
a.build_phase:      sqr = sequencer::create("sqr",this); // sequencer created NOW (after env.build_phase)
result:             vseqr.sqr_a captured the null → virtual sequence crashes using it
fix:                assign vseqr.sqr_a = a.sqr in CONNECT_PHASE (after all build_phases complete)

The agent handle a was valid (it was created), but a.sqr — the agent's child sequencer — is created inside agent a's own build_phase, which runs after the env's build_phase returns. So at the moment the env grabbed a.sqr, it was still null. Coordination handles must be assigned in connect_phase, after every component's build_phase has completed and all sequencers exist.

Diagnosis

The tell is a null handle to a component that demonstrably exists — a relationship established before its target was built. Diagnose phase-ordering relationship bugs:

  1. Check the phase of the handle assignment. Peer/coordination handles (and TLM connections) must be set in connect_phase, not build_phase — because a child's sequencer/ports don't exist until its build_phase runs, which is after its parent's. A null peer handle is almost always an assignment in build_phase.
  2. Confirm the target is a grandchild, not a direct field. a.sqr is built inside a's build, one level deeper; the env's build can't see it yet. Anything reached through a child (child.grandchild) isn't available until connect.
  3. Distinguish null reference from missing component. print_topology() shows the component exists (so it's not an orphan); the null is in the reference, pointing to a relationship wired too early.
Prevention

Establish each relationship in its correct phase, dictated by when its targets exist:

  1. Coordination handles and TLM connections go in connect_phase. By then every build_phase has run and all components (including grandchildren like agent sequencers) exist. Never assign a peer handle in build_phase.
  2. Ownership and config go in build_phase. Create children and set their configuration there; the config is read by the child in its own (later) build_phase.
  3. Reach peers through proper navigation, not absolute paths. Assign coordination handles via the component structure (a.sqr) in connect, not via hard-coded hierarchical strings — so the relationship survives nesting and reuse (Module 3.5).

The one-sentence lesson: a coordination handle points at a component that may be built later than its referrer — assign peer handles (and TLM connections) in connect_phase, after every build_phase has run, or the handle captures a null.

Common Mistakes

  • Assigning peer handles or TLM connections in build_phase. A child's sub-components (e.g., an agent's sequencer) don't exist until the child's own build_phase runs, which is after its parent's. Establish communication and coordination in connect_phase, after build.
  • Using the wrong mechanism for a relationship. Ownership is create, communication is connect, configuration is config_db, coordination is a handle assignment. Trying to establish data flow with create, or coordination with connect, mismatches the relationship to its mechanism.
  • Hard-coding cross-hierarchy references. Reaching a peer by an absolute hierarchical path couples components to one structure and breaks reuse (Module 3.5). Establish coordination through the component structure or config, scoped relative to this.
  • Confusing ownership with the other relationships. Containment (who builds whom) is not data flow (who sends to whom) or coordination (who references whom). The monitor lives in the agent (ownership) but sends to the scoreboard (communication) — different relationships over the same components.
  • Forgetting coordination exists. Newcomers wire ownership, TLM, and config but miss the peer-reference relationship, then struggle to orchestrate across agents. The virtual sequencer holding sequencer handles is how cross-agent stimulus is coordinated.
  • Reading config in the wrong phase. Configuration is set top-down and must be read in the configured component's build_phase; reading it later (or before it's set) misses it.

Senior Design Review Notes

Interview Insights

In four distinct ways, each with its own mechanism and phase. First, ownership — the parent–child containment relationship, established by create(name, parent) in build_phase; the env owns the agent, the agent owns its sequencer/driver/monitor. Second, communication — the producer–consumer data relationship, established by connect()-ing TLM ports in connect_phase; the monitor sends transactions to the scoreboard. Third, configuration — the configurer–configured relationship, established through the config database, set top-down by an ancestor and read by the component in its own build_phase; the test configures the agent's mode and interface. Fourth, coordination — the peer-to-peer reference relationship, established by handing one component a handle to another it doesn't own, assigned in connect_phase after both exist; a virtual sequencer holds handles to several agents' sequencers so a virtual sequence can orchestrate them. The same two components can be related in several of these ways at once, and the skill is recognising which relationship you're establishing so you use the right mechanism in the right phase — ownership and config in build, communication and coordination in connect.

Exercises

  1. Name the four. List the four component relationships, and for each give its mechanism and the phase it's established in.
  2. Fix the phase. A virtual sequencer assigns vseqr.sqr_a = a.sqr in build_phase and the handle is null at run time. State the root cause in phase-ordering terms and the corrected phase, and explain why a.sqr is null in build.
  3. Match relationship to mechanism. For each, name the relationship and mechanism: (a) the env builds an agent; (b) the monitor feeds the scoreboard; (c) the test sets the agent's mode; (d) a virtual sequence drives two agents' sequencers.
  4. Coordinate two agents. Describe how you'd set up a virtual sequence to write on one agent then read on another: which handles the virtual sequencer holds, where they're assigned, and why that phase.

Summary

  • Two UVM components can relate in four distinct ways, each with its own mechanism and phase: ownership (parent–child, create in build_phase), communication (producer→consumer, connect TLM ports in connect_phase), configuration (configurer→configured, config database, set top-down, read in build_phase), and coordination (peer→peer, a handle assignment in connect_phase).
  • The phase is dictated by when targets exist: ownership and config in build_phase; communication and coordination in connect_phase, after every build_phase has run so all components (including grandchildren like agent sequencers) exist.
  • Coordination is the relationship newcomers miss: one component holding a handle to a peer it doesn't own — the virtual sequencer holding agent sequencers — which is how cross-agent stimulus is orchestrated by a virtual sequence.
  • The classic bug is a peer handle (or TLM connection) established in build_phase — the target sub-component isn't built yet, so the handle captures a null. And hard-coding cross-hierarchy paths breaks reuse; wire through the structure/config instead.
  • The durable rule of thumb: recognise which of the four relationships you're establishing, use its mechanism (create/connect/config_db/handle), and establish it in its phase (ownership+config in build, communication+coordination in connect) — relationships are wired during zero-time construction and used at run, and the build-before-connect order guarantees every target exists before it's referenced.

Next — UVM Phasing Overview: you've repeatedly relied on the phases — build before connect, run after both — to establish and use relationships. Module 5 makes phasing itself the subject: the complete phase schedule, what each phase is for, and how the framework orchestrates the whole tree of components through it.