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
  • UVM Phases & Simulation Lifecycle26
    • The Phase System
      • Why Phases Exist
      • The Phase Schedule: Common and Runtime Phases
      • Traversal Order Rules
      • Function Phases vs Task Phases
      • Signatures, Super Calls, and Debug
      • Inside the Phaser: Nodes, Imps and Schedules
    • UVM Phases: Build, Connect, Elaborate and Run
    • Objections Deep Dive: Drain Time and Propagation
    • Phase Debugging and End-of-Test Signoff
  • 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

‹The Phase System
  1. Topics
  2. UVM Verification
  3. UVM Phases & Simulation Lifecycle
  4. The Phase System
  5. Traversal Order Rules
On this page

    Part 2/Phases & LifecycleIntermediate

    Traversal Order Rules

    Top-down build, bottom-up connect, parallel run: what order the phaser visits components in, and which orders you must never rely on.

    A scoreboard VIP has worked for a year. Then a team instantiates it in a new env with one change: the APB agent is renamed from apb to x_apb to match their naming scheme. Now the scoreboard's build fails to find a setting the agent was supposed to leave for it. Nothing about the code changed except a string. The phaser's visiting order did.

    The idea: managers hire, reports file status

    Think of an org chart. When a team is set up, a manager must be hired before they can hire their reports, so setup goes top-down: the parent before its children. When the quarter closes, each report files a status before the manager writes the summary, so close-out goes bottom-up: every child before its parent.

    For each function phase the phaser does exactly one of these walks over the component tree. A top-down walk (pre-order) visits a component, then each of its children's subtrees. A bottom-up walk (post-order) visits each child's subtree first, then the component. Task phases do not walk at all: the phaser starts every component's thread at the same time.

    Walk

    Phases

    Why this direction

    Top-down

    build_phase, final_phase

    A parent creates its children, so it must run first.

    Bottom-up

    connect_phase, end_of_elaboration_phase, start_of_simulation_phase, extract_phase, check_phase, report_phase

    A parent can use what its children prepared: forwarded ports, child audits, child results.

    Parallel

    run_phase and the twelve runtime phases

    Every component's task is forked at once.

    The picture: the DMA tree, both walks

    Tree of the DMA bench with each node labelled by its position in the top-down build walk and the bottom-up connect walk.
    Visit order for the drawn part of the DMA tree (the children of in_agt and out_agt are left out of the drawing and the numbers). Children are visited in name order, which is why drv comes before mon and sqr.

    Build numbers grow downward: every parent before its children. Connect numbers grow upward: every child before its parent. Those two facts are guaranteed. The order among siblings is a different matter.

    Sibling order: name order in practice, unspecified in principle

    The standard does not say in which order siblings are visited. The Accellera reference library keeps a component's children in an associative array keyed by name, so in practice siblings are visited in lexical order of their names. The DMA topology print shows it: under apb, the children appear as drv, mon, sqr, even though the agent created sqr before drv (its handle is @335, the driver's @458).

    That is exactly the trap from the opening. The scoreboard's build read a value that the APB agent's build set, and it worked only because apb sorts before sb. Renamed to x_apb, the agent now builds after the scoreboard. Senior engineers know about name order so that they can recognise this bug, not so that they can rely on it.

    Build it: why connect really runs bottom-up

    A common explanation is that connect is bottom-up "so leaf ports exist before parents connect to them". That is not the reason: by connect time, every build in the tree has finished, so every port exists. Also, connect() does not complete a link on the spot. It records the request, and the library resolves every recorded link in one pass just before end_of_elaboration_phase.

    The real reason is handle forwarding. Look at the bench's stream agent:

    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
    // snippet: from the course DMA bench
    class axis_agent extends uvm_agent;
      `uvm_component_utils(axis_agent)
      axis_sequencer sqr;
      axis_driver    drv;
      axis_monitor   mon;
      uvm_analysis_port #(axis_item) ap;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        mon = axis_monitor::type_id::create("mon", this);
        if (get_is_active() == UVM_ACTIVE) begin
          sqr = axis_sequencer::type_id::create("sqr", this);
          drv = axis_driver::type_id::create("drv", this);
        end
      endfunction
    
      function void connect_phase(uvm_phase phase);
        ap = mon.ap;
        if (get_is_active() == UVM_ACTIVE)
          drv.seq_item_port.connect(sqr.seq_item_export);
      endfunction
    endclass

    Its connect_phase sets ap = mon.ap, so the agent exposes one public port and the env never reaches inside it. The env's connect_phase then calls in_agt.ap.connect(sb.in_export). That only works if the agent's connect has already run, and bottom-up order guarantees it: every child's connect finishes before its parent's starts.

    A detail worth knowing: the bench's monitors create ap in their constructors, so even a copy made in the agent's build would have worked here. Many monitors create their port in build_phase instead, and then only a connect-time copy is safe. Forwarding in connect works for both styles, which is why it is the convention.

    Watch it run: a real walk over the same tree

    The model below is plain SystemVerilog, not the UVM library. It builds the DMA tree in the bench's creation order, keeps each node's children sorted by name the way the reference library does, and then runs a real recursive pre-order and post-order walk. The checks are computed from the tree, so they test the walk rather than restate it.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class tnode;
      string name;
      tnode  parent;
      tnode  kids[$];
      function new(string n, tnode p);
        int i;
        name = n;
        parent = p;
        if (p != null) begin
          // Keep children in name order, as the reference library's associative array does.
          i = 0;
          while (i < p.kids.size() && p.kids[i].name < n) i++;
          p.kids.insert(i, this);
        end
      endfunction
    endclass
    
    Diagram
    build (top-down):   test env apb apb.drv apb.mon apb.sqr in_agt in.drv in.mon in.sqr out_agt out.mon sb
    connect (bottom-up): apb.drv apb.mon apb.sqr apb in.drv in.mon in.sqr in_agt out.mon out_agt sb env test
    PASS: 13 nodes, every parent before its children going down and after them going up

    What breaks: two siblings that talk during build

    The renamed-agent bug has a standard shape: one sibling produces something in its build (a config object, a handle in a shared pool, a config_db entry), and another sibling consumes it in its own build. It works until a rename, a new instance, or a different simulator changes which sibling goes first.

    The fix is to move the shared thing up one level. The common parent creates the config object or sets the config_db entry in its own build, which runs before both children, and each child reads it. If the consumer needs a handle to the producer itself, it takes it in connect_phase or later, when every build has finished.

    Do not "fix" a sibling-order bug by renaming instances so that the producer sorts first. The next person to add an instance, or a simulator with a different internal order, breaks it again, and nothing in the code says why.

    "Why is build_phase top-down but connect_phase bottom-up?"

    A weak answer: "Build is top-down because parents create children. Connect is bottom-up so the children's ports exist before the parents connect to them." The second half is wrong: by connect time, all ports exist.

    A strong answer: "Build is top-down because a component can only be built after its parent created it. Connect is bottom-up because components forward handles upward: an agent sets ap = mon.ap in its connect, and the env uses agent.ap in its own connect. connect() calls are only recorded and resolved together before end_of_elaboration, so the order matters for those handle copies, not for the links. Sibling order isn't specified; the reference library happens to use name order, which is a source of bugs, not a feature."

    Follow-ups: Which other phases are bottom-up, and why is check_phase one of them? (a parent can aggregate its children's results) and Which phase besides build is top-down? (final_phase).

    Check yourself In dma_env::build_phase you create the APB agent and, on the next line, read apb.mon. What do you get, and why?

    null. create only constructs the agent. The agent's build_phase, which creates mon, runs after the env's build_phase returns, because the walk is top-down: the env is visited before its children.

    Check yourself The scoreboard (sb) and the APB agent (apb) are siblings. The scoreboard's build reads a setting the agent writes in its build. Why does it work today, and what is the robust fix?

    It works because the reference library visits siblings in name order and apb sorts before sb. That order is not guaranteed by the standard and changes with names. The robust fix is to have the env (their parent) create or set the shared setting in its own build, before either child builds.

    Check yourself Why can check_phase in the env safely read totals computed by its children's check_phase?

    check_phase is bottom-up, so every child's check_phase has run before the env's. The same reasoning makes extract and report bottom-up: parents aggregate what children prepared.

    Next: walks are how function phases run. Task phases do something quite different, covered in Function Phases vs Task Phases.

    Related topics

    • Function Phases vs Task Phases

    • Why build_phase Is Top-Down

    • Connect Phase: Bottom-Up TLM Wiring

    Previous

    21 / 280

    Next: Function Phases vs Task Phases

    Up next

    Up nextFunction Phases vs Task Phases

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Why Phases Exist
    • Signatures, Super Calls, and Debug
    • Inside the Phaser: Nodes, Imps and Schedules