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
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
    • Sequence Items and the Driver Handshake
    • Sequence Mechanics: Lifecycle, Sequencer, Responses and Control
    • Virtual Sequences and Coordination
      • The Coordination Problem
      • Virtual Sequencer Wiring
      • Declaring p_sequencer
      • Scenario Patterns and Placement
      • Sub-sequences and Sequence Libraries
      • Virtual Sequences Without a Virtual Sequencer
      • lock vs grab: Who Gets the Sequencer
      • Protocol Layering with Translator Sequences
      • Reactive (Slave) Sequences
  • 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

‹Virtual Sequences and Coordination
  1. Topics
  2. UVM Verification
  3. UVM Sequences & Stimulus
  4. Virtual Sequences and Coordination
  5. Virtual Sequencer Wiring

Part 5 · Sequences · Intermediate

Virtual Sequencer Wiring

The virtual sequencer as a bundle of handles to real sequencers: building it in the env, assigning handles in connect_phase (never build_phase), defending against null handles, and the alternatives.

On this page

    The first chip-level run of your new virtual sequence dies at time 0 with a null object access on the line prog.start(p_sequencer.apb_sqr, this). The virtual sequencer exists, p_sequencer is valid, and the APB agent is in the topology printout with its sequencer. Yet the handle inside the virtual sequencer is null. Where did the assignment go wrong, and how do you make that mistake impossible to miss?

    The idea: a key rack, not a machine

    A virtual sequencer is a component whose only job is to hold handles to the real sequencers (and sometimes a virtual interface or a config object) that virtual sequences need. It has no driver connected to it, so nothing it receives ever reaches a pin. Think of the key rack behind a hotel reception desk: the rack opens no doors itself, but anyone who needs a room knows exactly where to find its key.

    Virtual sequences start on the virtual sequencer, and then reach through it: p_sequencer.apb_sqr is the APB agent's sequencer. Because the virtual sequencer is a component, it lives in the hierarchy, can be found by path, can be overridden by the factory, and can read config_db like any other component. Those are its advantages over handing handles around by hand, which the bench does instead (see the lesson on that).

    The picture

    Flowchart: an env with a virtual sequencer holding handles to the APB and stream sequencers; a virtual sequence runs on the virtual sequencer.
    The virtual sequencer is a sibling of the agents inside the env. Dashed arrows are handles assigned in connect_phase; no driver hangs below the virtual sequencer.

    The virtual sequencer is a sibling of the agents, not their parent. That placement matters for timing: the env builds both, and only in connect_phase do both exist at once.

    Build it on the DMA bench

    The handle bundle

    The class extends uvm_sequencer with its default parameter (uvm_sequence_item); uvm_virtual_sequencer is a typedef for the same thing. It declares one handle per real sequencer, plus the interrupt interface, which it reads from config_db in its own build_phase.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // snippet
    class dma_vsqr2 extends uvm_sequencer;
      `uvm_component_utils(dma_vsqr2)
      apb_sequencer  apb_sqr;                    // assigned by the env in connect_phase
      axis_sequencer axis_sqr;
      virtual irq_if irq_vif;                    // from config_db
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if (!uvm_config_db#(virtual irq_if)::get(this, "", "irq_vif", irq_vif))
          `uvm_fatal("NOVIF", "irq_if not set for the virtual sequencer")
      endfunction
    endclass

    Build in build_phase, assign in connect_phase

    Rather than edit the bench's dma_env, extend it. The derived env builds the virtual sequencer next to the agents, and assigns its handles in connect_phase, then checks them. A test swaps it in with a factory type override before the env is created, the same move dma_truncate_test makes for the driver (see create-time resolution).

    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
    // snippet
    class dma_vsqr_env extends dma_env;
      `uvm_component_utils(dma_vsqr_env)
      dma_vsqr2 v_sqr;
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);                // agents and scoreboard
        v_sqr = dma_vsqr2::type_id::create("v_sqr", this);
      endfunction
    
      function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);              // monitor -> scoreboard
        v_sqr.apb_sqr  = apb.sqr;
        v_sqr.axis_sqr = in_agt.sqr;
        if (v_sqr.apb_sqr == null || v_sqr.axis_sqr == null)
          `uvm_fatal("VSQR", "an agent built no sequencer: is it passive?")
      endfunction
    endclass
    
    // In the test
    function void build_phase(uvm_phase phase);
      dma_env::type_id::set_type_override(dma_vsqr_env::get_type());
      super.build_phase(phase);                  // creates env: now a dma_vsqr_env
      uvm_config_db#(virtual irq_if)::set(this, "env.v_sqr", "irq_vif", irq_vif);
    endfunction

    Why connect_phase and not the env's build_phase? Because build is top-down: the env's build_phase creates the agent objects, but each agent creates its own sequencer in its build_phase, which runs only after the env's returns. At the moment the env would copy apb.sqr, that member is still null. The assignment copies a null, and nothing complains until a sequence dereferences it. In connect_phase every component in the tree has been built, so the handles are real.

    Common pitfall

    Assigning sequencer handles in the env's build_phase never works, not "usually works until an override reorders things". Children are always built after their parent's build_phase returns.

    Why the null check earns its place

    The VSQR fatal looks paranoid, but it catches a real configuration mistake at the end of elaboration instead of mid-test. If someone sets the stream agent passive (to reuse the env at subsystem level, where another block drives that port), the agent builds no sequencer. The assignment then copies null legally, and without the check the first sign is a null access deep inside a sequence, long after the cause. The check turns it into one line that names the problem.

    There are two alternatives to a virtual sequencer worth knowing. A sequence can get handles from config_db (an env does uvm_config_db#(apb_sequencer)::set(...) in connect_phase), or the test can assign them directly to fields of the virtual sequence, which is what the bench does. Both are covered in Virtual Sequences Without a Virtual Sequencer.

    Watch it run

    This log is from a scratch run: the bench plus dma_vsqr2, dma_vsqr_env and a dma_p_vseq that reads its handles through p_sequencer (the next lesson shows it). The topology printout lists the new component, and the job runs exactly as dma_vseq_test does. Random values differ by seed.

    Diagram
    v_sqr                  dma_vsqr2               -     @321
    UVM_INFO dma_tb.sv(572) @ 365000: uvm_test_top.env.apb.sqr@@vseq.isr [IRQ] handler has the APB sequencer
    UVM_INFO dma_tb.sv(623) @ 545000: uvm_test_top.env.v_sqr@@vseq [VSEQ] job done: STATUS=00000002 after 4 polls
    UVM_INFO dma_tb.sv(411) @ 645000: uvm_test_top.env.sb [SB] 6 words matched, 0 mismatched
    UVM_ERROR :    0
    • `uvm_test_top.env.v_sqr@@vseq`: the virtual sequence now has a sequencer, so its messages are reported through it instead of the global reporter.

    • The interrupt handler still runs on env.apb.sqr. The virtual sequencer only handed out the handle; the work happened on the real sequencer.

    What breaks: the null handle with a clean topology

    That opening crash came from a chip-level env where the virtual sequencer's handles were assigned at the end of the env's build_phase, right after create. On the block bench it had seemed to work, because a different engineer had put the assignment in connect_phase there and the chip team copied an older version. The topology print was perfect, which sent everyone looking at the sequence. The log line to look for is a null access at the first p_sequencer.<handle> use. The fix was the connect_phase assignment plus the null check, which would have stopped the run at elaboration with a sentence naming the cause.

    Interview

    "Where does the virtual sequencer live, and when are its handles assigned?"

    Weak: "In the env, in build_phase, after creating the agents."

    Strong: "In the env, as a sibling of the agents. It's built in build_phase, but its handles are assigned in connect_phase, because each agent builds its own sequencer in its own build_phase, after the env's has returned. I check the handles for null right there, because a passive agent builds no sequencer and the copy succeeds silently. The virtual sequencer has no driver; virtual sequences start on it and reach the real sequencers through p_sequencer."

    Follow-ups: Can you avoid having a virtual sequencer at all? (yes: handles assigned by the test or fetched from config_db, then start(null)), and Why is it a uvm_sequencer and not a plain uvm_component? (sequences need a sequencer to start on; it also gives you p_sequencer typing and reporting through a hierarchy path).

    Check yourself An env assigns v_sqr.apb_sqr = apb.sqr in end_of_elaboration_phase instead of connect_phase. Does it work?

    Yes. By end of elaboration every component is built and connected, so the handle is real. connect_phase is the convention because it is where cross-component references are made, and it runs before anything in end_of_elaboration might want to use the handles (for example a sequencer arbitration setting done through the virtual sequencer).

    Check yourself The same env is reused at subsystem level with the stream-in agent passive. What happens, with and without the null check?

    The passive agent builds no sequencer, so in_agt.sqr is null and the assignment copies null. Without the check, the first virtual sequence that starts a packet on p_sequencer.axis_sqr fails with a null access mid-run. With the check, elaboration ends with the VSQR fatal that says an agent built no sequencer. At subsystem level the right fix is a different virtual sequence that does not drive that port.

    Next: the virtual sequence reaches the handles through p_sequencer. What that macro really declares, and the fatal you get when the types do not match, is in Declaring p_sequencer.

    Related topics

    • Build, connect and elaboration

    • Agent architecture

    • Virtual sequences without a virtual sequencer

    Previous

    86 / 280

    Next: Declaring p_sequencer

    Up next

    Up nextDeclaring p_sequencer

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Scenario Patterns and Placement
    • Sub-sequences and Sequence Libraries
    • Virtual Sequences Without a Virtual Sequencer