A new DMA bug: it mishandles a packet whose words have a particular bit pattern, but only on the input stream, never on the register bus. You want error-flavoured items on in_agt and nowhere else, for one test, without touching the packet sequence. A type override on axis_item would also change every item the output monitor creates. You need an instance override for objects, and objects have no place in the component tree. This lesson shows how to give them one.
Components have a permanent address, their full hierarchical name. Sequences and items are created on the fly and belong to nothing in the tree. When you create one, the factory forms its instance path from the context string you pass, plus the name. No context, and the path is just the name: "it", "pkt". An instance override can only target an address that exists, so path-scoped overrides on stimulus depend on every create passing a meaningful context.
For sequences the natural context is get_full_name(). Once a sequence is started, its full name is its parent sequence's full name plus its own name, or, for a top-level sequence, the sequencer's full name plus its name. Inside axis_pkt_seq started on in_agt.sqr, that is uvm_test_top.env.in_agt.sqr.pkt.
The bench's packet sequence creates items with a name only, which is fine for type overrides but leaves no address for instance overrides:
// 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
endclassThe one-word change that makes path-scoped overrides possible is the third argument:
// snippet: inside axis_pkt_seq::body, creating each beat with a context
axis_item it = axis_item::type_id::create("it", null, get_full_name());
// path: uvm_test_top.env.in_agt.sqr.pkt.it when pkt runs on in_agt.sqrThe test targets everything created under the input sequencer. The pattern covers the sequence and every item it creates; the output side and the APB side are unaffected.
// snippet: in a test's build_phase
axis_item::type_id::set_inst_override(axis_err_item::get_type(),
"env.in_agt.sqr.*", this);
// this prefixes the test's full name: "uvm_test_top.env.in_agt.sqr.*"axis_err_item must extend axis_item and carry its own ` uvm_object_utils `` (without it, the inherited type_id trap silently turns the override into a no-op).
The same idea replaces a whole sequence. A virtual sequence that coordinates the DMA (program registers, then stream data) creates its sub-sequences through the factory, with its own full name as context:
// snippet: a virtual sequence's body; the test can swap the packet sequence
task body();
axis_pkt_seq pkt = axis_pkt_seq::type_id::create("pkt", null, get_full_name());
if (!pkt.randomize() with { n == 8; })
`uvm_fatal("RAND", "axis_pkt_seq randomize failed")
pkt.start(in_sqr, this); // parent = this vseq
endtaskBecause the virtual sequence passes itself as parent, pkt's full name is the virtual sequence's full name plus .pkt, not the input sequencer's path. If the test started the virtual sequence with vseq.start(null) from a test task, its full name is just vseq, so the sub-sequence is vseq.pkt. The override pattern must follow that chain:
// snippet: replace only the packet sequence inside the vseq
axis_pkt_seq::type_id::set_inst_override(axis_burst_seq::get_type(), "vseq.pkt");The bench as shipped uses type overrides only, so its evidence is the familiar truncation line. With the context change and the instance override in place, two things confirm the override: items from in_agt report get_type_name() as axis_err_item (log it from the driver at UVM_HIGH), and debug_create_by_type for the item's path shows the match:
// snippet: confirm which item class a path resolves to
uvm_factory::get().debug_create_by_type(axis_item::get_type(),
"uvm_test_top.env.in_agt.sqr.pkt", "it");The scoreboard's numbers are also a check: an error item that changes only data bits leaves the word count unchanged, so the line 8 words matched, 0 mismatched from a smoke-style run should still appear, because the scoreboard predicts from the observed input words.
Goal | Override | Requirement |
|---|---|---|
Every item of a type, everywhere (including monitors' items) | Type override | None |
Items from one agent's sequences only | Instance override at | Items created with |
One sequence inside a virtual sequence | Instance override at the parent chain, such as | Sub-sequence created with the parent's full name as context |
A different sequence class for one test | Type override on the sequence class | The replacement extends the original and keeps its knobs |
The last row is often the cleanest: a test that wants bursty packets type-overrides axis_pkt_seq with a subclass whose constraints differ. Every place that creates the packet sequence through the factory picks it up, and the replacement keeps the n field that callers randomize.
A team wanted error items on one of two identical stream agents. They wrote set_inst_override_by_type(axis_item::get_type(), axis_err_item::get_type(), "uvm_test_top.env.in_agt.*"), reran, and saw no error items. The pattern was fine: * matches dots, so it would have covered uvm_test_top.env.in_agt.sqr.pkt.it. But the sequence created its items with no context, so the path the factory actually saw for each item was just it, which matches nothing under in_agt. One call to debug_create_by_type with the item's real context showed the gap between the address the team imagined and the one the code produced.
Two habits prevent this: always pass get_full_name() as the context when creating items and sub-sequences, and write object override patterns from the sequencer path down (...in_agt.sqr.*). When the target is "every item of this type everywhere", use a type override and skip paths entirely.
uvm_test_top.env.in_agt.sqr with parent null is named pkt. It creates axis_item::type_id::create("it", null, get_full_name()). What path does the factory resolve?uvm_test_top.env.in_agt.sqr.pkt.it. A started top-level sequence's full name is the sequencer's full name plus its own name; the item's path is that context plus .it.
pkt is started from a virtual sequence with pkt.start(in_sqr, this), and the virtual sequence was started with vseq.start(null). Does an instance override at uvm_test_top.env.in_agt.sqr.* reach its items?No. With a parent, pkt's full name is the parent's full name plus .pkt, here vseq.pkt, so its items live at vseq.pkt.it. The override pattern must match that chain, for example vseq.pkt.*, or *.pkt.it if you accept the broader reach.
Nobody needs to override a monitor's items per path: a monitor reports what it saw, and changing its item class would change what subscribers receive everywhere. A type override still reaches it if ever needed. Contexts matter for stimulus objects, where per-agent variation is common.
Next: the factory decides which class is built. The other half of reusable benches is deciding how each instance is configured: config_db and resources.