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?
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.
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:
// 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
endclassWhen 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:
// 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
endclassThe test (or the command line) sets the knob below the sequencer, so it reaches any sequence running there, whatever its name:
// snippet: in a test's build_phase
uvm_config_db#(int unsigned)::set(this, "env.in_agt.sqr.*", "max_idle", 0); // back-to-backTwo 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 ``:
// 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
endclassp_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.
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.
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:
// 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.
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):
+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 mismatchedWith 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.
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.
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.
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().
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.