In short: A layered (virtual) sequence is a high-level UVM sequence that does not drive pins itself but orchestrates lower-level sequences on one or more sequencers, so you can compose a realistic system scenario out of reusable protocol-level building blocks.
Once a UVM testbench grows past a single agent, the hardest part of verification stops being "can I generate a legal transaction?" and becomes "can I express a scenario that spans several interfaces, in the right order, without copy-pasting stimulus into every test?" Layered sequences are the mechanism UVM gives you for exactly that. They let a senior scenario author think in terms of use cases, while the protocol experts below them keep owning the bit-level legality of each bus.
What "layered" actually means
In a flat testbench, a test starts one sequence on one sequencer and that sequence sends transaction items straight to a driver. In a layered testbench you build a small hierarchy of abstraction:
- System / scenario layer — a sequence describing intent: "boot the device, program 8 DMA descriptors, then inject a bus error and check recovery."
- Protocol layer — reusable sequences that know one bus: an AXI write-burst sequence, an APB register-write sequence, a UART frame sequence.
- Driver / item layer — the actual
uvm_sequence_itemobjects a driver consumes on a pin-level interface.
The scenario sequence never randomizes an awaddr or a strobe. It calls other sequences. That single rule is what makes stimulus reusable across a 32-bit and a 64-bit variant of the same SoC, or across block-level and chip-level environments.
A layered/virtual sequence carries no driver of its own. If a sequence's body() only ever starts other sequences (via `uvm_do_on or seq.start(target_sqr)), it is operating at the scenario layer.
Layered vs. virtual: the same idea, two emphases
People use "layered sequence" and "virtual sequence" almost interchangeably, and the overlap is real. The distinction worth remembering for an interview:
- A virtual sequence emphasizes coordinating multiple sequencers. It runs on a virtual sequencer that holds handles to the real sequencers and typically has no driver.
- A layered sequence emphasizes abstraction depth — a scenario built from sub-scenarios built from protocol items, even on a single interface.
Most practical testbenches are both at once. For the sequencer-handle mechanics and the p_sequencer macro, see our deep dive on the virtual sequence in UVM; this article focuses on composition.
Starting a sub-sequence: the three patterns
Inside a layered sequence's body(), you launch a child sequence in one of three ways:
| Pattern | Call | When |
|---|---|---|
start() | child.start(env.axi_sqr, this) | Explicit, most readable; lets you pass the parent for priority/arbitration. |
`uvm_do_on | `uvm_do_on(child, p_sequencer.axi_sqr) | Compact; randomizes then starts on a named sequencer. |
`uvm_do_on_with | same + with { len==16; } | Constrain the child inline at the scenario layer. |
The second argument — the target sequencer — is the whole point of layering. The parent decides where a child runs; the child decides what legal traffic it produces.
Ordering: sequential, parallel, and interleaved
A realistic scenario is rarely strictly sequential. UVM's fork...join inside body() lets you express concurrency precisely:
- Sequential — call sub-sequences one after another; useful for a boot/config phase that must complete before traffic.
- Parallel (
join) — launch AXI read traffic and interrupt-service traffic together to hit a race window. - Interleaved (
join_none+wait fork) — fire background sequences, do foreground work, then reconcile.
A common bug: starting a sub-sequence on the wrong sequencer handle. If your virtual sequencer connects axi_sqr but you call start(apb_sqr), the simulator (VCS, Xcelium, or Questa) will happily run legal APB traffic where you expected AXI — a silent scenario error, not a compile error.
A concrete numbers example
Consider a DMA controller verified at chip level. A single "stress" scenario might: program 64 descriptors over APB (64 register writes), kick off 64 AXI bursts of 16 beats each (1,024 beats), and assert one interrupt per completed descriptor. Written flat, that is ~1,100 hand-ordered items in one test. Written layered, it is one dma_stress_seq that loops a desc_program_seq and a burst_seq 64 times — roughly 40 lines, and the same two protocol sequences get reused by the directed smoke test, the error-injection test, and the throughput regression. On a real program this is the difference between 5 tests sharing 2 sequences and 5 tests each reinventing stimulus.
Reuse across block and chip level
The strongest argument for layering is vertical reuse. A protocol sequence written for an AXI VIP at block level (where the VIP is the only master) runs unchanged at SoC level, because the scenario layer above it simply targets a different sequencer instance. Teams doing SoC integration — the kind of work Avecas design-services engineers handle on production tape-outs — lean heavily on this so block owners' stimulus survives into the full-chip environment. For the sequencer-arbitration side of sharing one interface among many layered sequences, see UVM sequencer arbitration.
Pitfalls to avoid
- Putting randomization intent in the wrong layer. Keep bus legality constraints in the protocol sequence; keep scenario shape (counts, ordering, which errors) in the scenario sequence.
- Blocking the virtual sequence on a hung child. Always consider timeouts and
disable forkfor background traffic. - Hard-coding sequencer handles. Resolve them through the virtual sequencer or config DB so the same scenario ports across environments.
Want to write layered scenarios that survive from block to chip level? CourseTron's UVM verification track builds them hands-on with VCS and Questa labs — explore the courses.
Frequently asked questions
Is a layered sequence the same as a virtual sequence?
They overlap heavily. "Virtual sequence" stresses coordinating multiple sequencers from a driverless virtual sequencer; "layered sequence" stresses building scenarios from reusable lower-level sequences. In practice a scenario sequence is usually both.
Can a layered sequence drive items directly too?
It can, but it is considered poor style. Mixing scenario orchestration with pin-level item generation couples the layers and kills reuse. Keep the top layer calling sub-sequences only.
How do child sequences get the right sequencer?
Through the parent's call: child.start(target_sqr, this) or `uvm_do_on(child, target_sqr). The virtual sequencer typically holds the real sequencer handles, resolved via the UVM config DB.
Do layered sequences hurt simulation performance?
Negligibly. The abstraction is compile-time structure; at run time the same items reach the drivers. The reuse and readability gains far outweigh the tiny object-creation overhead.