UVM
Multi-Agent Systems
Coordinating multiple agents — the virtual sequencer that holds handles to the agents' sequencers, virtual sequences that orchestrate synchronized cross-interface stimulus, and why order-dependent multi-interface scenarios need a conductor instead of independent per-agent sequences.
Environments · Module 15 · Page 15.2
The Engineering Problem
The environment holds multiple agents — one per interface (Module 15.1). Most stimulus is per-interface: a test runs a sequence on one agent's sequencer, and that agent drives its interface. But many real scenarios need coordinated stimulus across interfaces: a configuration write on a control bus that must complete before data traffic starts on a data bus; a packet injected on an input port that must be correlated with credits on a flow-control interface; two interfaces that must be driven in a specific relative order or timing. You cannot achieve this by running independent sequences on each agent's sequencer — those sequences run concurrently, with no shared timing, ordering, or state, so an order-dependent scenario races. The problem this chapter solves is cross-agent coordination: how do you orchestrate multiple agents' stimulus in concert — in the right order, synchronized, correlated — from a single point of control?
The answer is the virtual sequencer and virtual sequence. A virtual sequencer is a sequencer that drives no interface itself — instead it holds handles to the agents' real sequencers, making it the coordination hub. A virtual sequence runs on the virtual sequencer and orchestrates across agents: it starts sub-sequences on any of the agents' sequencers, controlling their relative order, timing, and synchronization. The environment wires the virtual sequencer's handles to the agents' sequencers (in connect_phase), and a test runs a virtual sequence on the virtual sequencer — one entry point for coordinated, multi-interface stimulus. The virtual sequence is the conductor; the agents' sequencers are the players. This chapter is multi-agent coordination: the virtual sequencer as the coordination hub, the virtual sequence that orchestrates, why coordination needs shared control, and the race that results from running independent sequences instead.
How do you coordinate multiple agents in an environment — orchestrating synchronized, order-dependent stimulus across interfaces — through a virtual sequencer that holds handles to the agents' sequencers and a virtual sequence that conducts sub-sequences on them?
Motivation — why independent sequences can't coordinate
Per-agent sequences are fine for per-interface stimulus — but coordination across interfaces needs shared control they can't provide. The reasons:
- Cross-interface scenarios are common and important. Configure-then-operate, request-on-one-interface/response-on-another, correlated multi-port traffic — real DUT behavior spans multiple interfaces, and verifying it needs coordinated stimulus across them.
- Coordination requires ordering. "Write the config before sending data" is an ordering constraint across two agents. Independent sequences have no shared timeline — they start together and run concurrently, so the ordering is not enforced; the data can arrive before the config.
- Coordination requires synchronization. "Wait for the response on interface B before driving the next request on interface A" is a synchronization across agents — one sequence's progress depends on another's. Independent sequences can't wait on each other; they have no shared state.
- Coordination requires correlation. "The address on the control bus must match the port chosen for data" correlates fields across interfaces. Independent sequences randomize independently — they can't share a chosen value.
- A single point of control is needed. Coordination needs one place that knows about all the agents and orchestrates them. The virtual sequencer (holding all the agents' sequencer handles) is that point; a virtual sequence running on it is the orchestrator.
The motivation, in one line: cross-interface scenarios need ordering, synchronization, and correlation across agents — shared control that independent per-agent sequences cannot provide (they run concurrently with no shared timeline or state) — so coordination requires a virtual sequencer (the hub holding all sequencer handles) and a virtual sequence (the orchestrator) that conducts the agents' stimulus in concert.
Mental Model
Hold multi-agent coordination as a conductor leading an orchestra:
The agents' sequencers are the orchestra's sections — each can play on its own — and the virtual sequence is the conductor who cues each section at the right moment so they perform one coordinated symphony. The conductor plays no instrument; they coordinate the players. Picture an orchestra. Each section — strings, brass, percussion (the agents' sequencers, each driving one interface) — can play on its own: hand the strings a part and they'll play it (run a sequence on one agent's sequencer, and it drives its interface). But a symphony — a coordinated piece where sections come in in a specific order, wait for each other, and play together (a cross-interface scenario: config then data, request then response, correlated traffic) — needs a conductor. The conductor (the virtual sequence, running on the virtual sequencer) cues each section at the right moment: brings in the strings (starts a sequence on agent A), waits for them to reach a point, then cues the brass (starts a sequence on agent B), keeps them synchronized. Without a conductor, the sections play independently — they'd all start at once and drift, no coordination, cacophony instead of a symphony. Crucially, the conductor plays no instrument — they don't produce sound themselves (the virtual sequencer drives no interface); their entire job is coordinating the players. And the conductor needs to know the sections — to cue them, they must have a baton pointed at each (the virtual sequencer holds handles to the agents' sequencers; the environment connects those handles). So the performance (a coordinated scenario) is the conductor cuing the sections in concert — not each section playing whenever it likes.
So multi-agent coordination is a conductor leading the sections: the virtual sequence (conductor) cues the agents' sequencers (sections) in a controlled order, timing, and synchronization to perform a coordinated cross-interface scenario (symphony). The conductor plays no instrument (the virtual sequencer drives nothing) and must know the sections (holds handles to the real sequencers, wired by the env). Independent sequences are sections playing whenever they like — fine for a solo (single-interface stimulus), useless for a symphony (a coordinated scenario). To coordinate, conduct.
Visual Explanation — the virtual sequencer as coordination hub
The defining picture is the hub: a virtual sequencer holding handles to the agents' real sequencers, with a virtual sequence running on it starting sub-sequences on each.
The figure shows the virtual sequencer as the coordination hub. A test runs a virtual sequence (the conductor) on the virtual sequencer — the hub that holds handles to the agents' real sequencers and drives no interface itself. The virtual sequence starts sub-sequences on agent A's sequencer and agent B's sequencer (the real sequencers), and those drive their respective interfaces (A's control bus, B's data bus). The crucial reading is the structure of control: the warning-colored virtual sequencer is a pure coordination point — it has no driver, drives nothing, and exists only to hold the agents' sequencer handles so a virtual sequence can reach them. The brand-colored virtual sequence is the orchestrator — running on the hub, it starts and controls sub-sequences on the success-colored real sequencers, governing their order and timing. The environment wires the virtual sequencer's handles to the real sequencers (in connect_phase) — that's what lets the virtual sequence reach them. The test's role is minimal: it runs one virtual sequence on the virtual sequencer, and that orchestrates both interfaces. This is the single point of control for coordinated stimulus: instead of the test starting two independent sequences on two agents (no coordination), it runs one virtual sequence that conducts both (coordination). The diagram is the anatomy of multi-agent coordination: a hub (virtual sequencer) that knows all the players, an orchestrator (virtual sequence) that cues them in concert, and real sequencers that play — one coordinated system driven from one place.
RTL / Simulation Perspective — the virtual sequence orchestrating
In code, the virtual sequencer holds handles to the agents' sequencers (wired by the env), and the virtual sequence starts sub-sequences on them in a controlled order. The code shows the orchestration.
// === VIRTUAL SEQUENCER: holds handles to the real sequencers, drives NOTHING ===
class my_vseqr extends uvm_sequencer; // note: no transaction type — it drives no interface
ctrl_sequencer ctrl_seqr; // handle to agent A's (control) sequencer
data_sequencer data_seqr; // handle to agent B's (data) sequencer
endclass
// === ENV connects the virtual sequencer's handles to the real sequencers (connect_phase) ===
function void my_env::connect_phase(uvm_phase phase);
vseqr.ctrl_seqr = ctrl_agt.seqr; // wire the handles — so the vseq can reach them
vseqr.data_seqr = data_agt.seqr;
endfunction
// === VIRTUAL SEQUENCE: orchestrate — config FIRST, then data (ordering enforced) ===
class config_then_data_vseq extends uvm_sequence;
`uvm_object_utils(config_then_data_vseq)
`uvm_declare_p_sequencer(my_vseqr) // gives p_sequencer typed as my_vseqr
task body();
cfg_seq cs = cfg_seq::type_id::create("cs");
data_seq ds = data_seq::type_id::create("ds");
cs.start(p_sequencer.ctrl_seqr); // 1) run config on agent A — and WAIT for it
ds.start(p_sequencer.data_seqr); // 2) THEN run data on agent B (ordering enforced)
endtask
endclass
// === TEST runs the VIRTUAL sequence on the VIRTUAL sequencer (one entry point) ===
task my_test::run_phase(uvm_phase phase);
config_then_data_vseq vseq = config_then_data_vseq::type_id::create("vseq");
phase.raise_objection(this);
vseq.start(env.vseqr); // coordinate BOTH interfaces from one sequence
phase.drop_objection(this);
endtaskThe code shows the coordination mechanism. The virtual sequencer (my_vseqr) holds handles to the real sequencers (ctrl_seqr, data_seqr) and drives nothing — it has no transaction type, no driver; it's a pure coordination point. The environment wires those handles in connect_phase (vseqr.ctrl_seqr = ctrl_agt.seqr) — so the virtual sequence can reach the real sequencers. The virtual sequence (config_then_data_vseq) orchestrates: via p_sequencer (typed as the virtual sequencer by uvm_declare_p_sequencer), it starts the config sequence on the control sequencer and waits for it (cs.start(p_sequencer.ctrl_seqr) blocks until the config completes), then starts the data sequence on the data sequencer — enforcing the ordering (config before data). The test runs the virtual sequence on the virtual sequencer (vseq.start(env.vseqr)) — one entry point that coordinates both interfaces. The shape to carry: a virtual sequencer is a handle-holder (the env wires it to the real sequencers); a virtual sequence orchestrates by starting sub-sequences on those handles in a controlled order (each start blocks until done, so sequencing them enforces ordering); and a test runs the virtual sequence as the single entry point for coordinated stimulus. The ordering — config-then-data — is enforced because the virtual sequence runs them in sequence (waits for one before starting the next), which is exactly the coordination independent sequences can't provide.
Verification Perspective — what coordination provides
The virtual sequence provides three kinds of shared control across agents — ordering, synchronization, and correlation — none of which independent per-agent sequences can offer. Seeing them is seeing why coordination needs a conductor.
The figure shows the three forms of shared control a virtual sequence provides across agents. Ordering: the virtual sequence runs sub-sequences in a controlled order — config before data — by starting one and waiting before starting the next. Synchronization: it makes one agent's sequence wait for an event on another — drive interface B only after a response arrives on interface A — by coordinating their progress (e.g., starting B's sequence only after A's reaches a point, or using shared events). Correlation: it shares a chosen value across agents — the same address on the control bus and the data port — by generating the value in the virtual sequence and passing it to both sub-sequences. The crucial reading is that all three require a shared control point — one place that sees both agents and governs their relationship. The brand-colored virtual sequence is that point; the success-colored capabilities (ordering, sync, correlation) flow from it across the warning-colored agents. The verification insight is that independent per-agent sequences offer none of these: run two sequences on two agents and they start together, run concurrently, randomize independently, with no shared timeline or state — so they can't be ordered (no shared clock of progress), can't be synchronized (no way to wait on each other), and can't be correlated (no shared value). Every cross-interface scenario that depends on a relationship between interfaces — and most interesting system scenarios do — requires this shared control, which only a virtual sequence provides. The figure is the argument for coordination: a virtual sequence is the shared control point that turns independent interfaces into a coordinated system — ordering them, synchronizing them, correlating them — exactly the control that makes cross-interface verification possible.
Runtime / Execution Flow — orchestrating a coordinated scenario
At run time, a coordinated scenario runs as a fixed orchestration: the test starts the virtual sequence, which starts and sequences sub-sequences on the agents, enforcing order and synchronization. The flow shows the orchestration.
The flow is the orchestration of a coordinated scenario. Run (step 1): the test runs the virtual sequence on the virtual sequencer — the one entry point for the coordinated scenario. First sub-seq + wait (step 2): the virtual sequence starts the first sub-sequence on agent A's sequencer (the config write on the control sequencer) and blocks until it completes — holding the next step until the config is done. Next sub-seq (step 3): only after A completes, the virtual sequence starts the next sub-sequence on agent B's sequencer (the data traffic) — enforcing the ordering (config-before-data). Drive (step 4): each real sequencer drives its interface in the order and timing the virtual sequence dictated. The runtime insight is that the ordering is enforced by the virtual sequence's control flow: because start blocks until the sub-sequence completes, running them in sequence (A's start, then B's start) guarantees A finishes before B begins — the config completes before the data starts, deterministically. This is the conductor cuing the sections in concert: the virtual sequence brings in A, waits for A, then brings in B — a controlled, deterministic progression across interfaces. Synchronization and correlation work the same way — the virtual sequence governs the agents' progress and shares values from its own control flow. Contrast the uncoordinated alternative (the DebugLab): two independent sequences started together run concurrently, so A and B race — the data might start before the config, nondeterministically. The flow shows why a virtual sequence achieves coordination: it holds the timeline — starting, waiting, and sequencing the agents' sub-sequences from one place — which is precisely the shared control that independent sequences lack.
Waveform Perspective — coordinated order across interfaces
The coordination is visible on a timeline: the config write on the control interface completes first, and only then does data traffic start on the data interface — the order enforced by the virtual sequence. The waveform shows the coordinated scenario.
Config-then-data: the virtual sequence orders the control write before the data traffic
12 cyclesThe waveform shows the coordinated ordering a virtual sequence enforces. The virtual sequence runs the config write first: ctrl_valid asserts and the control bus carries the config (ctrl_data = C0) at cycle 1, completing by cycle 2. Only after the config completes does the virtual sequence start the data traffic: data_valid asserts and the data bus carries traffic (D1, D2) starting at cycle 4. The crucial visual is the gap between the config completing (cycle 2) and the data starting (cycle 4) — that gap is the ordering the virtual sequence enforced: the data never starts before the config, because the virtual sequence ran the config sub-sequence, waited for it, then ran the data sub-sequence. The picture to carry is that coordination is visible as deterministic order across interfaces: the control interface's activity precedes the data interface's, every run, because the virtual sequence holds the timeline. The counterfactual — independent sequences — would show the two interfaces' activity overlapping or racing, with the data sometimes starting before the config (the DebugLab). Reading a waveform across coordinated interfaces — does the control activity complete before the data starts? is the order the same every run? — confirms the coordination. The deterministic, enforced order across two interfaces is the signature of a virtual sequence conducting: the control section played, the conductor waited, then the data section came in — one coordinated performance, not two independent solos racing.
DebugLab — the race from running independent sequences
Data arriving before config because two agents ran independent, uncoordinated sequences
A DUT intermittently failed: sometimes it processed data correctly, sometimes it produced garbage — nondeterministically, different across runs (and across random seeds). The failing runs showed the DUT receiving data traffic on the data bus before its configuration had been written on the control bus — so it processed data while unconfigured, producing garbage. The config and data stimulus were both present, but their relative order varied run to run — sometimes config-first (pass), sometimes data-first (fail). The scenario required config-before-data, but nothing enforced it.
The test ran two independent sequences — a config sequence on the control agent and a data sequence on the data agent — started concurrently, with no coordination. Independent sequences run concurrently with no shared timeline, so their relative order raced:
✗ TWO INDEPENDENT sequences started concurrently → no ordering → RACE:
fork
cfg_seq.start(env.ctrl_agt.seqr); // config on agent A
data_seq.start(env.data_agt.seqr); // data on agent B — runs CONCURRENTLY with config
join
// no shared timeline → data may start BEFORE config completes → DUT unconfigured → garbage
// order varies by seed/timing → INTERMITTENT, nondeterministic failure
✓ a VIRTUAL SEQUENCE enforces the order (config first, then data):
task config_then_data_vseq::body();
cfg_seq.start(p_sequencer.ctrl_seqr); // run config — and WAIT for it to complete
data_seq.start(p_sequencer.data_seqr); // THEN data — ordering enforced, deterministic
endtask
// data never starts before config completes → DUT always configured first → no raceThis is the uncoordinated-stimulus bug — running independent per-agent sequences for a scenario that requires coordination, so an order-dependent relationship races. The scenario needed config-before-data — a cross-interface ordering constraint. But the test started two independent sequences concurrently (in a fork, or just on two agents with no coordination), and independent sequences have no shared timeline: they start together and run concurrently, so which one's activity reaches the DUT first is a race, decided by timing and random seed. Sometimes the config won (config-first → pass); sometimes the data won (data-first → DUT processes data unconfigured → garbage). The result was an intermittent, nondeterministic failure — the worst kind to debug, because it passes on some seeds and fails on others, with no obvious cause in the DUT (which was innocent — it correctly produced garbage when fed data while unconfigured). The fix is a virtual sequence that enforces the ordering: run the config sub-sequence and wait for it to complete, then run the data sub-sequence — so the data never starts before the config, deterministically, every run. The general lesson, and the chapter's thesis: a scenario that requires coordination across interfaces — ordering, synchronization, or correlation — cannot be achieved with independent per-agent sequences, which run concurrently with no shared timeline or state and therefore race; use a virtual sequence on a virtual sequencer to orchestrate the agents' sub-sequences in a controlled order, providing the shared control that makes the scenario deterministic. Independent sequences for a coordinated scenario is a race; conduct it with a virtual sequence.
The tell is an intermittent, order-dependent failure across interfaces. Diagnose missing coordination:
- Check for order-dependence. If the failure correlates with the relative timing of stimulus on two interfaces (data-before-config), the scenario needs ordering it isn't getting.
- Look for independent sequences on multiple agents. Two sequences started concurrently on two agents, with no virtual sequence, have no enforced order.
- Confirm the nondeterminism tracks the seed. An intermittent failure that varies by random seed points at a race between uncoordinated sequences.
- Verify the DUT is innocent. If the DUT misbehaves only when stimulus arrives out of the required order, the bug is uncoordinated stimulus, not the DUT.
Coordinate cross-interface scenarios with a virtual sequence:
- Use a virtual sequence for any cross-interface ordering. Run sub-sequences in the required order, waiting for each before the next, on the virtual sequencer.
- Never rely on independent sequences for coordination. Concurrent per-agent sequences have no shared timeline; they race on order-dependent scenarios.
- Wire the virtual sequencer's handles in the env. Connect the virtual sequencer to the real sequencers in connect_phase so the virtual sequence can reach them.
- Use the virtual sequence for synchronization and correlation too. Wait for cross-agent events and share chosen values from the virtual sequence's control flow.
The one-sentence lesson: a scenario requiring coordination across interfaces (ordering, synchronization, or correlation) cannot use independent per-agent sequences — they run concurrently with no shared timeline and race — so orchestrate it with a virtual sequence on a virtual sequencer that runs the agents' sub-sequences in a controlled order, making the scenario deterministic.
Common Mistakes
- Running independent sequences for a coordinated scenario. Concurrent per-agent sequences have no shared timeline and race; use a virtual sequence to enforce order, synchronization, and correlation.
- Making the virtual sequencer drive an interface. A virtual sequencer drives nothing — it only holds handles to the real sequencers; driving belongs to the agents.
- Forgetting to wire the virtual sequencer's handles. The env must connect the virtual sequencer to the real sequencers in connect_phase, or the virtual sequence hits null handles.
- Coordinating from the test instead of a virtual sequence. Cross-agent orchestration belongs in a virtual sequence on the virtual sequencer, not ad hoc forks in the test.
- Assuming concurrent sub-sequences are ordered. Starting sub-sequences concurrently (fork) does not order them; run them in sequence (waiting) when order matters.
- Ignoring correlation across interfaces. When fields must match across interfaces, generate the value in the virtual sequence and pass it to both sub-sequences.
Senior Design Review Notes
Interview Insights
A virtual sequencer is a sequencer that drives no interface itself but holds handles to the agents' real sequencers, making it a coordination hub, and a virtual sequence is a sequence that runs on the virtual sequencer and orchestrates across agents by starting sub-sequences on those real sequencers in a controlled order, timing, and synchronization. They solve the problem of coordinating stimulus across multiple interfaces. A single agent's sequence drives one interface, which is fine for per-interface stimulus. But many scenarios need coordinated stimulus across interfaces — a configuration write on a control bus that must complete before data traffic on a data bus, a request on one interface correlated with a response on another, two interfaces driven in a specific relative order. You can't achieve this by running independent sequences on each agent's sequencer, because those run concurrently with no shared timeline, ordering, or state, so an order-dependent scenario races. The virtual sequencer and virtual sequence provide the single point of control. The environment wires the virtual sequencer's handles to the agents' sequencers in connect_phase, so the virtual sequence can reach them. A test runs the virtual sequence on the virtual sequencer, and the virtual sequence starts and sequences sub-sequences on the agents — for example, running the config sequence and waiting for it to complete, then running the data sequence, which enforces the ordering. The mental model is a conductor leading an orchestra: each agent's sequencer is a section that can play alone, but a coordinated symphony needs a conductor who cues each section at the right moment. The conductor plays no instrument — the virtual sequencer drives nothing — its whole job is coordinating the players. So they turn independent interfaces into a coordinated system controlled from one place.
Because independent sequences run concurrently with no shared timeline or state, so they can't be ordered, synchronized, or correlated — and any scenario depending on a relationship between interfaces will race. Coordination needs three kinds of shared control. Ordering: a constraint like write the config before sending data is an ordering across two agents, but independent sequences start together and run concurrently, so the ordering isn't enforced and the data can arrive before the config. Synchronization: a constraint like wait for the response on interface B before driving the next request on interface A means one sequence's progress depends on another's, but independent sequences have no shared state to wait on each other. Correlation: a constraint like the address on the control bus must match the port chosen for data means sharing a value across interfaces, but independent sequences randomize independently and can't share a chosen value. All three require a single control point that sees both agents and governs their relationship, which independent sequences lack by definition — each only knows its own agent. So if you run two sequences on two agents for an order-dependent scenario, which one's activity reaches the DUT first is a race decided by timing and random seed, producing intermittent, nondeterministic failures. The fix is a virtual sequence, which holds the timeline: it starts one sub-sequence and waits before starting the next, enforcing order; it coordinates cross-agent waiting, providing synchronization; and it generates and shares values from its own control flow, providing correlation. The virtual sequence is the shared control point that independent sequences can't be. So independent sequences are fine for single-interface stimulus, a solo, but useless for a coordinated cross-interface scenario, a symphony, which needs a conductor.
It enforces ordering by running the sub-sequences in sequence, relying on the fact that starting a sub-sequence blocks until it completes. In the virtual sequence's body, you call start on the first sub-sequence, targeting the first agent's real sequencer, and that call blocks until that sub-sequence finishes. Only after it returns do you call start on the next sub-sequence, targeting the second agent's sequencer. Because the first start blocks until done, running them in this order guarantees the first completes before the second begins. So for a config-then-data scenario, you run the config sequence on the control sequencer and wait for it, then run the data sequence on the data sequencer — and the data never starts before the config completes, deterministically, every run. The virtual sequence reaches the real sequencers through handles the environment wired into the virtual sequencer, accessed via p_sequencer, which is typed as the virtual sequencer using the declare-p-sequencer macro. The key is that the virtual sequence holds the timeline through its own control flow: the sequential code structure, with each start blocking, is what imposes the order. This is the opposite of starting the sub-sequences concurrently, say in a fork, which would not order them — they'd run in parallel and race. So ordering comes from sequential, blocking starts. If instead you want them to overlap but with a synchronization point, you'd use shared events or wait conditions within the virtual sequence to coordinate their progress. And if you want them truly parallel, you fork them. The point is that the virtual sequence's control flow is the coordination mechanism — sequential for ordering, events for synchronization, fork for parallelism — all governed from the one virtual sequence, which is what gives you deterministic, intended relationships across interfaces instead of a race.
Besides ordering, a virtual sequence can coordinate synchronization and correlation across agents, and parallelism. Synchronization means making one agent's sequence wait for an event on another — for example, drive the next request on interface A only after a response has been observed on interface B. The virtual sequence coordinates this through its control flow and shared events: it can wait on an event that another sub-sequence or the monitor triggers, or structure the sub-sequences so one only starts after a condition holds. This lets cross-interface scenarios depend on each other's progress, which independent sequences can't do because they have no shared state. Correlation means sharing a chosen value across agents — for example, ensuring the address driven on the control bus matches the port selected for data traffic. The virtual sequence generates the value in its own body and passes it to both sub-sequences, so they use the same value, rather than each randomizing independently and diverging. This is essential for scenarios where fields must match across interfaces. The virtual sequence can also coordinate parallelism: when sub-sequences should run concurrently rather than in order, it forks them, and it can combine patterns — run some setup in order, then fork parallel traffic, then synchronize at the end. So the virtual sequence is a general coordination mechanism: sequential starts for ordering, shared events and waits for synchronization, shared generated values for correlation, and fork for parallelism. All of these come from the virtual sequence holding the control flow over the agents' sub-sequences from one place. The common thread is shared control — the virtual sequence sees all the agents and governs the relationships among their stimulus, which is exactly what makes complex, realistic system-level scenarios across multiple interfaces possible and deterministic.
Exercises
- Identify the hub. Describe what a virtual sequencer holds, what it drives, and how the environment connects it.
- Enforce an order. Write the body of a virtual sequence that runs a reset sequence, then a config sequence, then data — and explain why the order holds.
- Diagnose the race. A DUT intermittently fails when data arrives before config. Name the cause and the fix.
- Coordinate three ways. Give an example each of ordering, synchronization, and correlation a virtual sequence would provide across two interfaces.
Summary
- Coordinating multiple agents uses a virtual sequencer — a sequencer that drives no interface but holds handles to the agents' real sequencers (the coordination hub) — and a virtual sequence that runs on it and orchestrates sub-sequences on those sequencers.
- The environment wires the virtual sequencer's handles to the agents' sequencers (in
connect_phase), and a test runs a virtual sequence on the virtual sequencer — one entry point for coordinated, multi-interface stimulus. - A virtual sequence provides the shared control that independent sequences can't: ordering (run sub-sequences in sequence, waiting for each), synchronization (wait for cross-agent events), and correlation (share chosen values) — the conductor cuing the sections in concert.
- Independent per-agent sequences cannot coordinate — they run concurrently with no shared timeline or state, so an order-dependent scenario (config-before-data) races, producing intermittent, seed-dependent failures.
- The durable rule of thumb: orchestrate any cross-interface scenario (ordering, synchronization, correlation) with a virtual sequence on a virtual sequencer — wire the sequencer's handles in the env, run the agents' sub-sequences in the required order from the virtual sequence's control flow — because independent per-agent sequences run concurrently with no shared timeline and race; the virtual sequencer drives nothing, it only conducts.
Next — Layered Environments: environments nest. The next chapter covers layered (hierarchical) environments — how a block environment becomes a sub-environment inside a larger subsystem or SoC environment, the reuse this enables, and the patterns for composing environments into a hierarchy that mirrors the design.