A chip has an always-on fabric and a power island that boots later, under its own reset. The island's env is reused from a block bench where it ran reset, configure and main on its own schedule. In the SoC bench, fabric traffic starts reaching the island while the island's agents are still in reset_phase, and the scoreboard fills with errors that are not DUT bugs.
The tool UVM offers for this is the phase domain. This lesson shows what a domain owns, what it does not own (the part most people get wrong), and how to make two domains meet at the right moments.
Picture two orchestras playing in the same hall, each with its own conductor. Each moves through its own movements at its own pace. Where the score demands it, both conductors wait for each other: a cue where neither starts the next movement until both have finished the current one. Outside the cues, each plays at its own tempo.
A phase domain is one conductor: it owns one copy of the runtime schedule, the twelve phases from pre_reset to post_shutdown. Components assigned to a domain step through that domain's copy. A sync between two domains is a cue: the named phase starts and ends together in both.
What a domain does not own matters as much. run_phase belongs to the common domain, together with build, connect, end_of_elaboration, start_of_simulation, extract, check, report and final. The twelve runtime phases belong to the uvm domain by default, or to a user domain, and run in parallel with run_phase. So a domain never changes when build or run happens, and a jump inside one domain never restarts run_phase.
Assign the island's env to its own domain in build_phase, right after creating it, on the root of the subtree. Two library rules make that enough: every component copies its parent's domain when it is constructed, so the island's children (built later) inherit it, and set_domain with its default hier=1 also updates any children that already exist. Calling it in connect_phase works too, as Custom Phases does. Install the barriers in connect_phase, before run time starts. Every other component stays in the default uvm domain, which you get with uvm_domain::get_uvm_domain().
// snippet
class soc_env extends uvm_env;
`uvm_component_utils(soc_env)
fabric_env fabric;
island_env island;
uvm_domain island_dom;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
fabric = fabric_env::type_id::create("fabric", this);
island = island_env::type_id::create("island", this);
island_dom = new("island_dom"); // domain names must be unique
island.set_domain(island_dom); // the whole island subtree
endfunction
function void connect_phase(uvm_phase phase);
uvm_domain uvm_dom = uvm_domain::get_uvm_domain();
super.connect_phase(phase);
uvm_dom.sync(island_dom, uvm_reset_phase::get()); // leave reset together
uvm_dom.sync(island_dom, uvm_main_phase::get()); // start traffic together
endfunction
endclasssync(target, phase) makes that phase start and end together in both domains; with no phase argument it syncs all twelve. unsync(target, phase) removes a barrier, for example when a test wants the island to reset on its own mid-run. uvm_domain::get_domains(map) returns every domain by name if you need to find one created elsewhere, such as inside vendor VIP.
Without the barriers, each domain leaves reset when its own reset objections drop. The fabric may reach main_phase and start traffic while the island is still holding reset_phase open. With them, the trace looks like this (schematic):
time uvm domain (fabric) island_dom
0 ns reset_phase starts reset_phase starts
200 ns reset objections dropped (still in reset) <- fabric waits at the barrier
900 ns reset_phase ends reset_phase ends <- together
... configure at its own pace configure at its own pace
1.4 us main_phase starts main_phase starts <- togetherBetween barriers the domains drift freely; that is the point. If one side never arrives, the other waits forever at the barrier, which looks exactly like an objection hang in the domain that is waiting. The fix is in the lagging domain.
Situation | Second domain? | Why |
|---|---|---|
Power island with its own reset and late bring-up | Yes | Its lifecycle really is independent; sync at reset and main. |
Vendor VIP that ships in its own domain | Required | Sync with the domain the VIP documents. |
Two agents that should start at different times | No | Objections and sequences on one schedule handle staggered starts. |
Components that only use run_phase | No effect | run_phase is in the common domain; set_domain changes nothing for them. |
Most block and subsystem benches never need one. Every barrier couples two schedules, and every domain is one more thing a reader has to keep in mind. And because most production components do their work in run_phase rather than in runtime phases, a domain often changes less than people expect.
A phase.jump() rewinds only the domain whose phase it was called on. If the island jumps from main back to reset to model a local power cycle, the fabric carries on in main, which is usually exactly what the test wants. Barriers make this harder to reason about: they were set up for both domains passing through the schedule once, not for one domain re-entering phases the other finished long ago. Before a domain-local jump, remove the barriers it would cross with unsync, and restore them afterwards if the test needs them again. The jump itself is covered in Reset phase and phase jump.
An engineer put the island env in its own domain, synced it with the fabric at main, and the early-traffic errors did not move at all. The reason: the island's drivers and monitors, like the bench's, ran entirely in run_phase, which is in the common domain. Only a configuration sequence in the island's configure_phase was affected by the domain. The fabric's traffic came from a virtual sequence in the test's run_phase, which no barrier touches either.
The real fix was a readiness handshake: the fabric's virtual sequence waited on an event that the island's env triggered when its own bring-up sequence completed. The domain was removed. The lesson: before reaching for a domain, list which phases your components actually use. Domains only reorganise the twelve runtime phases.
A sync barrier between the two domains at reset (and usually at main), or an explicit readiness wait. Without one, each domain leaves reset on its own objections, and the fabric's main_phase can start before the island's reset_phase ends.
No. A delay or an event in the sequences on one schedule does it. Separate domains are for independent lifecycles, and each barrier you would then need adds coupling and debugging cost.
uvm_dom.sync(island_dom) with no phase argument, the fabric hangs in post_reset_phase. The fabric's objections are all dropped. Where do you look?In the island domain. Syncing all phases means the fabric cannot leave post_reset until the island does too. Something in the island is still holding post_reset (or an earlier phase) open; its objection dump names the holder.
Next: a domain is one way to restart part of a schedule. Reset phase and phase jump covers the other, and the industry's usual alternative.