A code review comment reads: "Use type_id::create here, new() on a component is illegal." The author pushes back: it compiled and ran. Who is right? Half of each. new() on a component is legal, and it is also the reason a future override will silently do nothing. A senior engineer should be able to say exactly what create does that new does not.
Picture a building where the factory is the receptionist. type_id::create walks in through the front door: the receptionist checks the override notes and may send you a different worker. new comes in through the loading dock: perfectly allowed, but nobody checks any notes. The worker you get is always exactly the one you named.
So the rule is not about legality. A component's constructor is public (only uvm_root hides its constructor), and new("drv", this) builds a working driver. The rule is about substitutability: anything a test might ever want to replace must enter through create.
Components are always created with a name and a parent, normally in the parent's build_phase. The parent argument places the component in the hierarchy, and the parent's full name plus the child's name form the instance path that instance overrides and config_db match against.
// snippet: inside axis_agent::build_phase
drv = axis_driver::type_id::create("drv", this); // factory: overrides apply
// drv = new("drv", this); // legal, but no override can ever applyObjects have no parent in the component tree, so their create takes a name and two optional arguments: a parent component (rarely used) and a context string. The context gives the object an instance path, which is the only way an instance override can target it. The bench's tests create their job configs and sequences with a name only:
// snippet: object creation as the bench does it, and with a context
dma_job_cfg job = dma_job_cfg::type_id::create("job"); // path "job"
axis_item it = axis_item::type_id::create("it", null, get_full_name()); // path "<seq path>.it"With no context, the object's path is just its name, so only type overrides reach it in practice. Path-scoped overrides for items and sequences are covered in Overriding items and sequences.
type_id::create is a thin wrapper. Underneath are the factory's create methods, which you use when the type is only known at run time, for example from a string or a uvm_object_wrapper held in a config object. The component version takes four arguments and returns a uvm_component, so you cast the result:
// snippet: creating by type through the factory directly
uvm_factory f = uvm_factory::get();
uvm_component c;
c = f.create_component_by_type(axis_driver::get_type(), // requested type
get_full_name(), // parent instance path
"drv", // name
this); // parent
if (!$cast(drv, c))
`uvm_fatal("CAST", "factory did not return an axis_driver")Method | Returns | When to use it |
|---|---|---|
|
| Every normal component creation. |
|
| Objects; pass a context when an instance override must reach them. |
|
| Type held in a variable ( |
|
| The same for objects. |
|
| Type held as a string, as |
run_test itself uses the by-name form. It takes the test name from +UVM_TESTNAME (or its own argument) and calls create_component_by_name(test_name, "", "uvm_test_top", null), which is why the test instance is always called uvm_test_top, and why absolute config paths in the top module can start with that name.
Components are created during build, normally in the parent's build_phase. Creating a component after the build phase has ended is a fatal error (ILLCRT: it is illegal to create a component after the build phase has ended), because the phases that connect and configure it have already passed. Objects have no such limit: sequences, items and config objects are created whenever they are needed, including thousands of times during run_phase.
Two related rules. clone() works on objects but not on components, which cannot be copied into a second place in the tree. And the object returned by any create is brand new; nothing is shared with earlier creates unless you share it yourself, which is why the bench's monitors create a fresh item for every transfer.
In dma_truncate_test, the agent's ordinary create call returned the derived driver, and the log line came from the derived class at the agent's path:
UVM_INFO dma_tb.sv(520) @ 275000: uvm_test_top.env.in_agt.drv [TRUNC] TLAST forced on beat 3The instance name drv and the path uvm_test_top.env.in_agt.drv are exactly what the agent asked for. Only the class changed. That is the contract: the factory may change what is built, never where or under which name.
A DV team built a small library of utility sequences. One helper, written quickly, created its items with item = new("item");. Months later a test overrode axis_item with an error-injecting subclass to corrupt one beat in a thousand. The override was registered, other sequences picked it up, and coverage showed error beats arriving. But the failing DMA bug only reproduced with the helper's traffic, and that traffic never contained an error beat. It took a week to see that the helper's items were built through the loading dock.
The fix was a grep and a rule: no new for anything registered with the factory, except inside the class's own clone/create plumbing. Many teams add a lint check for = new( on registered types. It is cheap and it catches the bug before anyone loses a week.
axis_item::type_id::create("it") and no context. A test registers an instance override for axis_item at uvm_test_top.env.in_agt.sqr.*. Does it apply?No. Without a context the item's instance path is just it, which does not match uvm_test_top.env.in_agt.sqr.*. Only a type override (or an instance override whose pattern matches it) reaches it. Pass get_full_name() as the context so the item's path becomes <sequencer path>.<sequence name>.it.
uvm_object_wrapper from config_db that says which scoreboard class to build. Which create call do you use, and what must you do with its result?uvm_factory::get().create_component_by_type(wrapper, get_full_name(), "sb", this). It returns a uvm_component, so $cast it into the dma_scoreboard handle and fail loudly if the cast fails. Overrides still apply, because the call goes through the factory.
new() is fine for a component that "nobody will ever override". What is the practical argument?"Nobody will ever override" is a guess about future tests written by other people. The cost of create is one longer line; the cost of new is an override that silently does nothing, found much later. The rule is cheap insurance.
Next: create asks the factory which class to build. Override resolution answers that question when several overrides could apply.