Getting Configuration in Sequences with Null Sequencer - UVM Guide
When using virtual sequences or layered sequences in UVM, you often start upper-layer sequences on a null sequencer. This creates a challenge: how do you access configuration objects when there's no component hierarchy? This post covers the solutions.
The Problem
In a typical UVM environment:
- Sequences run on sequencers (components in the hierarchy)
uvm_config_dbuses the component hierarchy for context- Virtual sequences often run on
null- no sequencer, no hierarchy!
flowchart TD
subgraph NORMAL["Normal Sequence"]
N1["Sequence"] --> N2["m_sequencer"]
N2 --> N3["Component hierarchy"]
N3 --> N4["config_db::get() works"]
end
subgraph VIRTUAL["Virtual Sequence"]
V1["Virtual Sequence"] --> V2["null sequencer"]
V2 --> V3["No hierarchy!"]
V3 --> V4["config_db::get() fails"]
end
style N4 fill:#d1fae5,stroke:#10b981
style V4 fill:#fee2e2,stroke:#ef4444
Key Issue: Whenm_sequenceris null,get_full_name()returns just the sequence name with no hierarchy path, breaking config_db lookups.
Example: The Failing Approach
class virtual_sequence extends uvm_sequence;
`uvm_object_utils(virtual_sequence)
my_config cfg;
function new(string name = "virtual_sequence");
super.new(name);
endfunction
virtual task body();
// This FAILS when started on null sequencer!
if (!uvm_config_db#(my_config)::get(m_sequencer, "", "cfg", cfg)) begin
`uvm_fatal("CFG", "Failed to get config")
end
// Run sub-sequences...
endtask
endclass
When started with vseq.start(null), m_sequencer is null and the config lookup fails.
Solution 1: Use uvm_root as Context (Recommended)
Use uvm_root::get() as the context for global configurations:
class virtual_sequence extends uvm_sequence;
`uvm_object_utils(virtual_sequence)
my_config cfg;
virtual task body();
// Use uvm_root for context - works with null sequencer!
if (!uvm_config_db#(my_config)::get(uvm_root::get(), "", "cfg", cfg)) begin
`uvm_fatal("CFG", "Failed to get config from uvm_root")
end
`uvm_info("VSEQ", $sformatf("Got config: %s", cfg.convert2string()), UVM_MEDIUM)
endtask
endclass
Setting Config for This Approach
// In test or env build_phase:
uvm_config_db#(my_config)::set(uvm_root::get(), "", "cfg", my_cfg);
// Or with wildcard:
uvm_config_db#(my_config)::set(uvm_root::get(), "*", "cfg", my_cfg);
Solution 2: Use null with Wildcard Path
class virtual_sequence extends uvm_sequence;
`uvm_object_utils(virtual_sequence)
my_config cfg;
virtual task body();
// null context with empty path
if (!uvm_config_db#(my_config)::get(null, "", "cfg", cfg)) begin
`uvm_fatal("CFG", "Failed to get config")
end
endtask
endclass
Setting Config for This Approach
// Set with null context and empty path:
uvm_config_db#(my_config)::set(null, "", "cfg", my_cfg);
Solution 3: Pass Config Through Constructor/Property
Avoid config_db entirely by passing the config directly:
class virtual_sequence extends uvm_sequence;
`uvm_object_utils(virtual_sequence)
// Config passed directly - no config_db needed
my_config cfg;
function new(string name = "virtual_sequence");
super.new(name);
endfunction
virtual task body();
if (cfg == null) begin
`uvm_fatal("CFG", "Config not set before starting sequence")
end
`uvm_info("VSEQ", $sformatf("Using config: %s", cfg.convert2string()), UVM_MEDIUM)
endtask
endclass
// In test:
class my_test extends base_test;
virtual_sequence vseq;
virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
vseq = virtual_sequence::type_id::create("vseq");
vseq.cfg = env.cfg; // Direct assignment
vseq.start(null);
phase.drop_objection(this);
endtask
endclass
Solution 4: Access Through p_sequencer
If starting on an actual sequencer, use the p_sequencer macro:
class my_sequence extends uvm_sequence #(my_seq_item);
`uvm_object_utils(my_sequence)
`uvm_declare_p_sequencer(my_sequencer)
virtual task body();
// Access config through typed sequencer
my_config cfg = p_sequencer.cfg;
// Or through sequencer's config_db:
if (!uvm_config_db#(my_config)::get(p_sequencer, "", "cfg", cfg)) begin
`uvm_fatal("CFG", "Failed to get config")
end
endtask
endclass
Comparison of Approaches
| Approach | Pros | Cons | Best For |
|---|---|---|---|
uvm_root::get() | Always works, standard pattern | Global scope only | Top-level virtual sequences |
null context | Simple syntax | Can be confusing | Quick prototyping |
| Direct assignment | No config_db overhead, clear | Manual wiring | Tight coupling OK |
p_sequencer | Type-safe access | Requires real sequencer | Regular sequences |
Complete Working Example
// Configuration class
class my_config extends uvm_object;
`uvm_object_utils(my_config)
rand int timeout;
rand bit enable_coverage;
string dut_name;
function new(string name = "my_config");
super.new(name);
timeout = 1000;
enable_coverage = 1;
dut_name = "default";
endfunction
function string convert2string();
return $sformatf("timeout=%0d, cov=%0b, dut=%s",
timeout, enable_coverage, dut_name);
endfunction
endclass
// Virtual sequence using uvm_root
class my_virtual_sequence extends uvm_sequence;
`uvm_object_utils(my_virtual_sequence)
my_config cfg;
function new(string name = "my_virtual_sequence");
super.new(name);
endfunction
virtual task body();
// Get config using uvm_root - works with null sequencer
if (!uvm_config_db#(my_config)::get(uvm_root::get(), "", "my_cfg", cfg)) begin
`uvm_fatal("CFG", "Cannot get my_cfg from config_db")
end
`uvm_info("VSEQ", $sformatf("Config: %s", cfg.convert2string()), UVM_LOW)
// Start sub-sequences on actual sequencers
// ...
endtask
endclass
// Test that sets config and runs virtual sequence
class config_test extends uvm_test;
`uvm_component_utils(config_test)
my_config cfg;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// Create and configure
cfg = my_config::type_id::create("cfg");
cfg.timeout = 5000;
cfg.dut_name = "test_dut";
// Set in config_db with uvm_root context
uvm_config_db#(my_config)::set(uvm_root::get(), "", "my_cfg", cfg);
endfunction
virtual task run_phase(uvm_phase phase);
my_virtual_sequence vseq;
phase.raise_objection(this);
vseq = my_virtual_sequence::type_id::create("vseq");
vseq.start(null); // Started on null - still works!
phase.drop_objection(this);
endtask
endclass
Interview Questions
Q1: How do you get configuration in a sequence running on a null sequencer?
Answer: Use uvm_root::get() as the context in uvm_config_db::get(). This provides a valid component context even when m_sequencer is null. Alternatively, pass the config directly to the sequence before starting it.
Q2: Why does uvm_config_db::get fail with null sequencer?
Answer: uvm_config_db uses the component hierarchy for context matching. When m_sequencer is null, there's no hierarchy path, so the lookup fails. Using uvm_root::get() or null context with matching set() solves this.
Q3: What are virtual sequences and why use null sequencer?
Answer: Virtual sequences coordinate multiple sub-sequences across different sequencers. They don't generate transactions directly, so they don't need a real sequencer. Starting on null is cleaner and indicates the sequence is a coordinator, not a generator.
Key Takeaways
- Null sequencer means no component hierarchy for config_db
- Use
uvm_root::get()as context for global configs - Match set/get contexts and paths carefully
- Direct assignment is simpler when coupling is acceptable
p_sequenceronly works with real sequencers- Virtual sequences typically coordinate, not generate transactions
Comments (0)
Leave a Comment