Configuring UVM sequences means giving a sequence the settings and handles it needs, such as a packet count, an error-injection switch, the agent's configuration object or the register model, without hard-coding them. A sequence can be configured by setting its fields before start(), by randomizing it with inline constraints, or by reading values from uvm_config_db inside body(), using its sequencer or its own full name as the lookup scope.

Sequences are objects, not components. They are created and destroyed during the run, have no build_phase, and don't have a fixed place in the component hierarchy. That changes how configuration reaches them, and it is the source of most "config_db get failed" errors inside sequences.

8 min read | Intermediate

What You'll Learn

  • Configure a sequence directly from the code that starts it
  • Read configuration from uvm_config_db inside a sequence
  • Target settings at one particular sequence by its name
  • Use a base sequence to fetch shared handles once

Option 1: Set Fields Before start()

The simplest configuration needs no database at all. Whoever starts the sequence, a test or a virtual sequence, sets its fields or randomizes it first:


class burst_seq extends uvm_sequence #(bus_item);
  `uvm_object_utils(burst_seq)
  rand int unsigned num_items;
  rand bit          inject_error;
  constraint c_num { soft num_items inside {[4:16]}; }
  // Rest of the code (constructor, body)
endclass

// In a test or virtual sequence
burst_seq seq = burst_seq::type_id::create("seq");
if (!seq.randomize() with { num_items == 32; inject_error == 0; })
  `uvm_fatal("TEST", "Randomization failed")
seq.start(m_env.m_agent.m_seqr);

This is the right choice whenever the starter knows the values. It is explicit, it shows up in the test where a reader expects it, and the soft constraint gives a sensible default that any test can override. Use the configuration database only for values the starter doesn't know, such as settings that belong to the environment or handles created deep in the testbench.

Option 2: Read from uvm_config_db in body()

A sequence can call uvm_config_db::get() like a component can. The question is what to use as the scope, because a sequence has no hierarchical path of its own. There are two standard answers:


// a) Use the sequencer as the context: looks up the sequencer's path
if (!uvm_config_db #(bus_agent_cfg)::get(m_sequencer, "", "bus_agent_cfg", m_cfg))
  `uvm_fatal("CFG", "bus_agent_cfg not found")

// b) Use the sequence's own full name: sequencer path plus the sequence name
if (!uvm_config_db #(bus_agent_cfg)::get(null, get_full_name(), "bus_agent_cfg", m_cfg))
  `uvm_fatal("CFG", "bus_agent_cfg not found")

For a sequence started on uvm_test_top.m_env.m_agent.m_seqr with the name seq, the first form searches for uvm_test_top.m_env.m_agent.m_seqr and the second for uvm_test_top.m_env.m_agent.m_seqr.seq. A sequence's get_full_name() returns its sequencer's full name followed by its own name. A child sequence adds its parent's name too, as in m_seqr.seq.child.

Either works with the most common setting, made by the environment for an agent and everything below it:


// In the env's build_phase
uvm_config_db #(bus_agent_cfg)::set(this, "m_agent*", "bus_agent_cfg", m_agent_cfg);

The wildcard after m_agent matches the agent, its sequencer, and any sequence path under the sequencer, so both lookups find the object. Note the absence of a dot before the *: "m_agent.*" would match everything below the agent but not the agent itself.

Both lookups need m_sequencer, which is only set once the sequence has been started. Call get() in body() or pre_start(), never in the constructor.

Option 3: Settings for One Particular Sequence

Because the get_full_name() form includes the sequence's name, a setting can be aimed at specific sequences by naming them. Here every sequence on the agent gets the normal configuration, while sequences whose names start with err get one with error injection switched on:


// In the test's build_phase: general setting first, specific one second
uvm_config_db #(bus_agent_cfg)::set(this, "m_env.m_agent*",           "bus_agent_cfg", normal_cfg);
uvm_config_db #(bus_agent_cfg)::set(this, "m_env.m_agent.m_seqr.err*", "bus_agent_cfg", error_cfg);

// Later
burst_seq normal_seq = burst_seq::type_id::create("normal_seq");
burst_seq err_seq    = burst_seq::type_id::create("err_seq");
normal_seq.start(m_env.m_agent.m_seqr);     // finds normal_cfg
err_seq.start(m_env.m_agent.m_seqr);        // matches both; the later setting wins

The same class behaves differently depending only on the name it was created with. err_seq's path matches both settings. Because both were made from the same place in the hierarchy, the one made last wins, so the general setting must come first and the specific one after it. Swap the two lines and every sequence gets the error configuration. How precedence works in full is covered in UVM config database.

This only works with the get_full_name() lookup. The m_sequencer form looks up the sequencer's path, which is the same for every sequence on it.

Quick Check

Q: A sequence calls uvm_config_db#(int)::get(m_sequencer, "", "num_items", num_items) in its constructor. The test set the value correctly. Why does the get fail?

Show Answer

In the constructor the sequence has not been started yet, so m_sequencer is null. With a null context and an empty instance name the lookup has no path to match against, and fails. Move the call into body() or pre_start().

A Base Sequence for Shared Handles

Many sequences on the same agent need the same things: the agent's configuration object, a register model, a virtual interface for waiting on signals. Fetching them in every sequence repeats code. A base sequence fetches them once, and every sequence extends it and calls super.body() first:


class bus_base_seq extends uvm_sequence #(bus_item);
  `uvm_object_utils(bus_base_seq)
  bus_agent_cfg   m_cfg;
  ral_sys_traffic m_ral;
  // Rest of the code (constructor)

  virtual task body();
    if (!uvm_config_db #(bus_agent_cfg)::get(null, get_full_name(), "bus_agent_cfg", m_cfg))
      `uvm_fatal("CFG", "bus_agent_cfg not found")
    m_ral = m_cfg.m_ral;                         // the register model travels inside the config object
  endtask
endclass

class init_seq extends bus_base_seq;
  `uvm_object_utils(init_seq)
  // Rest of the code (constructor)

  virtual task body();
    uvm_status_e status;
    super.body();                                // handles are now set up
    m_ral.cfg.timer[0].write(status, 32'h400, .parent(this));
  endtask
endclass

Putting handles such as the register model inside the configuration object means sequences only need one lookup, and the environment controls everything they can reach.

Which Option to Use

SituationUse
The starter knows the value (count, mode, address range)Set fields or randomize with inline constraints before start()
The value belongs to the agent or environmentget(m_sequencer, "", ...) or get(null, get_full_name(), ...)
Different sequences on one sequencer need different valuesget(null, get_full_name(), ...) with settings aimed at sequence names
Several sequences need the same handlesA base sequence that fetches them in body()

Common Mistakes

Mistake #1: Calling get() in the constructor

m_sequencer is null until the sequence starts, so neither lookup form has a valid path. Read configuration in body() or pre_start().

Mistake #2: A scope that stops at the agent

A setting with the scope "m_agent" matches only the agent itself, not its sequencer or the sequences running on it. Use "m_agent*" so the setting reaches everything below.

Mistake #3: Expecting per-sequence settings with the m_sequencer lookup

The m_sequencer form looks up the sequencer's path, which every sequence on it shares. Settings aimed at a sequence name are only seen through get_full_name().

Mistake #4: Using the database for values the starter already knows

Passing a packet count through uvm_config_db when the test that starts the sequence could simply set it hides the value and makes the test harder to read. Set fields directly when you can.

Summary

In this article, you learned:

  • Setting fields or randomizing before start() is the simplest and clearest way to configure a sequence
  • Inside body(), a sequence reads uvm_config_db using m_sequencer or get_full_name() as the scope
  • A sequence's full name is its sequencer's path plus its own name, which lets settings target specific sequences
  • Configuration must be read after the sequence has started, never in its constructor
  • A base sequence can fetch shared handles once for every sequence that extends it
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5