An environment owns one agent per interface plus the scoreboard. Each agent owns its driver and monitor, and every component stores a name and a parent handle.
full_name() walks the parent chain. The dotted path it returns labels every log line with its source component.
Construction runs top-down, with parents building children. Connection runs after everything exists, so sibling wiring always finds its endpoints.
Running starts last, once every connection is in place. The split exists because a constructor cannot see siblings that are built later.
Hand-built convention | UVM equivalent |
|---|---|
Component base with |
|
Name plus parent handle |
|
Build wave, top-down |
|
Connect wave for sibling wiring |
|
Run wave with daemons |
|
Manual waves from the top |
|
UVM keeps the same waves and adds a scheduler. Objections replace the crude end-of-test delay, and TLM ports replace raw handle wiring.
Every connect() checks its built flag first. Unbuilt components set a sticky order flag, which the testbench reads.
Construction order is top-down, so a constructor cannot see siblings built later. By connect time every component exists, so sibling wiring always finds its endpoints.
env0.agt0.drv come from?Each component stores its name and parent handle. full_name() walks the parent chain, joining names with dots.
A minimal tree that proves wave order with flags and resolves every hierarchical name.
class component;
string name;
component parent;
bit built;
bit connected;
bit ran;
bit order_bad;
function new(string cname, component p);
name = cname;
parent = p;
endfunction
function string full_name();
if (parent == null) return name;
return {parent.full_name(), ".", name};
endfunction
virtual function void build();
built = 1;
endfunction
virtual function void connect();
if (built == 0) order_bad = 1;
connected = 1;
endfunction
virtual task run();
if (connected == 0) order_bad = 1;
ran = 1;
endtask
endclass
class leaf extends component;
function new(string lname, component p);
super.new(lname, p);
endfunction
endclass
class agent extends component;
leaf drv;
leaf mon;
function new(string aname, component p);
super.new(aname, p);
drv = new("drv", this);
mon = new("mon", this);
endfunction
virtual function void build();
super.build();
drv.build();
mon.build();
endfunction
virtual function void connect();
super.connect();
drv.connect();
mon.connect();
endfunction
virtual task run();
super.run();
drv.run();
mon.run();
endtask
endclass
class env extends component;
agent agt;
leaf sb;
function new(string ename);
super.new(ename, null);
agt = new("agt0", this);
sb = new("sb", this);
endfunction
virtual function void build();
super.build();
agt.build();
sb.build();
endfunction
virtual function void connect();
super.connect();
agt.connect();
sb.connect();
endfunction
virtual task run();
super.run();
agt.run();
sb.run();
endtask
endclass
PASS: build then connect then run, every name resolvesRun with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown, with no warnings. These class benches were run on Verilator only.