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
    • UVM Phases: Build, Connect, Elaborate and Run
      • Why build_phase Is Top-Down
      • config_db Gets and Conditional Construction
      • Connect Phase: Bottom-Up TLM Wiring
      • Elaboration, Start and the Pre-Time-Zero Contract
      • The Run Phase: Parallel Execution and Duration
      • Runtime Sub-Phases: Reset, Configure, Main and Shutdown
      • What super.build_phase() Really Does
      • run_test, Timeouts and How a Simulation Ends
    • 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

‹UVM Phases: Build, Connect, Elaborate and Run
  1. Topics
  2. UVM Verification
  3. UVM Phases & Simulation Lifecycle
  4. UVM Phases: Build, Connect, Elaborate and Run
  5. Elaboration, Start and the Pre-Time-Zero Contract

Part 2 · Phases & Lifecycle · Intermediate

Elaboration, Start and the Pre-Time-Zero Contract

What the library does when end_of_elaboration starts, how to audit the finished tree, and what belongs in start_of_simulation.

On this page

    A regression of 400 tests has one that, on a rarely used configuration, builds two scoreboards instead of one. Both see every transaction, both match, the test passes, and the coverage numbers are double-counted for a month. Nothing was wrong at run time. The tree was wrong at time 0, and nobody looked at it there.

    The idea: the inspection before the doors open

    Between the last connect and the first clock edge there is a moment when the whole testbench exists and nothing has happened yet. It is the cheapest moment to find a structural mistake: no waveform, no transaction, just the tree. UVM gives that moment two phases. end_of_elaboration_phase is the building inspection: is everything that should be here, here, and nothing else? start_of_simulation_phase is the announcement as the doors open: this is the test, this is the seed, this is the configuration. Both are functions, both run at time 0, and both walk the tree bottom-up.

    The picture: what happens as end_of_elaboration starts

    Flowchart: after connect, bindings are resolved, the topology is optionally printed, and if any UVM_ERROR has been reported the test stops with BUILDERR; otherwise end_of_elaboration and start_of_simulation methods run and then run_phase starts.
    The library's work at the start of end_of_elaboration (done by uvm_root before your methods run), followed by your audits and banners. Any error reported during build or connect stops the test here.

    The diamond is the rule worth remembering: any `UVM_ERROR` reported before end_of_elaboration ends the test at time 0 with BUILDERR. A missing config, a binding error, a raise in build: none of them gets a chance to run traffic and confuse the picture.

    Build it: the base test's audit

    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
    // snippet: from the course DMA bench
    class dma_base_test extends uvm_test;
      `uvm_component_utils(dma_base_test)
      dma_env env;
      virtual irq_if irq_vif;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        env = dma_env::type_id::create("env", this);
        if (!uvm_config_db#(virtual irq_if)::get(this, "", "irq_vif", irq_vif))
          `uvm_fatal("NOVIF", "irq_if not set for the test")
      endfunction
    
      function void end_of_elaboration_phase(uvm_phase phase);
        uvm_root::get().print_topology();
      endfunction
    
      // Register helpers used by every test.
      task reg_write(bit [7:0] addr, bit [31:0] data);
        apb_rw_seq s = apb_rw_seq::type_id::create("s");
        s.addr = addr; s.write = 1; s.data = data;
        s.start(env.apb.sqr);
      endtask
    
      task reg_read(bit [7:0] addr, output bit [31:0] data);
        apb_rw_seq s = apb_rw_seq::type_id::create("s");
        s.addr = addr; s.write = 0; s.data = 0;
        s.start(env.apb.sqr);
        data = s.rdata;
      endtask
    
      // One complete job: program the DMA, stream n words, wait for the interrupt,
      // return STATUS and clear its DONE/ERR bits.
      task run_job(dma_job_cfg job, int unsigned n_words, output bit [31:0] status);
        axis_pkt_seq pkt = axis_pkt_seq::type_id::create("pkt");
        reg_write(KEY, job.key);
        reg_write(LEN, job.len);
        reg_write(CTRL, {30'b0, job.irq_en, 1'b1});
        if (!pkt.randomize() with { n == n_words; })
          `uvm_fatal("RAND", "axis_pkt_seq randomize failed")
        pkt.start(env.in_agt.sqr);
        wait (irq_vif.irq === 1'b1);
        reg_read(STATUS, status);
        reg_write(STATUS, 32'h6);                // write 1 to clear DONE and ERR
      endtask
    endclass

    The bench's base test prints the topology in its end_of_elaboration_phase. Two details are worth knowing. The bench calls uvm_root::get().print_topology(); older code writes uvm_top, the global handle for the root, which IEEE 1800.2 deprecates, so uvm_root::get() is the form to copy. And instead of overriding the phase just to print, a test can set uvm_root::get().enable_print_topology = 1 in build, and the library prints it at the step shown in the diagram.

    A real audit checks what the configuration promised against what was built. Errors here are cheap and precise, because no simulated time has passed:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // snippet
    function void end_of_elaboration_phase(uvm_phase phase);
      dma_scoreboard sbs[$];
      uvm_component  found[$];
      super.end_of_elaboration_phase(phase);
      uvm_root::get().find_all("uvm_test_top.env.*", found);
      foreach (found[i]) begin
        dma_scoreboard s;
        if ($cast(s, found[i])) sbs.push_back(s);
      end
      if (sbs.size() != 1)
        `uvm_error("ELAB", $sformatf("expected 1 scoreboard, built %0d", sbs.size()))
      if (env.out_agt.get_is_active() != UVM_PASSIVE)
        `uvm_error("ELAB", "out_agt must be passive: the DUT drives that port")
      check_config_usage();   // report config_db settings nobody read
    endfunction

    Errors reported from end_of_elaboration do not trigger BUILDERR (that check has already run), so the test continues and the error is counted in the final summary. Use uvm_fatal when continuing makes no sense, such as a null interface handle.

    Choose the severity by what continuing would cost:

    Finding

    Severity

    Why

    Topology printed, configuration echoed

    uvm_info

    Documentation for triage, not a problem.

    Passive agent with a driver, unused config setting

    uvm_warning or uvm_error

    Runnable but wrong; an error makes regressions notice.

    Scoreboard count or agent mode differs from the configuration

    uvm_error

    The test would run and its results would mean something else.

    Null virtual interface, missing mandatory component

    uvm_fatal

    The first access would crash anyway; stop with a clear message.

    Because end_of_elaboration is bottom-up, each agent can audit itself before the env aggregates: the agent checks its own mode and handles, the env checks counts across agents, and the test checks the configuration as a whole. A finding is then reported by the component that owns it, which makes the message point at the right file.

    start_of_simulation: say what is about to run

    start_of_simulation is the last moment before time moves, and the right place for a banner a triage engineer will read first: the test name, the seed, and any configuration chosen randomly in build. Keep it to printing and arming. Anything that waits, drives a pin or starts a sequence is a task and belongs in run_phase.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    // snippet
    function void start_of_simulation_phase(uvm_phase phase);
      string seed;
      super.start_of_simulation_phase(phase);
      // The regression script passes the seed to the simulator and also as +SEED=...
      if (!$value$plusargs("SEED=%s", seed)) seed = "see simulator log";
      `uvm_info("BANNER", $sformatf("TEST=%s SEED=%s", get_type_name(), seed), UVM_NONE)
    endfunction

    start_of_simulation is also bottom-up, so a test-level banner prints after any banners its children print, and it is the last line before traffic. That makes it the natural place for the one line a triage engineer copies into a bug report: test, seed, and the handful of randomized choices that make this run different from the last.

    Seed reporting is simulator-specific: each tool has its own seed switch and prints the seed it used in its own way. $get_initial_random_seed is a vendor extension, not standard SystemVerilog, so a portable bench echoes a plusarg its regression script sets.

    Watch it run

    The smoke test's topology appears at time 0, before any traffic. Reading it is a habit worth building: look for the type you expect for every component you configured or overrode, and for components that should not be there.

    Diagram
    UVM_INFO uvm_root.svh(531) @ 0: reporter [UVMTOP] UVM testbench topology:
    ...
          out_agt                axis_agent              -     @272
            mon                  axis_monitor            -     @674
          sb                     dma_scoreboard          -     @280

    What breaks: the hang at time zero

    A test hangs with the simulator's time stuck at 0. A colleague says "something in end_of_elaboration must be waiting". It cannot be: a function cannot contain a wait, and the compiler would have refused it. A hang at time 0 is almost always a zero-delay loop in a task phase: a forever in a run_phase with no blocking statement on some path, a while (!flag) that never yields, or two threads waking each other through events in the same time step.

    In this case it was a monitor's forever begin if (vif.valid) ... end with the clock wait inside the if. Before reset, valid was X, the if never entered, and the loop spun forever at time 0. Moving @(vif.mon_cb) to the top of the loop, as both bench monitors do, fixed it. When time does not advance, look for the loop that does not block, not for a phase that waits.

    check_config_usage() deserves a place in every base test's audit. It lists every config_db setting that no component ever read. Most entries are harmless leftovers, but a misspelled field name or a path that matches nothing shows up there at time 0, long before anyone wonders why a knob had no effect.

    Back to the opening story: the double scoreboard was found by adding the scoreboard count check above, and the cause was a configuration flag that created a second scoreboard "for the checker team". The audit now fails that configuration at time 0.

    Interview

    "What would you put in end_of_elaboration_phase, and why there rather than in build or run?"

    A weak answer: "Printing the topology." True, but it misses why the phase exists.

    A strong answer: "Structural audits. It runs after every build and connect, after bindings are resolved, and before time moves, so the tree is final and checks are cheap. I compare what was built against the configuration (counts, active and passive modes, override types), call check_config_usage, and print the topology. I also know that uvm_root stops the test with BUILDERR if any UVM_ERROR happened before this phase, so build and connect errors never reach run."

    Follow-ups: Why do both these phases run bottom-up? (a parent can aggregate its children's audits) and Where does a reset sequence go? (a task phase: run_phase or reset_phase, never a function phase).

    Check yourself A config_db get fails in an agent's build and reports uvm_error, not uvm_fatal. What happens to the test?

    It stops at time 0 with BUILDERR as end_of_elaboration starts, because an error was reported before that phase. Using uvm_error is enough to stop the test; uvm_fatal stops it immediately and names the line.

    Check yourself Your end_of_elaboration audit reports uvm_error("ELAB", ...). Does the test stop?

    No: the BUILDERR check runs before your end_of_elaboration methods. The error is counted, so the test fails in the summary, but traffic still runs. Use uvm_fatal if running makes no sense.

    Check yourself Is it legal to start a sequence in start_of_simulation_phase?

    No. start() is a task and start_of_simulation_phase is a function, so it will not compile. Sequences start in run_phase or a runtime phase.

    Next: time is about to move. The Run Phase: Parallel Execution and Duration shows who decides how long it moves for.

    Related topics

    • The Run Phase: Parallel Execution and Duration

    • End-of-Test Chain: Extract, Check, Report and Final

    • Composition debug

    Previous

    28 / 280

    Next: The Run Phase: Parallel Execution and Duration

    Up next

    Up nextThe Run Phase: Parallel Execution and Duration

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Why build_phase Is Top-Down
    • config_db Gets and Conditional Construction
    • Runtime Sub-Phases: Reset, Configure, Main and Shutdown