← Design Verification Interviews

Interview Question · Design

Coordinating Two Interfaces

L2 Mid-LevelL3 Senioruvmsequencesvirtual sequences

The Question

The interviewer asks

Your DUT has an AXI host port and a UART. A test must configure it over AXI, then send UART traffic while AXI polls status. How do you structure the stimulus?

Reveal the Model Answer

Use a virtual sequence: a sequence that drives no pins itself. It holds handles to the AXI and UART sequencers and starts ordinary agent sequences on them. First it runs the configuration sequence on AXI. Then it forks the UART traffic and the AXI status polling. The scenario lives in one place, and each agent's sequences stay reusable.

Two Lanes Meet at One JoinThe plan starts a and b together. The next sequence, c, needs both leaf sequences to return.
Two lanes meet at one joinThe plan starts a and b together. The next sequence, c, needs both leaf sequences to return. plan Virtual sequence Start a and b inparallel stimulus Lane a sequence 16 then 32; returnsat 45 ns stimulus Lane b sequence 100, 101, 102;returns at 75 ns wait fork ... join Both child calls mustreturn next step Then start c Lane a accepts 200at 85 ns start astart ba endsb endsstart c
  1. Start together0 ns (illustrative)

    Two start calls run in separate branches. The virtual sequence waits at join.

    Follow the calls, values and waits
    1. call start(a): Virtual sequence → Lane a sequence
    2. call start(b): Virtual sequence → Lane b sequence
    3. Waiting: fork ... join — Waiting for both lanes
  2. One lane returns45 ns (illustrative)

    Lane a has had both requests accepted. Lane b still owes its third request, so c cannot start yet.

    Follow the calls, values and waits
    1. return a returned: Lane a sequence → fork ... join
    2. Waiting: fork ... join — Waiting for lane b
  3. The join releases75 ns (illustrative)

    Lane b returns after 102 is accepted. Both branches are finished, so the next start call can run.

    Follow the calls, values and waits
    1. return b returned: Lane b sequence → fork ... join
    2. call start(c): fork ... join → Then start c
    3. Released: fork ... join
  4. Stimulus is complete85 ns (illustrative)

    The driver completes c when 200 is accepted. Its result is still inside the DUT; checking completion is a separate wait.

Read the diagram as text
  • Virtual sequence: Start a and b in parallel
  • Lane a sequence: 16 then 32; returns at 45 ns
  • Lane b sequence: 100, 101, 102; returns at 75 ns
  • fork ... join: Both child calls must return
  • Then start c: Lane a accepts 200 at 85 ns
  • Virtual sequence → Lane a sequence: start a
  • Virtual sequence → Lane b sequence: start b
  • Lane a sequence → fork ... join: a ends
  • Lane b sequence → fork ... join: b ends
  • fork ... join → Then start c: start c

Arrows show sequence calls and branch completion, not DUT wires. This driver calls item_done at acceptance; a returned sequence does not prove its results were checked.

The test sets axi_sqr and uart_sqr from the environment, then starts this sequence with start(null).
class cfg_then_traffic extends uvm_sequence;   // no item type: drives nothing
  uvm_sequencer #(axi_item)  axi_sqr;
  uvm_sequencer #(uart_item) uart_sqr;
  task body();
    axi_cfg_seq  cfg  = axi_cfg_seq::type_id::create("cfg");
    uart_rx_seq  rx   = uart_rx_seq::type_id::create("rx");
    axi_poll_seq poll = axi_poll_seq::type_id::create("poll");
    cfg.start(axi_sqr);
    fork
      rx.start(uart_sqr);
      poll.start(axi_sqr);
    join
  endtask
endclass

Self-Check

Tick each key point you covered in your answer.

What Interviewers Listen For (Mid-Level)

A good mid-level answer draws the virtual sequence above both agents. It keeps each agent's sequences unaware of the other agent.

Cross-Questions

Where the interviewer goes next. Open a node to practise that follow-up.

WhyWhat IfProve ItCompareScale It

  1. Coordinating Two Interfaces
  2. CompareVirtual Sequencer vs Handles in the SequenceL2 Mid-Level

    Compare running a virtual sequence on a virtual sequencer with assigning sequencer handles to it directly.

    Reveal the Model Answer

    With a virtual sequencer, the env builds a sequencer that only holds handles, and the sequence reaches them through p_sequencer. That ties the sequence to one sequencer class. With direct handles, the test or a base sequence assigns them, and the sequence runs with start(null). That is fewer classes, and the sequence works in any env that can supply the handles.

    Who Starts WhomArrows are start calls. API sequences belong to the agent; workers and the virtual sequence belong to the test library.
    Who starts whomArrows are start calls. API sequences belong to the agent; workers and the virtual sequence belong to the test library. test Test run_phase starts onesequence virtual Virtual sequence Coordinates; sends noitems worker Worker: wrap_job Program a value, thenthe wrap case worker Worker on agentB Another interface'sjob api API: write_hi,write_wrap One item each, in theagent package sequencer Agent Asequencer Arbitrates the items vseq.start(null)uvm_do(w, a_sqr)uvm_do(b, b_sqr)uvm_do(hi)one item
    1. The test starts one thing

      The test's run_phase starts the virtual sequence and waits for it. Choice, order and parallelism live there.

      Follow the calls, values and waits
      1. call vseq.start(null): Test → Virtual sequence
      2. Waiting: Test — in vseq.start
    2. Workers per interface

      The virtual sequence starts a worker on each agent's sequencer. A worker composes small actions into a job.

      Follow the calls, values and waits
      1. call start wrap_job on a_sqr: Virtual sequence → Worker: wrap_job
      2. call start B's worker on b_sqr: Virtual sequence → Worker on agent B
      3. Waiting: Virtual sequence — until its workers return
    3. API sequences do one thing

      Each API sequence sends one protocol action: here one value from a range. The worker runs write_hi first.

      Follow the calls, values and waits
      1. call start write_hi: Worker: wrap_job → API: write_hi, write_wrap
      2. data item: x from 240 to 254: API: write_hi, write_wrap → Agent A sequencer
    4. Then the wrap case

      The worker's second action is write_wrap, which sends 255, whose result wraps to 0.

      Follow the calls, values and waits
      1. call start write_wrap: Worker: wrap_job → API: write_hi, write_wrap
      2. data item: x = 255: API: write_hi, write_wrap → Agent A sequencer
    5. Returns climb back up

      When its last API sequence returns, the worker returns to the virtual sequence; when every worker is done, the virtual sequence returns to the test.

      Follow the calls, values and waits
      1. return wrap_job returns: Worker: wrap_job → Virtual sequence
      2. return vseq returns: Virtual sequence → Test
      3. Released: Virtual sequence
      4. Released: Test
    Read the diagram as text
    • Test: run_phase starts one sequence
    • Virtual sequence: Coordinates; sends no items
    • Worker: wrap_job: Program a value, then the wrap case
    • Worker on agent B: Another interface's job
    • API: write_hi, write_wrap: One item each, in the agent package
    • Agent A sequencer: Arbitrates the items
    • Test → Virtual sequence: vseq.start(null)
    • Virtual sequence → Worker: wrap_job: uvm_do(w, a_sqr)
    • Virtual sequence → Worker on agent B: uvm_do(b, b_sqr)
    • Worker: wrap_job → API: write_hi, write_wrap: uvm_do(hi)
    • API: write_hi, write_wrap → Agent A sequencer: one item

    Change a test by changing the upper layers; the API layer ships, unchanged, with the agent.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "p_sequencer and m_sequencer are the same handle."

    m_sequencer is the generic base handle. p_sequencer is a typed handle that exists only if the sequence declares it with a macro.

    Source uvm-core 2020.3.1 (IEEE 1800.2 library) source, macros/uvm_sequence_defines.svh, `uvm_declare_p_sequencer

  3. WhyWhy a Virtual Sequence Can Start on NullL2 Mid-Level

    Why can a virtual sequence run with start(null) when it has no sequencer?

    Reveal the Model Answer

    A virtual sequence sends no items itself. It only starts child sequences on real sequencers, through its handles. So it needs no sequencer of its own, and start(null) just runs its body(). A child sequence is different, because it must send items through a sequencer.

    Self-Check

    Tick each key point you covered in your answer.

    Misconception

    "A virtual sequence must run on a virtual sequencer."

    It needs one only if it uses p_sequencer. With handles assigned directly, start(null) is enough.

    Source Verification Academy, UVM Cookbook, Sequences: virtual sequencer (PDF p238)

  4. What IfStarting Traffic Only After ConfigurationL2 Mid-Level

    Asked if they fork everything at once

    What if UART traffic must start only after the AXI write that enables the UART, not at time 0?

    Reveal the Model Answer

    Put the order in the virtual sequence: run the configuration sequence to completion, then fork the traffic. When the trigger is inside another running sequence, share a uvm_event or a barrier. The AXI side triggers it after the enabling write, and the UART side waits on it.

    Three Arrivals at a Barrier with Threshold 3Three processes meet at a uvm_barrier. Arrows point from each process to the barrier: wait_for() travels along them, and the release comes back.
    Three arrivals at a barrier with threshold 3Three processes meet at a `uvm_barrier`. Arrows point from each process to the barrier: `wait_for()` travels along them, and the release comes back. process Process A arrives at 30 ns process Process B arrives at 37 ns process Process C arrives at 55 ns barrier burst uvm_barrier,threshold 3 wait_for()wait_for()wait_for()
    1. A arrives30 ns (illustrative)

      A calls wait_for(). One waiter is fewer than three, so A is parked.

      Follow the calls, values and waits
      1. call wait_for(): Process A → burst
      2. Waiting: Process A — parked
    2. B arrives37 ns (illustrative)

      B is the second waiter and is parked too.

      Follow the calls, values and waits
      1. call wait_for(): Process B → burst
      2. Waiting: Process B — parked
    3. C arrives55 ns (illustrative)

      C prints 2 already waiting from get_num_waiters(), then calls wait_for() as the third arrival.

      Follow the calls, values and waits
      1. call wait_for(): Process C → burst
    4. All three pass55 ns (illustrative)

      The threshold is reached, and the barrier releases all three in the same time step. Each prints passed the barrier at 55 ns.

      Follow the calls, values and waits
      1. return release: burst → Process A
      2. return release: burst → Process B
      3. return release: burst → Process C
      4. Released: Process A
      5. Released: Process B
    Read the diagram as text
    • Process A: arrives at 30 ns
    • Process B: arrives at 37 ns
    • Process C: arrives at 55 ns
    • burst: uvm_barrier, threshold 3
    • Process A → burst: wait_for()
    • Process B → burst: wait_for()
    • Process C → burst: wait_for()

    Nobody passes until the last party arrives; then everybody passes at once.

    Self-Check

    Tick each key point you covered in your answer.

    Half-Truth

    "Add #1us before the UART sequence."

    A fixed delay breaks when configuration takes longer, and wastes time when it is short.

    Source uvm-core 2020.3.1 (IEEE 1800.2 library) source, base/uvm_event.svh and base/uvm_barrier.svh

  5. Prove ItSolve U5.3.1: A Background Sequence That Leaves Cleanly When KilledSimulator-graded

    Show it, don't just say it: this Silicon Lab problem is graded by the simulator.

    Open Problem U5.3.1

  6. Scale ItReusing Block Virtual Sequences at Subsystem LevelL3 Senior

    How do you reuse the block's virtual sequences when the block becomes one of four in a subsystem bench?

    Reveal the Model Answer

    Give each block virtual sequence its sequencer handles from outside, never by searching paths. The subsystem virtual sequence holds handles to each block environment's sequencers and starts the block sequences on them. Interfaces that become internal at subsystem level have no driver, so those agents turn passive and their sequences are not started.

    Self-Check

    Tick each key point you covered in your answer.

    Wrong Altitude

    "Copy the block sequences into the subsystem bench."

    Copies drift apart. Starting the same classes with different handles keeps one source of truth.

    Source Verification Academy, UVM Cookbook, Chaining virtual sequencers for vertical reuse (PDF p241)

What People Get Wrong

Common wrong answers and why they fail.

Misconception

"A virtual sequence drives pins through a virtual interface."

The two "virtual" words are unrelated. A virtual sequence drives nothing; it starts other sequences on real sequencers.

Source Accellera, UVM 1.1 User's Guide, §4.8 Virtual Sequences (PDF p77-78)

Half-Truth

"You must build a virtual sequencer to run one."

A virtual sequencer is one option. The Cookbook prefers assigning the sequencer handles to the sequence directly.

Source Verification Academy, UVM Cookbook, Sequences: virtual sequences and the alternative to a virtual sequencer (PDF p198, p237)

Wrong Altitude

"Let the AXI sequence start the UART sequence when it finishes."

That ties two agents together. Neither sequence can then be reused without the other.

Learn It, Then Prove It