Part 5 · Sequences · Intermediate

Test start() & run_phase Objections

How tests launch sequences — raise_objection, seq.start(sqr), blocking until body() returns, and starting phase.

Sequences do not run alone

A sequence class defines stimulus procedure, but nothing happens until something calls start() on a sequencer handle. In block-level tests, the test run_phase creates the sequence, configures knobs, raises an objection to keep simulation alive, calls start(), then drops the objection.

Without a raised objection, run_phase can end before the driver consumes items — the simulation stops while the sequence sits blocked at finish_item. Objections are the UVM mechanism that ties test lifetime to driver activity.

start() is blocking: it runs pre_body, body, post_body sequentially and returns only when body() completes. All finish_item calls inside body() must resolve before start() returns.

diagram
[STIM] test launch sequence

  run_phase:
    raise_objection    ← keep sim alive
    seq.start(sqr)       ← blocks until body() done
    drop_objection       ← allow sim to end

  Without objection: run_phase ends  driver killed  hang

Complete apb_smoke_test

systemverilog
class apb_smoke_test extends uvm_test;
  `uvm_component_utils(apb_smoke_test)

  apb_env env;

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    env = apb_env::type_id::create("env", this);
  endfunction

  task run_phase(uvm_phase phase);
    apb_wr_seq seq;

    phase.raise_objection(this);

    seq = apb_wr_seq::type_id::create("seq");
    seq.num_trans = 5;

    `uvm_info("TEST", "starting apb_wr_seq", UVM_LOW)
    seq.start(env.apb_agent.sqr);   // blocks until body() completes
    `uvm_info("TEST", "apb_wr_seq done", UVM_LOW)

    phase.drop_objection(this);
  endtask
endclass
  • raise_objection before start — driver run_phase stays active.

  • num_trans configured on sequence object before start().

  • start() argument is the agent sequencer — env.apb_agent.sqr.

  • drop_objection after start returns — all items driven.


What start() does internally

start() is more than calling body(). It sets p_sequencer, optionally jumps starting phase, and runs the sequence lifecycle hooks:

diagram
[SEQ] seq.start(sqr) internal sequence

  1. seq.m_sequencer = sqr           ← p_sequencer now valid
  2. pre_start()                       ← rarely overridden
  3. pre_body()
  4. body()                            ← your start_item/finish_item loop
  5. post_body()
  6. post_start()
  7. return to test

  [STIM] test blocked at step 4 until all finish_item complete
diagram
[STIM] [SEQ] [DRV] [UVM] — test-time stack

  [STIM] apb_smoke_test.run_phase
      │ raise_objection
      │ seq.start(env.apb_agent.sqr)
      ▼
  [SEQ] apb_wr_seq.body()  ──► start_item/finish_item loop
      ▼
  [SEQ] apb_sequencer
      ▼
  [DRV] apb_driver.run_phase  (active because objection raised)
      ▼
  DUT pins

Forking sequences from a test

Parallel scenarios fork multiple start() calls. Each sequence arbitrates on its sequencer independently:

systemverilog
task run_phase(uvm_phase phase);
  apb_wr_seq wr_seq = apb_wr_seq::type_id::create("wr");
  apb_rd_seq rd_seq = apb_rd_seq::type_id::create("rd");

  phase.raise_objection(this);

  fork
    wr_seq.num_trans = 50;
    wr_seq.start(env.apb_agent.sqr);
    rd_seq.num_trans = 50;
    rd_seq.start(env.apb_agent.sqr);
  join

  phase.drop_objection(this);
endtask
diagram
[SEQ] forked start() on same sqr

  wr_seq and rd_seq interleave at start_item boundaries
  [DRV] sees: wr item, rd item, wr item, ... (FIFO arbitration)

  fork/join waits for BOTH body() to complete before drop_objection
  • Same sequencer — items interleave; use lock/grab for atomic bursts.

  • Different sequencers — fully parallel, no arbitration between agents.

  • join_any + disable fork for timeout or first-complete scenarios.


Virtual sequence start from test

Chip tests start one virtual sequence on env.v_sqr — the test stays thin, scenario logic lives in the vseq:

systemverilog
task run_phase(uvm_phase phase);
  dma_xfer_vseq vseq = dma_xfer_vseq::type_id::create("vseq");

  phase.raise_objection(this);
  vseq.randomize();
  vseq.start(env.v_sqr);    // vseq starts sub-sequences on p_sequencer.apb_sqr etc.
  phase.drop_objection(this);
endtask

Virtual sequences do not call start_item themselves — they call start() on sub-sequences. See Virtual Sequences topic.


Objection debug checklist

  • Log at raise and drop — confirm test run_phase reached start().

  • If sim ends instantly: objection dropped before start() or never raised.

  • If sim hangs: objection still raised — sequence or driver blocked.

  • set_drain_time on drop for in-flight monitor transactions.

systemverilog
phase.raise_objection(this);
`uvm_info("TEST", "objection raised", UVM_LOW)
seq.start(env.apb_agent.sqr);
`uvm_info("TEST", "seq.start returned", UVM_LOW)
phase.drop_objection(this);
`uvm_info("TEST", "objection dropped", UVM_LOW)

Key takeaways

  • raise_objection before seq.start — driver must be running.

  • start() blocks until body() returns — all items must complete.

  • start(sqr) sets p_sequencer — required for lock/grab in body().

Common pitfalls

  • No raise_objection — run_phase ends, driver stops, sequence hangs.

  • drop_objection before start() returns — sim may end mid-sequence.

  • start() on wrong sequencer — passive agent or null handle.

  • Creating sequence but never calling start() — zero stimulus.