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
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
    • Environment Architecture
      • The Environment and Its Config Object
      • Vertical Reuse: From Block to Subsystem
      • Integrating Third-Party VIP
      • Where the Virtual Sequencer Lives (and Whether You Need One)
    • Tests: Base Classes, Selection and Startup
  • 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

‹Environment Architecture
  1. Topics
  2. UVM Verification
  3. Environment & Test Classes
  4. Environment Architecture
  5. Where the Virtual Sequencer Lives (and Whether You Need One)

Part 7 · Environment & Tests · Intermediate

Where the Virtual Sequencer Lives (and Whether You Need One)

The environment-design side of virtual sequencers: env-owned virtual sequencer vs handles passed by the test (dma_vseq uses none), keeping the handle map in the env, hierarchical virtual sequencers in a subsystem, and what passive agents do to virtual sequences.

On this page

    The subsystem team asks a reasonable question in a design review: "The block env has no virtual sequencer, and dma_vseq_test assigns env.apb.sqr and env.in_agt.sqr by hand. We now have two DMAs, and the chip will have fourteen agents. Do we add a virtual sequencer to every env, one at the top, or keep passing handles?" Each answer is defensible. Which one survives the next two levels of integration?

    The idea: the question is who owns the handle map

    A virtual sequence needs handles to real sequencers. How it gets them is covered elsewhere: a virtual sequencer holds them and the sequence reaches them through p_sequencer (Virtual Sequencer Wiring), or the test assigns them to the sequence's fields and calls start(null) (Virtual Sequences Without a Virtual Sequencer). This lesson asks the environment-design question behind both: which component knows the map from "the APB sequencer of DMA 1" to a path like env.dma1.apb.sqr?

    Keys in an office building make the point. A tenant can keep a key rack for its own rooms (a virtual sequencer per block env). The front desk can keep a master rack whose hooks point at the tenants' racks (a hierarchical virtual sequencer). Or the building manager can hand keys to each contractor personally (handles passed by the test). All three open doors. The difference is who has to change when a tenant moves rooms.

    The picture: a hierarchical virtual sequencer

    Tree: the subsystem virtual sequencer points to dma0's and dma1's virtual sequencers, which point to their APB and stream sequencers; dma1's stream sequencer handle is null because the agent is passive. A subsystem vseq starts on the top and starts block vseqs on the block virtual sequencers.
    One option for the subsystem: each block env keeps its own virtual sequencer, and the subsystem env's virtual sequencer holds handles to them. A subsystem virtual sequence starts block virtual sequences on the block virtual sequencers. dma1's input handle is null because that agent is passive.

    Build it: three placements, one decision

    Option 1: no virtual sequencer, handles from the test (the bench)

    dma_vseq_test writes vseq.apb_sqr = env.apb.sqr; vseq.axis_sqr = env.in_agt.sqr;. It is simple, type-checked at compile time, and fine for one block. Its cost is that the env's internal paths now appear in every test that starts a virtual sequence. Rename in_agt, and every such test changes. At subsystem level, the test writes env.dma0.in_agt.sqr, so it also knows the subsystem env's layout.

    Option 2: keep the map in the env, without a virtual sequencer

    You can keep handle passing and still put the map in one place: give the env a function that fills a virtual sequence's handles. The test calls it instead of naming paths.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // snippet
    // In a derived dma_env: the one place that knows its own paths.
    function void fill_vseq(dma_vseq vseq);
      vseq.apb_sqr  = apb.sqr;
      vseq.axis_sqr = in_agt.sqr;          // null if in_agt is passive
    endfunction
    
    // In the test's run_phase:
    //   env.fill_vseq(vseq);  vseq.irq_vif = irq_vif;  vseq.start(null);

    This keeps the compile-time checking of option 1 and the encapsulation of a virtual sequencer, with no extra component. What it does not give you is a place in the hierarchy: no full name for reporting and per-path config, and no default_sequence.

    Option 3: an env-owned virtual sequencer, hierarchical at subsystem level

    Each block env builds a virtual sequencer as a sibling of its agents and assigns its handles in connect_phase. The subsystem env builds one more, whose handles point at the block virtual sequencers:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // snippet
    class dma_subsys_vsqr extends uvm_sequencer;
      `uvm_component_utils(dma_subsys_vsqr)
      dma_vsqr dma0_vsqr, dma1_vsqr;       // the block envs' virtual sequencers
    
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
    endclass
    
    // In dma_subsys_env (with block envs that build v_sqr):
    //   build_phase:   v_sqr = dma_subsys_vsqr::type_id::create("v_sqr", this);
    //   connect_phase: v_sqr.dma0_vsqr = dma0.v_sqr;  v_sqr.dma1_vsqr = dma1.v_sqr;
    // In a subsystem vseq body, with uvm_declare_p_sequencer(dma_subsys_vsqr):
    //   blk0.start(p_sequencer.dma0_vsqr, this);

    The block virtual sequences run unchanged on the block virtual sequencers, and the subsystem sequence only orders them. That is the main reason to choose this option: block-level virtual sequences become reusable building blocks one level up, and each level's paths are known only to that level's env.

    What passive agents do to all three

    At subsystem level dma1.in_agt is passive, so dma1.in_agt.sqr is null whichever option you pick. Any block virtual sequence that needs a stream sequencer cannot run on dma1. The bench's dma_vseq checks for exactly that and stops with [VSEQ] set apb_sqr and axis_sqr before starting. The fix is not to make the agent active (it would fight dut0 on the middle bus); it is to split the virtual sequence into a part that programs and services one DMA (APB and interrupt) and a part that supplies data, so the subsystem sequence can program both DMAs and stream only into dma0. Whatever holds handles should also null-check them in connect_phase and say which agent is passive, so the failure names its cause.

    Choosing

    Situation

    Usual choice

    Why

    One block, a few virtual sequences, one author

    Handles from the test, or an env fill function

    Least machinery; compile-time checks.

    Block env meant for reuse; several vseq authors

    Env-owned virtual sequencer

    Paths stay inside the env; vseqs get a reporting name and per-path config.

    Subsystem built from block envs that have virtual sequencers

    Hierarchical virtual sequencer

    Block vseqs run unchanged; the subsystem vseq orders them.

    SoC with mostly passive agents

    A small top virtual sequencer

    Few active agents remain; it mainly holds their handles, events and config.

    Watch it run: the reporting name tells you which option ran

    The bench's dma_vseq_test, with no virtual sequencer, and a scratch version with an env-owned one from the wiring lesson, print the same job line under different names (+UVM_VERBOSITY=UVM_MEDIUM; times differ between the two builds):

    Diagram
    UVM_INFO dma_tb.sv(623) @ 575000: reporter@@vseq [VSEQ] job done: STATUS=00000002 after 4 polls
    UVM_INFO dma_tb.sv(623) @ 545000: uvm_test_top.env.v_sqr@@vseq [VSEQ] job done: STATUS=00000002 after 4 polls
    • reporter@@vseq: the sequence has no sequencer, so it reports through the global reporter, and its config_db full name is just vseq.

    • uvm_test_top.env.v_sqr@@vseq: the env owns a virtual sequencer, so the sequence has a place in the hierarchy. Per-path verbosity, config_db scoping and default_sequence all work from that name.

    • Either way, the interrupt handler's line reads uvm_test_top.env.apb.sqr@@vseq.isr: the work happens on the real sequencer.

    What breaks: reusing the block virtual sequence on dma1

    The subsystem engineer's first attempt reuses dma_vseq twice, filling one from dma0's handles and one from dma1's: vseq1.axis_sqr = env.dma1.in_agt.sqr. The second start stops at once with the vseq's own fatal, set apb_sqr and axis_sqr before starting, because dma1's input agent is passive and built no sequencer. Their first instinct is to set chain_internal = 0 to get a sequencer, which brings back the two-drivers-on-one-bus problem from Vertical Reuse.

    The real fault is in the block virtual sequence's design, not in the env: it assumed it owned every interface of its DMA. Splitting it into a programming-and-interrupt sequence and a data sequence made both reusable, and the subsystem virtual sequence became a few lines that order them. Decide this at block level, when you write the first virtual sequence: ask which of its interfaces could be internal one level up.

    Interview

    "Where should the virtual sequencer live in a multi-level environment, and do you always need one?"

    A weak answer: "In the top env, holding every sequencer in the chip." One flat registry at the top couples every test to every block's internals and stops block virtual sequences from being reused.

    A strong answer: "You don't always need one: a virtual sequence only needs handles, and for one block, handles from the test or an env fill function are simplest. For a reusable block env I put a virtual sequencer in the env, as a sibling of the agents, with handles assigned and null-checked in connect_phase. At subsystem level the subsystem env's virtual sequencer holds the block virtual sequencers, so block vseqs run unchanged and each env knows only its own paths. Passive agents mean null sequencer handles one level up, so I write block vseqs that don't assume they own every interface."

    Follow-ups: What does a virtual sequencer give you that handles don't? (a hierarchical name for reporting and config, default sequences, one owner for the map). Why assign handles in connect_phase, not build_phase? (each agent builds its sequencer in its own build_phase, after the env's returns).

    Check yourself Your block env has no virtual sequencer and twelve tests assign env.in_agt.sqr to their virtual sequences. The agent is renamed to stream_in. What changes, and what would have limited it?

    All twelve tests change, because each one knows the env's internal path. An env fill function or an env-owned virtual sequencer would have kept the path in one place, the env, so the rename would touch one file.

    Check yourself In the hierarchical option, why does the subsystem virtual sequencer hold handles to the block virtual sequencers rather than directly to all four real sequencers?

    So block virtual sequences can run unchanged on their own block virtual sequencer, and so the subsystem env never depends on what is inside a block env. If a block env adds an agent, only that block's virtual sequencer changes. A flat list of real sequencers would make the subsystem level re-implement every block-level coordination.

    Next: the environment is designed; the tests that drive it are next. Continue with Tests: Base Classes, Selection and Startup.

    Related topics

    • Virtual Sequencer Wiring

    • Virtual sequences without a virtual sequencer

    • Declaring p_sequencer

    • Tests: Base Classes, Selection and Startup

    Previous

    118 / 280

    Next: Tests: Base Classes, Selection and Startup

    Up next

    Up nextTests: Base Classes, Selection and Startup

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • The Environment and Its Config Object
    • Vertical Reuse: From Block to Subsystem