Part 2 · Phases & Lifecycle · Intermediate

Conditional Construction: Active, Passive, and Feature Gates

Building only what you need — active/passive agents, optional scoreboards, parameterized agent arrays, and connect-safe branching.

Structure follows config

build_phase is where cfg decides topology . The canonical pattern gates driver and sequencer creation on active mode, and omits optional checkers when disabled.

systemverilog
function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  void'(uvm_config_db#(agent_cfg)::get(this, "", "cfg", cfg));
  mon = my_monitor::type_id::create("mon", this);
  if (cfg.is_active == UVM_ACTIVE) begin
    drv = my_driver::type_id::create("drv", this);
    sqr = my_sequencer::type_id::create("sqr", this);
  end
endfunction
diagram
[PHASE][UVM] active vs passive build

UVM_ACTIVE:
  create mon + drv + sqr

UVM_PASSIVE:
  create mon only
  drv = null, sqr = null

connect_phase MUST mirror this branch

Key takeaways

  • Every conditional create in build_phase needs a matching guard in connect_phase.

  • Passive agents still need monitor and vif; they skip driver/sequencer.

  • Feature flags (enable_sb, enable_cov) belong in build_phase branching.

Common pitfalls

  • Connecting drv.seq_item_port when drv was never created — null deref.

  • Changing is_active after build — too late, topology already fixed.

  • Creating optional components in connect_phase instead of build.


Parameterized and multi-instance builds

Large environments build agent arrays and optional subsystems from cfg fields — keep loops deterministic and named consistently.

Agent array pattern

systemverilog
function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  void'(uvm_config_db#(env_cfg)::get(this, "", "cfg", cfg));
  foreach (cfg.agent_en[i]) begin
    if (cfg.agent_en[i]) begin
      string agt_name = $sformatf("agt[%0d]", i);
      agents[i] = my_agent::type_id::create(agt_name, this);
      uvm_config_db#(agent_cfg)::set(this, {agt_name, "*"}, "cfg", cfg.agt_cfg[i]);
    end
  end
endfunction
diagram
[PHASE] multi-instance naming

Good:  agt[0], agt[1], agt[2]
Bad:   agt0, agent_1, tx_agent (inconsistent override paths)

Factory inst_override paths depend on exact instance names

Optional subsystem gates

  • if (cfg.enable_scoreboard) sb = scoreboard::type_id::create(...)

  • if (cfg.enable_coverage) cov = coverage::type_id::create(...)

  • connect_phase checks null before wiring optional analysis paths.

systemverilog
function void connect_phase(uvm_phase phase);
  super.connect_phase(phase);
  agt.mon.ap.connect(sb.act_imp);
  if (cfg.enable_coverage)
    agt.mon.ap.connect(cov.analysis_export);
endfunction
diagram
[PHASE][UVM] conditional build/connect parity

build:   if (enable_X) create X
connect: if (enable_X) wire X
debug:   print_topology shows only created instances

Common pitfalls

  • Foreach loop index mismatch between cfg arrays and created agents.

  • Hardcoded agent count ignoring cfg.agent_en bitmap.

  • Optional checker disabled in cfg but still referenced in connect.