Part 5 · Sequences · Intermediate

Pull Model Overview: Sequence, Sequencer, Driver Timeline

Why UVM uses pull not push, the three-component handshake timeline, and back-pressure semantics.

Why pull, not push

In a naive push model, the sequence would fire transactions at the driver whenever body() runs — regardless of whether the DUT can accept them. Real buses have back-pressure: APB waits for PREADY, AXI stalls on AWREADY, PCIe credits limit outstanding TLPs. The pull model lets the driver request the next item only when it is ready to drive, naturally matching DUT readiness.

The sequencer sits between sequence and driver as an arbiter and queue. When multiple sequences compete for one driver, the sequencer serializes start_item requests. The driver never sees competing sequences directly — only the next granted item via get_next_item.

finish_item blocks the sequence thread until the driver completes the current item. This is intentional back-pressure propagated up to the test — a slow DUT slows stimulus generation automatically.

diagram
PUSH (not UVM) vs PULL (UVM)

  PUSH:  seq  driver "here's a txn"  drive immediately (ignores DUT ready)
  PULL:  driver "I'm ready"  seq  item  drive  item_done  seq continues

  [DRV] controls throughput — sequence cannot outrun pin-level reality

Three-component timeline

The canonical handshake involves three active participants. The timeline below shows one APB write beat from all three perspectives:

diagram
Legend: [SEQ] [DRV] [ITEM]

  TIME 
  ─────────────────────────────────────────────────────────────────

  [SEQ] SEQUENCE
    │ start_item(req)
    │──────────────────────────────► sequencer (item pending)
    │ randomize(req)                  [ITEM] addr,data,write filled
    │ finish_item(req) ─── BLOCKS ─────────────────────────────┐
    │                                                             │
  [SEQ] SEQUENCER                                               │
    │                    get_next_item(req) ◄── [DRV] request   │
    │──────────────────► deliver req to driver                    │
    │                              item_done() ◄── [DRV] done     │
    │ finish_item UNBLOCKS ◄─────────────────────────────────────┘
    │ (sequence continues)

  [DRV] DRIVER
    │                              get_next_item(req)  ← blocks until pending
    │                              drive_apb(req)       ← reads [ITEM]
    │                              item_done()          ← releases [SEQ]
diagram
[STIM] [SEQ] [DRV] [ITEM] [UVM] — stack view during one beat

  [STIM] test waiting on seq.start() to return
  [SEQ]  apb_wr_seq blocked at finish_item
  [SEQ]  sequencer holding req in pending slot
  [DRV]  apb_driver in drive_apb — wiggling psel, waiting pready
  [ITEM] apb_item { addr=0x4000, data=0xDEAD, write=1 } — read-only at driver
  [UVM]  agent connects sqr ↔ driver via seq_item_port

Blocking points — where threads stall

Understanding where each thread blocks is essential for hang debug. There are exactly four blocking API calls in the basic handshake:

diagram
Blocking call          Who blocks          Until when
  ─────────────────────────────────────────────────────────────
  start_item(req)        sequence            sequencer grants slot
  finish_item(req)       sequence            driver calls item_done
  get_next_item(req)     driver              sequence calls finish_item path
  seq.start(sqr)         test/vseq           body() returns
  • start_item rarely blocks long — unless arbitration queues behind another sequence.

  • finish_item is the long block — entire drive_apb duration.

  • get_next_item blocks when no sequence has called start_item yet.

  • seq.start blocks for entire body() — all items in the sequence.


Multi-sequence arbitration preview

When two sequences run on the same sequencer, items interleave at start_item boundaries — not mid-transaction. The pull model preserves atomic per-item drive:

diagram
[SEQ] two sequences, one [DRV] — FIFO arbitration

  bg_seq:  start_item  finish_item (item A)
  test_seq: start_item  finish_item (item B)
  bg_seq:  start_item  finish_item (item C)

  Driver sees: A  B  C (serialized at item boundaries)

  [DRV] never gets two items simultaneously without explicit pipeline
systemverilog
fork
  bg_seq.start(sqr);    // long-running background
  test_seq.start(sqr);  // short directed burst
join
// Items interleave — see Sequencer Arbitration topic for lock/grab

Pull model and passive agents

Passive agents have a monitor but no driver. Sequences started on a passive agent's sequencer block forever at finish_item — nothing calls get_next_item. This is a common integration mistake when mixing active and passive agents.

diagram
[UVM] active vs passive — handshake requirement

  ACTIVE agent:  sqr ← driver ← DUT pins     handshake complete
  PASSIVE agent: sqr (no driver)                 finish_item HANGS

  Passive agent: do NOT start sequences on its sequencer
  Use active agent sqr for stimulus

Key takeaways

  • Pull model: driver requests when ready — matches DUT back-pressure.

  • finish_item blocks until item_done — the primary sequence stall point.

  • Three components: sequence produces, sequencer mediates, driver executes.

Common pitfalls

  • Expecting push semantics — sequence cannot 'fire and forget' items.

  • Starting sequences on passive agent sequencers — permanent hang.

  • Assuming fork order equals item order — arbitration interleaves.