Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here4
  • UVM Foundations14
  • UVM Phases & Simulation Lifecycle26
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
    • Sequence Items and the Driver Handshake
    • Sequence Mechanics: Lifecycle, Sequencer, Responses and Control
    • Virtual Sequences and Coordination
      • The Coordination Problem
      • Virtual Sequencer Wiring
      • Declaring p_sequencer
      • Scenario Patterns and Placement
      • Sub-sequences and Sequence Libraries
      • Virtual Sequences Without a Virtual Sequencer
      • lock vs grab: Who Gets the Sequencer
      • Protocol Layering with Translator Sequences
      • Reactive (Slave) Sequences
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Virtual Sequences and Coordination
  1. Topics
  2. UVM Verification
  3. UVM Sequences & Stimulus
  4. Virtual Sequences and Coordination
  5. Sub-sequences and Sequence Libraries

Part 5 · Sequences · Intermediate

Sub-sequences and Sequence Libraries

Building bigger sequences from smaller ones with the parent argument, and sequence libraries: registration, the four selection modes, counts, factory creation, how library traffic shares the sequencer, and the ITEM-mode trap, with real logs from the DMA bench.

On this page

    Your DMA regression has three tiny tests that each read one register in a loop: STATUS, COUNT, KEY. A lead asks for one test that mixes them differently on every seed. Someone suggests a sequence library; someone else says libraries are "magic nobody can debug". Then a third engineer tries one in UVM_SEQ_LIB_ITEM mode, and the log says it ran in UVM_SEQ_LIB_RAND instead. What does a library actually do, step by step, and why did it change its own mode?

    The idea: compose, then shuffle

    Big stimulus is built from small stimulus. A sub-sequence is a sequence started from inside another sequence's body, on the same or another sequencer. The register programming of a DMA job is three single-register sequences in a row; a long stream is several packets. Composing keeps each small piece testable and reusable, and the parent argument of start() keeps the family together for naming, locks and priority.

    A sequence library adds choice. It is itself a sequence that holds a list of registered sequence types and, when started, runs a number of them picked by a selection mode. Think of a jukebox on shuffle: you load it with records (registered sequences), pick a shuffle rule (random, or every record once before any repeat), and say how many songs to play. Each seed gives a different playlist from the same records.

    The picture: what a library does when started

    Flowchart: the library is randomized, started, and repeatedly picks a type by selection mode, creates it through the factory, randomizes and sends it, and finally reports a distribution.
    A sequence library picks a type per iteration, creates it through the factory, randomizes it and sends it on the library's own sequencer, then reports how often each type ran.

    Two details in the picture matter later. The picked sequences are created through the factory, so a type override replaces them inside the library too. And the configuration is read in pre_randomize, which runs when you call randomize() on the library, not when you start it.

    Build it

    Sub-sequences: the DMA job's register writes as one sequence

    The bench's dma_vseq programs KEY, LEN and CTRL through a helper that starts one apb_rw_seq per register. Packaged as a plain APB sequence, the same idea reads like this. Every child is started on the parent's own sequencer with this as the parent:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // snippet
    class dma_prog_seq extends uvm_sequence #(apb_item);
      `uvm_object_utils(dma_prog_seq)
      rand bit [31:0] key;
      rand bit [15:0] len;
      constraint len_c { len inside {[1:16]}; }
    
      function new(string name = "dma_prog_seq");
        super.new(name);
      endfunction
    
      task write_reg(bit [7:0] addr, bit [31:0] data);
        apb_rw_seq s = apb_rw_seq::type_id::create("s");
        s.addr = addr; s.write = 1; s.data = data;
        s.start(m_sequencer, this);            // same sequencer, this as parent
      endtask
    
      task body();
        write_reg(KEY, key);
        write_reg(LEN, len);
        write_reg(CTRL, 32'h3);                // IRQ_EN | GO
      endtask
    endclass

    Because the children belong to dma_prog_seq, a lock() taken at the top of its body would let all three writes through and keep every other sequence out, which is how you make register programming atomic (lock vs grab). Without a lock, the three children's items still compete with everything else on the APB sequencer one item at a time; how the sequencer chooses, and what priority does in each mode, is the home topic of Inside the Sequencer.

    A library of register reads

    For the scratch run, three one-access sequences share a base class that reads one register; each derived class picks the address. The library registers them with ` uvm_add_to_seq_lib `, and its constructor must call init_sequence_library()`, which copies the registered types into this instance's list:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // snippet
    class dma_reg_rd_seq extends uvm_sequence #(apb_item);
      `uvm_object_utils(dma_reg_rd_seq)
      bit [7:0] reg_addr = STATUS;
      function new(string name = "dma_reg_rd_seq");
        super.new(name);
      endfunction
      task body();
        apb_item it = apb_item::type_id::create("it");
        start_item(it);
        if (!it.randomize() with { addr == reg_addr; write == 0; })
          `uvm_fatal("RAND", "apb_item randomize failed")
        finish_item(it);
      endtask
    endclass
    // dma_status_rd_seq, dma_count_rd_seq and dma_key_rd_seq extend it and set reg_addr in new()
    
    class dma_rd_lib extends uvm_sequence_library #(apb_item);
      `uvm_object_utils(dma_rd_lib)
      `uvm_sequence_library_utils(dma_rd_lib)
      function new(string name = "dma_rd_lib");
        super.new(name);
        init_sequence_library();               // without it: SEQLIB/NOSEQS
      endfunction
    endclass
    `uvm_add_to_seq_lib(dma_status_rd_seq, dma_rd_lib)
    `uvm_add_to_seq_lib(dma_count_rd_seq,  dma_rd_lib)
    `uvm_add_to_seq_lib(dma_key_rd_seq,    dma_rd_lib)

    The test sets the mode and the count range, randomizes the library (which picks sequence_count between the minimum and maximum), and starts it on the APB sequencer like any sequence:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // snippet: dma_lib_test::run_phase
    dma_rd_lib lib = dma_rd_lib::type_id::create("lib");
    phase.raise_objection(this);
    lib.selection_mode   = UVM_SEQ_LIB_RANDC;
    lib.min_random_count = 6;
    lib.max_random_count = 6;
    if (!lib.randomize()) `uvm_fatal("RAND", "dma_rd_lib randomize failed")
    lib.start(env.apb.sqr);
    #100ns;
    phase.drop_objection(this);

    Selection mode

    What runs

    UVM_SEQ_LIB_RAND

    A random registered sequence each iteration

    UVM_SEQ_LIB_RANDC

    Random cyclic: every registered sequence once before any repeats

    UVM_SEQ_LIB_ITEM

    No sequences at all: randomized items of the library's item type

    UVM_SEQ_LIB_USER

    Whatever your select_sequence(max) override returns: an index from 0 to max into the registered sequences, for example a weighted pick or a round-robin counter

    The defaults are 10 for min_random_count, max_random_count and sequence_count. The same three settings can come from config_db instead, as a uvm_sequence_library_cfg under the key default_sequence.config in the scope of the sequencer and phase, which is how a library is used as a phase's default sequence. Setting them on the object, as above, is easier to read.

    Watch it run

    The scratch run of dma_lib_test with +UVM_VERBOSITY=UVM_HIGH (the distribution line is printed at UVM_HIGH):

    Diagram
    UVM_INFO uvm_sequence_library.svh(752) @ 0: uvm_test_top.env.apb.sqr@@lib [SEQLIB/START] Starting sequence library dma_rd_lib in unknown phase: 6 iterations in mode UVM_SEQ_LIB_RANDC
    UVM_INFO uvm_sequence_library.svh(829) @ 275000: uvm_test_top.env.apb.sqr@@lib [SEQLIB/END] Ending sequence library in phase unknown
    UVM_INFO uvm_sequence_library.svh(831) @ 275000: uvm_test_top.env.apb.sqr@@lib [SEQLIB/DSTRB] '{"dma_count_rd_seq":'h2, "dma_key_rd_seq":'h2, "dma_status_rd_seq":'h2}
    • 6 iterations: the randomized count, forced to 6 by equal minimum and maximum.

    • `'h2` for each type: RANDC ran every sequence once before repeating, twice round. With RAND the counts would be uneven.

    • "in unknown phase": the library looks for a starting phase, which only exists when it runs as a phase's default sequence. Started from a test, there is none; that is harmless.

    • 275000 ps for six reads: one APB read every 40 ns after reset. The library's traffic is ordinary traffic on the APB sequencer: if a poller were running, the two would interleave item by item.

    What breaks: the library that changed its own mode

    Here is the third engineer's run from the opening: the same test with selection_mode = UVM_SEQ_LIB_ITEM. The library refused, with an error that blames a virtual sequence nobody wrote:

    Diagram
    UVM_ERROR uvm_sequence_library.svh(712) @ 0: reporter@@lib [SEQLIB/VIRT_SEQ] selection_mode cannot be UVM_SEQ_LIB_ITEM when running as a virtual sequence. Using UVM_SEQ_LIB_RAND mode
    UVM_INFO uvm_sequence_library.svh(752) @ 0: uvm_test_top.env.apb.sqr@@lib [SEQLIB/START] Starting sequence library dma_rd_lib in unknown phase: 6 iterations in mode UVM_SEQ_LIB_RAND

    Look at the reporter: reporter@@lib, not env.apb.sqr@@lib. The check ran inside randomize() (from pre_randomize), before start() had given the library a sequencer. With no sequencer, the library cannot tell it will run on a real one, so it treats itself as virtual, reports an error and switches to RAND. The fix is one line before the randomize: lib.set_sequencer(env.apb.sqr);. With it, ITEM mode runs, and shows what it really does:

    Diagram
    UVM_INFO uvm_sequence_library.svh(752) @ 0: uvm_test_top.env.apb.sqr@@lib [SEQLIB/START] Starting sequence library dma_rd_lib in unknown phase: 6 iterations in mode UVM_SEQ_LIB_ITEM
    UVM_INFO dma_tb.sv(151) @ 75000: uvm_test_top.env.apb.mon [APB_MON] WR addr=3c data=baac3d01 SLVERR
    UVM_INFO dma_tb.sv(151) @ 115000: uvm_test_top.env.apb.mon [APB_MON] RD addr=d4 rdata=00000000 SLVERR
    UVM_INFO uvm_sequence_library.svh(831) @ 275000: uvm_test_top.env.apb.sqr@@lib [SEQLIB/DSTRB] '{"apb_item":'h6}

    Six raw apb_items, randomized only by the item's own constraints: random addresses, mostly unmapped, so mostly SLVERR, and random writes. None of the three registered sequences ran. ITEM mode is a quick way to spray a bus with legal-by-constraint items, which is useful for a protocol agent's own tests and almost never what a design-level test wants. Two lessons generalize: configuration that is read in pre_randomize needs the sequencer set first, and a library's distribution line is the first thing to read when it does not do what you expected.

    Interview

    "What is a sequence library, and would you use one?"

    Weak: "It's a collection of sequences that runs randomly."

    Strong: "It's a sequence parameterized by the item type that holds registered sequence types, added with uvm_add_to_seq_lib and copied in by init_sequence_library() in the constructor. When started, it runs sequence_count sequences, chosen by selection_mode: RAND, RANDC, USER with select_sequence(), or ITEM, which sends raw random items and runs no sequences. It creates them through the factory and randomizes them, so overrides apply. I use one for protocol-level soak traffic on one agent; for design scenarios I prefer a virtual sequence with random knobs, because the scenario is readable in one place. One trap: randomizing the library before start reads its config with no sequencer, so ITEM mode falls back to RAND unless you call set_sequencer first."

    Follow-ups: How do you make a library the default sequence of a phase? (config_db default_sequence on the sequencer's phase scope, with a uvm_sequence_library_cfg), and How do you see what it ran? (SEQLIB/DSTRB at UVM_HIGH).

    Check yourself A library contains axis_pkt_seq, and a test adds axis_pkt_seq::type_id::set_type_override(axis_short_pkt_seq::get_type()). Which sequence does the library run?

    axis_short_pkt_seq. The library creates each picked type through the factory, so the type override applies inside it exactly as it would anywhere else.

    Check yourself dma_prog_seq starts its three register writes with s.start(m_sequencer), without this. What changes when it runs on its own, and what changes if it is ever wrapped in a lock()?

    On its own, only the names (apb.sqr@@s instead of apb.sqr@@prog.s) and anything keyed on them, such as config_db lookups. Inside a lock, the children are no longer part of the lock holder's family, so the first write is blocked by its own parent's lock and the sequence deadlocks.

    Check yourself A library in UVM_SEQ_LIB_RANDC mode with three registered sequences runs 7 iterations. Can one sequence run three times?

    Yes, but only in the third cycle: RANDC runs all three once (3 iterations), then all three again (6), then one more. Two of them run twice and one runs three times.

    Next: the bench's own virtual sequence needs none of the virtual-sequencer machinery from earlier in this topic. Virtual Sequences Without a Virtual Sequencer takes dma_vseq apart.

    Related topics

    • Inside the sequencer: arbitration and priority

    • Overriding items and sequences

    • lock vs grab

    Previous

    89 / 280

    Next: Virtual Sequences Without a Virtual Sequencer

    Up next

    Up nextVirtual Sequences Without a Virtual Sequencer

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • The Coordination Problem
    • Virtual Sequencer Wiring
    • Declaring p_sequencer