Override one instance

Replace the driver inside one agent from the test, write a path pattern that matches that driver and nothing else, and confirm with the factory's own reports before the run starts.

Lesson
U11.3
Time
25 min
Runs on
Verilator with UVM
Check
5 questions

The chip has twelve lanes, lane0 to lane11, each with its own incrementer agent. An error test should corrupt lane 1 only, so it adds an instance override for the pattern "*lane1*". Three lanes start failing: the pattern also matches lane10 and lane11, and nothing warns about it.

Driver pathMatches "lane1"Matches "*.lane1.drv"
uvm_test_top.env.lane0.drvNoNo
uvm_test_top.env.lane1.drvYesYes
uvm_test_top.env.lane10.drvYesNo
uvm_test_top.env.lane11.drvYesNo

A star matches any characters, including digits. Anchoring the name between dots matches only lane 1.

Change one agent without editing it

A reusable agent is shared by many tests and often by other teams, so a test should not edit it to inject errors. The factory lets the test swap a class from outside. U2.2 showed the two kinds of override: a type override replaces a class everywhere, an instance override replaces it at matching paths only.

This lesson uses the instance kind. The test asks for err_driver wherever incr_driver is created at one path. The agent's code still says incr_driver::type_id::create("drv", this). At that call, the factory looks at the requested type and the new component's full path, and builds err_driver instead when an override matches both.

For that to work, the agent must create its driver through the factory. A driver built with new never consults the factory, so no override can reach it. The replacement must also extend incr_driver, so the agent's handle of that type can hold it.

The path names the component you create

An instance override matches the full path of the component being created, not of its parent. The agent uvm_test_top.port_b creates its driver as "drv", so the driver's path is uvm_test_top.port_b.drv. That is the string the pattern must match.

Pattern given to set_inst_overrideCalled withDriver at uvm_test_top.port_b.drv
"port_b.drv"parent this (the test)Replaced: the test's path is put in front
"uvm_test_top.port_b.drv"no parentReplaced
"uvm_test_top.port_b.*"no parentReplaced, with every other incr_driver under port_b
"uvm_test_top.port_b"no parentNot replaced: that is the agent's path
"port_b.drv"no parentNot replaced: no path starts with port_b

Five ways to aim at one driver. Only the requested type, incr_driver, is affected in every row.

The fourth row is a common slip. The author thinks "override in port B" and names the agent. The agent is an incr_agent, not an incr_driver, so nothing is replaced and the call reports nothing. The last row is U2.2's trap again: without a parent, the pattern is read from the root.

Wildcards match more than you think

Patterns use glob wildcards: * matches any run of characters, including dots, and ? matches one character. A star is handy for "every driver under port B", but it does not stop at a name boundary. As the hook showed, "*lane1*" matches lane10 and lane11 as well.

Anchor names with the dots around them. "*.lane1.drv" matches only a drv whose parent is called exactly lane1. "*.lane1.*" matches every descendant of lane1, at any depth, but nothing under lane10. Better still, give the full relative path from the test, such as "env.lane1.drv" with this as the parent.

Instance overrides beat type overrides

What create() asks the factoryThe factory's decision for one create call. Arrows are the order of the checks.
What create() asks the factoryThe factory's decision for one `create` call. Arrows are the order of the checks. request create("drv") incr_driver atport_b.drv check Instanceoverrides In order added; firstmatch wins check Type override Only if no instancematch result Instance result err_driver forport_b result Type result Its override type result Requested type incr_driver forport_a type + full pathpath matchesno matchregisterednone
  1. port_b.drv

    The path uvm_test_top.port_b.drv matches the instance override, so the factory builds err_driver and looks no further.

    More context

    The example prints uvm_test_top.port_b.drv is a err_driver, and debug_create_by_type reports The factory will produce an object of type 'err_driver'.

    Follow the calls, values and waits
    1. call incr_driver @ port_b.drv: create("drv") → Instance overrides
    2. data err_driver: Instance overrides → Instance result
  2. port_a.drv

    No instance override matches uvm_test_top.port_a.drv, and the example has no type override, so the factory builds the requested incr_driver.

    More context

    The example prints uvm_test_top.port_a.drv is a incr_driver. Both agents are the same class with the same create call; only the path differs.

    Follow the calls, values and waits
    1. call incr_driver @ port_a.drv: create("drv") → Instance overrides
    2. call incr_driver @ port_a.drv: Instance overrides → Type override
    3. data incr_driver: Type override → Requested type
Read the diagram as text
  • create("drv"): incr_driver at port_b.drv
  • Instance overrides: In order added; first match wins
  • Type override: Only if no instance match
  • Instance result: err_driver for port_b
  • Type result: Its override type
  • Requested type: incr_driver for port_a
  • create("drv") → Instance overrides: type + full path
  • Instance overrides → Instance result: path matches
  • Instance overrides → Type override: no match
  • Type override → Type result: registered
  • Type override → Requested type: none

The order of the calls that added the overrides does not change this order of checks.

A test may hold both kinds of override for the same requested type. At each create, the factory checks the instance overrides first, in the order they were added, and uses the first one whose path matches. Only when none matches does it use the type override.

The order of the calls in the test does not change this. A type override to slow_drv and an instance override to err_drv for port 1 give err_drv on port 1 and slow_drv everywhere else, whichever call came first.

When the override must exist

The factory decides at the moment of create. An override added after a component was created does not change it. So the question is when the driver is created, and the answer is in the agent's build_phase.

Build runs top-down. The test's build_phase runs to its end before the agent's build_phase starts. So an override added late in the test's build_phase still reaches the driver, even after the test has created the agent object. An override added in connect_phase arrives after every driver exists, and changes nothing.

  1. Test build_phase

    Adds the override, then creates the agents. Their drivers do not exist yet.

  2. Agent build_phase

    Each agent calls create for "drv". The factory matches the path and builds err_driver for port_b.

  3. end_of_elaboration_phase

    Every component exists. Print each driver's type name to confirm the swap before any time passes.

Check the result at the start of every error test. drv.get_type_name() returns the class actually built, and uvm_top.print_topology() lists every component with its type. A test that silently runs the normal driver passes for the wrong reason.

Ask the factory before the run

Two factory reports show the overrides themselves, before any time passes. The example calls both in the test's end_of_elaboration_phase.

Measured: uvm_factory::get().print(0), then debug_create_by_type(incr_driver::get_type(), "uvm_test_top.port_b", "drv"), report headers shortened.
[UVM/FACTORY/PRINT]
Instance Overrides:
  Requested Type  Override Path            Override Type
  --------------  -----------------------  -------------
  incr_driver     uvm_test_top.port_b.drv  err_driver
No type overrides are registered with this factory

[UVM/FACTORY/DUMP]
Given a request for an object of type 'incr_driver' with an instance
path of 'uvm_test_top.port_b.drv' the factory encountered
no relevant overrides.
Result:
  The factory will produce an object of type 'err_driver'

print(0) lists every registered override with its full path, so a pattern that aims at the agent instead of its driver shows up here. debug_create_by_type answers one question: what would create build at this path now? Its Result line names err_driver. Neither report says what was built earlier; that is what get_type_name() on the built driver tells you.

Choosing the smallest override

Pick the narrowest change that does the job. Overriding one driver at one path affects one port. Overriding the agent class would replace its monitor and sequencer too, and a type override would reach every port. Each wider choice is one more thing that can change by accident when the bench grows.

The same rules work for objects, with one extra step. A sequence item created inside a sequence with no context gets a path that is only its own name, such as it, as U2.2 showed. A full-path pattern then never matches it, while a pattern that is just the name matches every such item in the bench. Pass a context to create to give it a real path. Components always have one, which makes instance overrides the natural tool for one-port changes.

Injecting the error

In the example, err_driver extends incr_driver and overrides one virtual function, value_to_drive. The normal driver returns x; the error driver flips bit 7. The test overrides the driver at "port_b.drv" relative to itself, and runs the same two items, 10 and 11, on both ports.

Both ports at each rising edge (10 ns clock)
01234time (ns)515253545port A x01011port B x0138139valid (both)first itemsecond item

Port B carries 10 + 128 = 138 and 11 + 128 = 139. Port A is unchanged.

Only port B changed, and neither the agent nor the sequence was edited. The design still adds 1, so a scoreboard that predicts from the intended items flags both port B results.

Try it

The first two lines confirm the swap before the run starts: port B's driver is an err_driver. The factory reports follow: one instance override at uvm_test_top.port_b.drv, and err_driver as the result for that path. Both monitors then report the same two items, with bit 7 set on port B only.

Change the pattern to "port_b" and run again: both drivers are incr_driver, because the pattern now names the agent. Then move the set_inst_override call to the test's connect_phase and see that it comes too late.

RUNVerilator + UVM 2020.3.1 · simulator service
Edit the code, then run it
Expected among the UVM messages: uvm_test_top.port_a.drv is a incr_driver uvm_test_top.port_b.drv is a err_driver incr_driver uvm_test_top.port_b.drv err_driver no relevant overrides. The factory will produce an object of type 'err_driver' 15 ns port_a saw x=10 y=11 15 ns port_b saw x=138 y=139 35 ns port_a saw x=11 y=12 35 ns port_b saw x=139 y=140 UVM_ERROR : 0

Check yourself

Predict the outputEach port creates its driver as "drv" in its own build_phase. What line does the $display print? UVM's own messages are not part of the answer.
`timescale 1ns/1ns
`include "uvm_macros.svh"
import uvm_pkg::*;
class base_drv extends uvm_component;
  `uvm_component_utils(base_drv)
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
endclass
class slow_drv extends base_drv;
  `uvm_component_utils(slow_drv)
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
endclass
class err_drv extends base_drv;
  `uvm_component_utils(err_drv)
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
endclass
class port extends uvm_component;
  `uvm_component_utils(port)
  base_drv drv;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    drv = base_drv::type_id::create("drv", this);
  endfunction
endclass
class my_test extends uvm_test;
  `uvm_component_utils(my_test)
  port p0, p1, p2;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    p0 = port::type_id::create("p0", this);
    base_drv::type_id::set_type_override(slow_drv::get_type());
    set_inst_override_by_type("p1.*", base_drv::get_type(), err_drv::get_type());
    set_inst_override_by_type("p2", base_drv::get_type(), err_drv::get_type());
    p1 = port::type_id::create("p1", this);
    p2 = port::type_id::create("p2", this);
  endfunction
  function void end_of_elaboration_phase(uvm_phase phase);
    $display("%s %s %s", p0.drv.get_type_name(), p1.drv.get_type_name(), p2.drv.get_type_name());
  endfunction
endclass
module tb;
  initial run_test("my_test");
endmodule
Spot the bugThe environment has lanes 0, 1, 10 and 11. Only lane 1 should get err_driver. Each option is the override call in the test's build_phase. Which one also changes other lanes?
ConceptA test adds set_inst_override for "env.port_b.drv" with this as the parent, and the log shows port B still running incr_driver. Which cause fits?
ConceptWhere should a test add an instance override for the driver inside one agent?
ConceptAn error test passes, and you want proof that port B really ran err_driver. Which check, made in end_of_elaboration_phase, gives that proof?

Remember

  • An instance override replaces a class only where the new component's full path matches the pattern.
  • Aim the pattern at the component being created, such as port_b.drv, not at its parent agent.
  • A * matches any characters, so anchor names with dots: "*lane1*" also matches lane10.
  • Instance overrides win over type overrides, and both must exist before the create call they should change.
  • Confirm the swap in end_of_elaboration_phase with get_type_name() before trusting an error test; factory.print(0) lists the overrides and debug_create_by_type names what create would build at a path.