Factory creation and type substitution
Ask the factory for objects and components, replace a class everywhere or at one path, and find out why an override did nothing.
- Lesson
- U2.2
- Time
- 25 min
- Runs on
- Verilator with UVM
- Check
- 5 questions
The incrementer bench now has two ports, each with its own agent. An error test should corrupt traffic on port 1 only, so it registers a type override from incr_driver to err_driver. That night the port-0 scoreboard reports hundreds of mismatches, because a type override replaces the class at every path, not at one.
The build creates five components in this order. A type override changes every incr_driver; an instance override for env.port1.drv changes one.
Registering a class with the factory
The UVM factory is a single object that builds other objects on request. Before it can build a class, the class must be registered. The uvm_component_utils and uvm_object_utils macros do that, using the class name as a string key.
The macro also adds a nested type called type_id, a small proxy for that class. incr_driver::type_id::create(...) asks the factory for an incr_driver. incr_driver::get_type() returns the proxy itself, which is how you name a class in an override call. get_type_name() returns the name of the class an object really is, which is the quickest way to check what the factory built.
The string key is also how a test is started. run_test("err_test") asks the factory to create a component from the name err_test. A test class without the macro cannot be found that way, and the run stops at time 0 with an INVTST fatal error saying the requested test was not found.
What create does
A create call passes a name and, for a component, its parent. From those the factory works out the full path of the new object. Then it decides which class to build.
Work out the path
The parent's full name plus the new name, such as
uvm_test_top.env.port1.drv.Check instance overrides
Each instance override names a requested type and a path pattern. The first one that matches both is used.
Check type overrides
With no instance match, the type override for the requested type is used, if there is one.
Follow chains
The replacement class may itself be overridden, so the factory repeats the search for it.
Build and cast
The factory builds the final class and casts it back to the requested type. If that cast fails, the run stops with a fatal error.
The last step is why a replacement must extend the class it replaces. The caller holds an incr_driver handle, so whatever arrives must be an incr_driver or something derived from it. Overriding incr_driver with an unrelated class compiles fine, but the first create call stops the run with an FCTTYP fatal error.
Type overrides change every path
incr_driver::type_id::set_type_override(err_driver::get_type()); tells the factory to build an err_driver whenever anyone asks for an incr_driver. Every path is affected, which suits a test that wants a different driver throughout.
A second type override for the same requested type replaces the first. That is the default, and you rarely want anything else. Type overrides also work for objects: override incr_item with a derived wrap_item, and every item a sequence creates through the factory becomes a wrap_item.
Instance overrides change one path
An instance override adds a path pattern. Called from the test, incr_driver::type_id::set_inst_override(err_driver::get_type(), "env.port1.drv", this); affects only the driver at uvm_test_top.env.port1.drv. The third argument makes the path relative: the factory puts the test's own full name in front of it.
Leave out the third argument and the path is taken as complete, from the root. "env.port1.drv" then matches nothing, because every real path starts with uvm_test_top. The call succeeds, nothing warns you, and the override never applies. Patterns may also use * and ? as wildcards, so "*.port1.*" matches everything below port 1.
When an instance override and a type override both match, the instance override wins. The order in which the test registered them makes no difference. So a test can use a type override for the common case and an instance override for the one path that needs something different.
Overrides apply at create time
The factory checks overrides when an object is created, never afterwards. An object that already exists keeps its class. So an override must be registered before the create call it is meant to change.
For components, that leaves more room than it seems. The test's build_phase runs to the end before the environment's build_phase starts. An override registered anywhere in the test's build_phase is therefore in place before the environment creates its agents and drivers, even if the line comes after env = incr_env::type_id::create("env", this);.
The environment object itself is different. It was created on that line, so an override for incr_env registered below it is too late. The habit that avoids all of this is simple: register overrides at the top of the test's build_phase, before super.build_phase(phase) and before any create call. Objects follow the same rule at their own create call: in [U1.4](U1-4-uvm-test-family.html), an item override set during the run still works because the sequence creates its item later, in body().
When an override does nothing
An override that silently fails is one of the most common UVM debugging sessions. Work through the causes in this order.
| Cause | How to see it |
|---|---|
The class is built with new, which never asks the factory | get_type_name() on the object shows the original class |
| The override was registered after the create call | Move the override to the top of the test's build_phase |
| The instance path does not match | Compare the override path with the object's get_full_name() |
| The object was created without a context | Its full path is only its name; pass a context to create |
Why a factory override has no effect.
Printing the factory shows what it holds. Call uvm_factory::get().print(0); at the end of elaboration, and it lists every instance override with its full path, then every type override. An instance path with a typo, or one missing uvm_test_top, is easy to spot in that table.
Try it
err_test extends the normal test and only adds two overrides before calling the base class's build_phase. The instance override gives port 1 the error driver and leaves port 0 alone. The type override turns every factory-created incr_item into a wrap_item, but the item made with new stays an incr_item. The factory table at the end lists both overrides.
Change set_inst_override to incr_driver::type_id::set_type_override(err_driver::get_type()); and run again: both ports now get err_driver, as in the opening story. Then remove this from the original instance override call and watch port 1 lose its error driver.
uvm_test_top.env.port0.drv: incr_driver uvm_test_top.env.port1.drv: err_driver create: wrap_item, x=255 new: incr_item, x=0 Instance Overrides: Type Overrides: UVM_ERROR : 0Check yourself
Remember
- The utils macros register a class with the factory;
type_id::createasks the factory for it, andnewbypasses it. - A type override replaces a class at every path; an instance override replaces it only where the path pattern matches.
- A matching instance override beats a type override, whatever order they were registered in.
- Overrides apply at create time: register them at the top of the test's
build_phase, before anything is created. - An instance path without a parent argument is a full path from the root, starting with
uvm_test_top; print the factory to check it.