Part 5 · Sequences · Intermediate

Layering Stack: Beat → Burst → Flow → Virtual Sequence

The four-level stimulus stack, what each layer owns, and when to call start() vs inline start_item.

Why layer at all

Protocol stimulus has natural granularity. An AXI burst is not one transaction — it is a sequence of beats with shared address and burst type. A stress scenario is not one burst — it is many bursts with idle gaps and competing masters. A chip test is not one agent — it is coordinated traffic across DMA, PCIe, and AXI ports. Layering maps each granularity to its own sequence class so tests express intent at the top and reuse

mechanics at the bottom. Without layers, every test re-implements beat loops, burst length encoding, and idle insertion — and a single constraint fix requires editing dozens of files.

diagram
[SEQ] abstraction ownership — what each layer adds

  BEAT layer     one start_item/finish_item cycle
                 owns: per-beat randomization, handshake timing

  BURST layer    repeat beats with shared burst metadata
                 owns: burst_len, addr increment, WLAST/RLAST policy

  FLOW layer     multiple bursts with scenario knobs
                 owns: num_bursts, inter-burst gaps, error injection policy

  VSEQ layer     multi-agent coordination
                 owns: fork/join, sequencer handles, scenario ordering

  TEST layer     objection lifecycle, config, library selection
                 owns: when to start, how long to run, factory overrides

The full stack diagram

The diagram below is the canonical layering map for a single AXI master in a multi-agent SoC. Virtual sequences sit above agent-local flow sequences; flow sequences call burst sequences; burst sequences call beat sequences or base helpers.

diagram
Legend: [STIM] [SEQ] [DRV] [UVM]

  TEST [STIM]
    │
    │  env.vseqr is virtual — no driver below it
    ▼
  ┌──────────────────────────────────────────────────────────────┐
  │ chip_stress_vseq [SEQ]                                        │
  │   fork                                                  join  │
  │     dma_vseq.start(env.dma_sqr)                               │
  │     pcie_vseq.start(env.pcie_sqr)                             │
  │     axi_flow.start(env.axi_sqr)   ◄── flow on real sequencer│
  └──────────────────────────────────────────────────────────────┘
         │
         ▼ (per agent)
  ┌──────────────────────────────────────────────────────────────┐
  │ axi_burst_flow_seq [SEQ]                                      │
  │   repeat (num_bursts)                                         │
  │     axi_burst_seq.start(p_sequencer)  ◄── start() delegation  │
  │     #(gap_cycles)                                             │
  └──────────────────────────────────────────────────────────────┘
         │
         ▼
  ┌──────────────────────────────────────────────────────────────┐
  │ axi_burst_seq [SEQ]                                           │
  │   repeat (burst_len + 1)                                      │
  │     send_beat(req)  ◄── base helper OR axi_single_seq.start() │
  └──────────────────────────────────────────────────────────────┘
         │
         ▼
  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐
  │ SEQUENCER   │───►│   DRIVER    │───►│  DUT pins   │
  │ [UVM]       │    │ [DRV]       │    │             │
  └─────────────┘    └─────────────┘    └─────────────┘
  • Virtual sequencer has child sequencer handles — vseq never calls start_item directly.

  • Flow and burst sequences run on the agent sequencer — they own arbitration slots.

  • Beat layer is the only layer that routinely touches start_item/finish_item.


start() vs inline start_item — decision guide

Two mechanisms connect layers: sub_seq.start(p_sequencer) delegates an entire sequence body, while send_beat(req) (or inline start_item) handles a single atomic transaction. Choosing wrong creates either bloated inheritance or hidden arbitration surprises.

diagram
[SEQ] when to use which

  use start(sub_seq) when:
    sub-sequence has its own body() with multiple beats
    you want independent arbitration rounds per sub-seq
    sub-seq is reused standalone in other flows

  use send_beat(req) / inline start_item when:
    beat is one atomic handshake
    burst loop is simple repeat — no separate sequence class needed
    you are inside a base class helper shared by many derived seqs

Walkthrough — one burst through the stack

diagram
[STIM] timeline — axi_burst_flow_seq drives one burst

  T0  TEST raises objection, starts chip_stress_vseq on v_sqr
  T1  vseq forks axi_burst_flow_seq.start(axi_sqr)
  T2  flow_seq: axi_burst_seq burst = create; burst.start(p_sequencer)
  T3  burst_seq body: req = axi_item::create; burst_len = 3
  T4  beat 0: start_item(req)  randomize  finish_item(req)  [SEQDRV]
  T5  beat 1: start_item(req)  randomize  finish_item(req)
  T6  beat 2: start_item(req)  randomize  finish_item(req)
  T7  beat 3: start_item(req)  randomize  finish_item(req)  (WLAST)
  T8  burst_seq body returns; flow_seq inserts #(20) idle
  T9  flow_seq starts next burst or returns

Each start_item is one arbitration slot. A burst with four beats consumes four slots unless the burst sequence holds lock (covered in arbitration lesson).


Layering guidelines

  • Never skip layers with a 500-line body() — split at natural protocol boundaries.

  • Each layer adds exactly one level of abstraction; do not mix burst logic into vseq.

  • Share randomization policy in base sequences, not in every derived body().

  • Libraries and virtual sequences compose — vseq can start a seq_lib on one agent.

  • Name layers by protocol action: axi_single_seq, axi_burst_seq, axi_stress_flow_seq.

Key takeaways

  • Beat → burst → flow → vseq is the standard reuse stack for protocol VIP.

  • Virtual sequences coordinate; agent sequences drive; beat layer handshakes.

  • start() for reusable sub-sequences; inline start_item for atomic beats.

Common pitfalls

  • Virtual sequence calling start_item on v_sqr — no driver below virtual sequencer.

  • Flow sequence that randomizes beat fields — belongs in burst or beat layer.

  • Test body() with nested repeat loops — belongs in flow or burst sequence class.