Part 3 · Factory & Configuration · Intermediate

Factory vs Direct new: The Non-Negotiable Rule

Why new() bypasses override resolution, where direct construction still appears, and migration patterns for legacy code.

Why new() breaks reuse

Overrides apply only when the factory mediates construction. Direct new() hard-wires the concrete type and silently ignores all override policy.

diagram
[FACTORY][UVM] create vs new

test policy:
  base_driver -> err_driver (type override)

env path A (factory):
  drv = base_driver::type_id::create("drv", this)
  result: err_driver built

env path B (direct new):
  drv = new("drv", this); // if legal in custom code
  result: base_driver built, override ignored
systemverilog
// GOOD
drv = base_driver::type_id::create("drv", this);

// BAD — never for replaceable components
// drv = new base_driver("drv", this);  // illegal for uvm_component
// Some teams wrap illegally:
// drv = base_driver::type_id::create("drv", this); // then replace handle manually

Key takeaways

  • If a type should be replaceable, it must be factory-created.

  • new() is for non-UVM helper classes outside override policy.

  • A single new() in env can waste hours debugging 'ignored' overrides.

Common pitfalls

  • Refactoring macros but leaving old new() calls in agents.

  • Constructing sequences with new() when override policy is needed.

  • Library code that returns new() objects while env expects factory path.


Allowed and discouraged construction cases

Not every object participates in override policy. Separate replaceable UVM types from plain SV helpers.

Construction matrix

diagram
[FACTORY] construction policy

factory create required:
  uvm_component children in env/agents
  override-able sequences/items/cfg objects

direct new acceptable:
  plain structs, interfaces wrappers, non-UVM utilities
  static helpers with no factory registration

gray zone:
  uvm_object created once in package init — document as fixed type
  • Code review grep for `= new(` under uvm_component build paths.

  • Lint/custom script: flag new() on classes with utils macros.

  • VIP docs must state which types are override targets.

Migration snippet

systemverilog
// legacy
function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  mon = new("mon", this);
endfunction

// factory-correct
function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  mon = base_monitor::type_id::create("mon", this);
endfunction
diagram
[UVM] migration validation

1) replace new with type_id::create
2) add/verify utils macro
3) run test with intentional override
4) confirm get_type_name() shows derivative

Common pitfalls

  • Partial migration — one agent still uses new().

  • Thinking compile-time type check equals factory compliance.

  • Using create() only at top level while sub-hierarchy uses new().