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
    • UVM Factory: Registration, Overrides and Debug
    • config_db and Resources
      • The set/get Contract: Four Arguments That Must Agree
      • Instance Paths and Wildcards: Who Can See a Setting
      • config_db Resolution: Who Wins at Build and After
      • Config Objects: One Typed Handle Instead of Twenty Knobs
      • config_db Performance and Anti-Patterns
      • Auto-Config and Field Macros on Components
      • Configuring Sequences
  • 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

‹config_db and Resources
  1. Topics
  2. UVM Verification
  3. Factory & Configuration
  4. config_db and Resources
  5. Configuring Sequences

Part 3 · Factory & Configuration · Intermediate

Configuring Sequences

How a sequence reads config_db: its full name is its sequencer's path (or its parent sequence's name) plus its own, so it gets with get(null, get_full_name(), ...); the sequencer and p_sequencer alternatives; and per-path knobs.

On this page

    The regression owner wants the DMA input stream to run back-to-back (no idle beats) in some runs and bursty in others, chosen per run, without editing tests. The packet sequence is the natural place for that knob. You type uvm_config_db#(int)::get(this, "", "max_idle", max_idle) in its body, and the compiler refuses: this is a sequence, and config_db's context must be a uvm_component. Sequences are objects. Where do they read config from?

    The idea: a sequence borrows an address while it runs

    A sequence has no permanent place in the component tree, but once it is started it has a full name built from where it runs. A sequence started on a sequencer with no parent sequence is named after that sequencer: uvm_test_top.env.in_agt.sqr.pkt. A sequence started by a parent sequence is named after the parent: vseq.pkt, or uvm_test_top.env.vsqr.vseq.pkt if the parent ran on a virtual sequencer. Like a visitor's badge, the address is valid while it is in the building, and it says who brought the visitor in.

    config_db needs a context and a string. A sequence passes null (the top) and its own full name, so the key is exactly its badge: uvm_config_db#(T)::get(null, get_full_name(), "field", value). Any set whose scope matches that string reaches it, with the usual precedence rules.

    The picture

    Diagram: the test sets max_idle under the input sequencer; the started sequence has a full name under the sequencer and can read config by its own name, through its sequencer, or from p_sequencer's config object.
    Three ways a running sequence reads configuration. The first matches settings by the sequence's own full name; the second reads what was set for its sequencer; the third reads a config object the sequencer fetched in build_phase.

    Build it: a packet sequence with a per-path knob

    The bench's sequence

    axis_pkt_seq is configured by whoever starts it: the test randomizes n with an inline constraint before start. That is the simplest configuration of all, and the right one when the starter owns the knob:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // snippet: from the course DMA bench
    // A packet of n words; the last word carries TLAST.
    class axis_pkt_seq extends uvm_sequence #(axis_item);
      `uvm_object_utils(axis_pkt_seq)
      rand int unsigned n;
      constraint n_c { n inside {[1:16]}; }
    
      function new(string name = "axis_pkt_seq");
        super.new(name);
      endfunction
    
      task body();
        for (int i = 0; i < n; i++) begin
          axis_item it = axis_item::type_id::create("it");
          start_item(it);
          if (!it.randomize() with { last == (i == n - 1); })   // i and n are not item fields
            `uvm_fatal("RAND", "axis_item randomize failed")
          finish_item(it);
        end
      endtask
    endclass

    Reading a knob by the sequence's own path

    When the knob belongs to the regression, not to the starter, the sequence reads it. Do it in body() (or pre_body()), after start has given the sequence its full name:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // snippet: a packet sequence that caps idle cycles from config
    class axis_gap_seq extends axis_pkt_seq;
      `uvm_object_utils(axis_gap_seq)
      int unsigned max_idle = 8;                 // default when nothing is set
    
      function new(string name = "axis_gap_seq");
        super.new(name);
      endfunction
    
      task body();
        // started on in_agt.sqr: get_full_name() is "uvm_test_top.env.in_agt.sqr.<name>"
        void'(uvm_config_db#(int unsigned)::get(null, get_full_name(), "max_idle", max_idle));
        for (int i = 0; i < n; i++) begin
          axis_item it = axis_item::type_id::create("it", null, get_full_name());
          start_item(it);
          if (!it.randomize() with { last == (i == n - 1); idle <= max_idle; })
            `uvm_fatal("RAND", "axis_item randomize failed")
          finish_item(it);
        end
      endtask
    endclass

    The test (or the command line) sets the knob below the sequencer, so it reaches any sequence running there, whatever its name:

    design.sv
    // snippet: in a test's build_phase
    uvm_config_db#(int unsigned)::set(this, "env.in_agt.sqr.*", "max_idle", 0);   // back-to-back

    Reading through the sequencer

    Two alternatives trade per-sequence reach for simplicity. get(m_sequencer, "", ...) reads what was set for the sequencer itself (scope uvm_test_top.env.in_agt.sqr), so every sequence on it shares the value. Or the sequencer fetches a config object in its own build_phase, and sequences reach it through p_sequencer, which requires a sequencer class and ` uvm_declare_p_sequencer ``:

    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
    // snippet: config held by the sequencer, read through p_sequencer
    class axis_cfg_sequencer extends uvm_sequencer #(axis_item);
      `uvm_component_utils(axis_cfg_sequencer)
      axis_agent_cfg cfg;
      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#(axis_agent_cfg)::get(this, "", "cfg", cfg))
          `uvm_fatal("NOCFG", "axis_agent_cfg not set")
      endfunction
    endclass
    
    class axis_cfg_seq extends uvm_sequence #(axis_item);
      `uvm_object_utils(axis_cfg_seq)
      `uvm_declare_p_sequencer(axis_cfg_sequencer)
      function new(string name = "axis_cfg_seq");
        super.new(name);
      endfunction
      task body();
        int unsigned cap = p_sequencer.cfg.max_idle;   // one shared, typed object
        // ...
      endtask
    endclass

    p_sequencer is the cleanest choice inside one agent's VIP. Starting such a sequence on a sequencer of the wrong type fails at start with a DCLPSQ fatal from the cast; see p_sequencer.

    Changing a sequence knob during the run

    Because sequences usually read config in body(), at run time, a set made during run_phase reaches the next sequence that starts. A virtual sequence can switch the input stream to back-to-back halfway through a test with uvm_config_db#(int unsigned)::set(null, "uvm_test_top.env.in_agt.sqr.*", "max_idle", 0). Made outside build, that set has precedence 1000 and is the newest, so it beats the test's build-time value, as the precedence rules say. Sequences already running keep the value they read.

    default_sequence: config that starts a sequence

    config_db can also tell a sequencer which sequence to run on its own, in a given phase. The key is a uvm_object_wrapper (the sequence's proxy), set on the sequencer's path plus the phase name:

    design.sv
    // snippet: in a test's build_phase
    uvm_config_db#(uvm_object_wrapper)::set(this, "env.in_agt.sqr.main_phase",
                                            "default_sequence", axis_pkt_seq::type_id::get());

    The sequencer creates the sequence through the factory (so overrides apply), randomizes it and starts it when that phase begins. It runs in a runtime phase such as main_phase, alongside run_phase, and it needs an objection to keep that phase open (set_automatic_phase_objection(1) in the sequence, or the test's own objection). The DMA bench starts every sequence explicitly from the test, which is easier to read and debug; default sequences are common in VIP demos and older benches.

    Watch it run

    The bench's random test starts axis_pkt_seq on in_agt.sqr for each job, so a per-path knob set under env.in_agt.sqr.* reaches every packet. The baseline run, with the item's default idle distribution (random values differ by simulator and seed):

    Diagram
    +UVM_TESTNAME=dma_rand_test
    UVM_INFO dma_tb.sv(731) @ 0: uvm_test_top [JOB] key=90fdd8fd len=10 irq_en=1
    UVM_INFO dma_tb.sv(731) @ 645000: uvm_test_top [JOB] key=0e564b57 len=1 irq_en=1
    UVM_INFO dma_tb.sv(731) @ 885000: uvm_test_top [JOB] key=5a1793f4 len=3 irq_en=1
    UVM_INFO dma_tb.sv(411) @ 1305000: uvm_test_top.env.sb [SB] 14 words matched, 0 mismatched

    With axis_gap_seq and max_idle set to 0, the word count stays at 14 and the jobs finish earlier in simulated time, because no beat waits. The scoreboard does not care about timing, which is exactly what makes a timing knob safe to vary per run.

    What breaks: the knob that stopped at the virtual sequence

    A team moved their stimulus into a virtual sequence started with vseq.start(null) from the test. Sub-sequences were started with pkt.start(in_sqr, this). Overnight, every "back-to-back" regression became bursty again. The set was unchanged: env.in_agt.sqr.*. But a sequence with a parent takes its name from the parent, so pkt was now vseq.pkt, not uvm_test_top.env.in_agt.sqr.pkt. The get missed, and the default of 8 applied silently.

    Three fixes, depending on intent. Set on the new path (vseq.* or *.pkt); read through the sequencer (get(m_sequencer, "", ...)), which does not depend on who started the sequence; or let the virtual sequence read the knob once and pass it to its children as a field. Whatever you pick, log the value a sequence actually uses at UVM_MEDIUM, so a silent default is one grep away.

    Interview

    "How does a sequence get configuration from config_db?"

    A weak answer: "Same as a component, uvm_config_db get with this."

    A strong answer: "A sequence isn't a component, so it uses null as the context and its full name as the instance string: get(null, get_full_name(), ...). Once started, its full name is its parent sequence's full name plus its name, or its sequencer's path plus its name. So a test can set below env.agt.sqr.* to reach sequences on that sequencer. I read config in body, after start. Alternatives are get(m_sequencer, "", ...) for per-sequencer values, or a config object on the sequencer reached through p_sequencer. If the starter owns the knob, I just set the field or randomize with before start."

    Follow-ups: Why might the get miss for a sub-sequence of a virtual sequence? (its name follows the parent chain) and What happens if you read config in the sequence's constructor? (no sequencer yet, so the full name is just the name).

    Check yourself A test creates axis_gap_seq named gap, and starts it on env.in_agt.sqr with no parent. What key does its get use, and does set(this, "env.in_agt.sqr.*", "max_idle", 0) from the test reach it?

    The key is uvm_test_top.env.in_agt.sqr.gap. The test's set has scope uvm_test_top.env.in_agt.sqr.*, which matches, so the sequence reads 0.

    Check yourself The same sequence reads max_idle in its constructor instead of body(). What does it read?

    Its default. In the constructor the sequence has no sequencer and no parent yet, so get_full_name() is just gap, which does not match uvm_test_top.env.in_agt.sqr.*. Read config after start, in pre_body() or body().

    Check yourself When is config_db the wrong tool for a sequence knob?

    When the code that starts the sequence owns the decision. Then setting the field or using randomize() with before start is direct, typed and visible in the test, as the bench does with n. config_db earns its place when the knob comes from far away: the command line, a base test, or a sequence created deep inside a virtual sequence.

    Next: with the factory and config_db in hand, the next part connects components to each other: TLM.

    Related topics

    • Resolution order: who wins

    • Overriding items and sequences

    • p_sequencer

    • Virtual sequences

    Previous

    59 / 280

    Next: Transaction-Level Modeling (TLM)

    Up next

    Up nextTransaction-Level Modeling (TLM)

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • The set/get Contract: Four Arguments That Must Agree
    • Instance Paths and Wildcards: Who Can See a Setting
    • config_db Resolution: Who Wins at Build and After