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
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
    • Callbacks, Command Line, Seeds and Versions
      • Callbacks Done Right
      • The Command Line in Full
      • Random Stability and Seeds
      • UVM 1.2 to IEEE 1800.2: The Delta Table
      • Performance at Scale
    • Synchronisation and Reporting
    • Testbenches at Scale
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Callbacks, Command Line, Seeds and Versions
  1. Topics
  2. UVM Verification
  3. Advanced UVM in Practice
  4. Callbacks, Command Line, Seeds and Versions
  5. Random Stability and Seeds

Part 10 · Advanced Topics · Intermediate

Random Stability and Seeds

Thread and object random stability in SystemVerilog, how UVM seeds component phases, sequences and items by name, what changes stimulus and what does not, replaying a failing seed, and seed policy in regressions.

On this page

    Seed 7731 failed last night: a mismatch on the third random job. You rerun it with the same seed and it fails again, good. You add one line to the test to read and print one more register first, rerun, and the failure is gone. All three jobs now have different keys and lengths. Nothing in the DUT changed. Was the bug fixed by a register read? No: the read created a sequence object in the test's thread, and that drew from the thread's random generator. Why did one line move everything, when adding a whole new component would not have?

    Every thread and every object has its own dice

    SystemVerilog gives each thread (process) and each object its own random number generator (RNG). When a thread starts or an object is constructed, it takes its seed from the RNG of the thread that created it. This is random stability: the values a thread draws depend only on its own seed and on the order of random calls and object creations inside that thread. Another thread doing more or less random work does not disturb it.

    Think of a card table where every player gets a deck shuffled from the dealer's deck at the moment they sit down. A new player joining another table changes nothing for you. But if your own dealer deals one extra card before your hand, every card you get afterwards is different. UVM adds one more rule on top: it reshuffles each component's phase thread and each sequence and item using a hash of its name, so even the order in which players sit down stops mattering.

    How one number becomes every random value

    Flowchart: the simulator seed sets uvm_global_random_seed. That seeds each component phase thread by phase type and full name, and each sequence and item by type, full name and count. The test's run_phase thread seeds objects created in it, such as dma_job_cfg; the packet sequence and its items get their own name-based seeds.
    The simulator seed decides UVM's global seed. From it, UVM seeds every component phase thread from the phase and the component's full name, and every sequence or item from its type, full name and a per-name counter. Objects that UVM does not reseed take their seed from the thread that creates them.

    Build it: the library code and the experiments

    Where UVM seeds

    Three places in the library do the work. Read them once and the rules follow:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // snippet: from the UVM library source (1800.2-2020), shortened
    int unsigned uvm_global_random_seed = $urandom;          // uvm_misc.svh: set once from the simulator seed
    
    // uvm_task_phase.svh: every component's run_phase (and other task phases) runs in its own thread
    proc = process::self();
    proc.srandom(uvm_create_random_seed(phase.get_type_name(), comp.get_full_name()));
    
    // uvm_sequence_item.svh, set_item_context(): called when a sequence or item is started
    reseed();   // if use_uvm_seeding: srandom(uvm_create_random_seed(get_type_name(), get_full_name()))
    
    // uvm_create_random_seed(type_id, inst_id): a hash of type and instance name with the global
    // seed, plus a counter that increments each time the same name asks again.
    • A component's run_phase thread is seeded from its full name, not from its position in the tree. Adding a sibling component cannot move it.

    • A sequence or item is reseeded from its type, full name (sequencer path, sequence name, item name) and a counter. The k-th item named it in pkt on env.in_agt.sqr always gets the same seed.

    • A plain object such as dma_job_cfg is never reseeded by UVM. It takes its seed from the thread that constructs it, so anything else that thread does first, a $urandom or even constructing another object, moves it.

    The experiments

    The scratch tests extend the bench's dma_rand_test (three random jobs) and add a subscriber that prints the first data word of each packet. One adds two components that draw random numbers of their own; one draws a single $urandom before the jobs; one starts the same packet sequence under another instance name.

    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
    // snippet: scratch run alongside the DMA bench
    class s_seed_more_test extends s_seed_test;            // s_seed_test = dma_rand_test + the word printer
      `uvm_component_utils(s_seed_more_test)
      s_noise noise;                                       // its run_phase calls $urandom and randomize()
      dma_activity_watchdog wd;
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        noise = s_noise::type_id::create("noise", this);
        wd    = dma_activity_watchdog::type_id::create("wd", this);
      endfunction
    endclass
    
    class s_seed_call_test extends s_seed_test;
      `uvm_component_utils(s_seed_call_test)
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
      task run_phase(uvm_phase phase);
        void'($urandom);                                   // one extra draw in the test's own thread
        super.run_phase(phase);
      endtask
    endclass

    Watch it run: what moved and what did not

    All runs are scratch runs on Verilator with the 1800.2 library. The baseline is exactly the bench's dma_rand_test log:

    Diagram
    UVM_INFO dma_tb.sv(731) @ 0: uvm_test_top [JOB] key=90fdd8fd len=10 irq_en=1
    UVM_INFO dma_tb.sv(731) @ 645000: uvm_test_top [JOB] key=0e564b57 len=1 irq_en=1
    UVM_INFO dma_tb.sv(731) @ 885000: uvm_test_top [JOB] key=5a1793f4 len=3 irq_en=1
    UVM_INFO adv_tb.sv(286) @ 1305000: uvm_test_top.words [WORDS] first word of each packet: 1364cd0b 194e3e45 6114f42c

    Change

    Jobs (key, len)

    First data words

    Why

    Two extra components with their own randomness

    unchanged

    unchanged

    The test thread and the items are seeded by name.

    One $urandom at the top of the test's run_phase

    all three changed (7ee9766a len 12, ...)

    first unchanged (1364cd0b), then different

    The job objects come from the test thread. Item seeds did not change, but longer packets shift the per-name counter.

    One debug reg_read at the top of the test's run_phase

    all three changed (8cc3a093 len 7, ...)

    first unchanged, then different

    The read constructs an apb_rw_seq in the test thread.

    Delete the config_db::get from the test's loop

    all three changed (74a8151c len 8, ...)

    first unchanged, then different

    The library constructs objects inside get, in the test thread.

    A printed uvm_info, a filtered one, or a $display at the top

    unchanged

    unchanged

    No change observed on this tool, although uvm_info does construct a message object (see below).

    Packet sequence named stream instead of pkt

    unchanged

    all changed (a96fbf88 ...)

    Item full names changed, so their seeds did.

    uvm_object::use_uvm_seeding = 0

    unchanged

    all changed (cb51a68f ...)

    Items now take their seed from the creating thread.

    +verilator+seed+5

    all changed (6d806e0d len 7, ...)

    all changed

    The global seed went from 4133899051 to 2516928731.

    Verilator matched UVM's model in every case here. Commercial simulators implement the IEEE 1800 stability rules too, but the actual values for a given seed differ between tools and versions: a seed replays on the same tool, version and build, not across them.

    Replaying a failing seed

    A seed alone reproduces nothing. Reproduction needs the same compiled bench, the same command line and the same seed on the same tool version. Keep a replay record with every failure: the simulator seed as the tool printed it, the full command line (+UVM_DUMP_CMDLINE_ARGS), the source revision and compile defines, and the tool and UVM library versions.

    The replay record can also hold the random state itself. Every class object and thread has one: string s = obj.get_randstate(); captures it, and obj.set_randstate(s); restores it, so a debug run can snapshot a sequence's state just before the failing job, then replay only that job with extra prints, without re-running everything before it. It is per object or thread, not global, and only valid on the same tool and build that produced it.

    When you debug the replay, keep edits out of the stimulus thread. Verbosity plusargs and new components or subscribers are safe, because UVM seeds them by name. Inside the thread, anything that constructs an object can move later values: the debug register read did. A printed uvm_info left the jobs unchanged on Verilator, but it constructs a report message object, and the IEEE 1800 object-stability rule seeds every new object from its thread, so do not rely on that across simulators; $display constructs nothing. Capture the values you need from the failing log before you edit.

    Tool

    Seed switch (check your version's manual)

    Siemens Questa

    vsim -sv_seed <n>, or -sv_seed random (the chosen value is printed in the log)

    Cadence Xcelium

    xrun -svseed <n>, or -svseed random

    Synopsys VCS

    +ntb_random_seed=<n> at run time, or +ntb_random_seed_automatic

    Verilator (this course)

    +verilator+seed+<n> at run time; without it, runs repeat exactly

    Seed policy in a regression

    • Fixed seeds for gates, random seeds for discovery. Smoke and merge-gate runs use a fixed list of seeds so a red result means the code changed. Nightly runs draw fresh seeds to find new bugs.

    • Record the seed the tool chose, not the one you meant to pass. With random the simulator picks; grep the value from the log into the result database.

    • Rerun failures with the same seed first, then with a handful of new seeds. A failure that appears on one seed in a thousand is still a bug; one that disappears on rerun with the same seed means the replay record is incomplete.

    • Name repeated starts distinctly. With one shared name, each start's seed depends on how many starts with that name came before it, so inserting one shifts all the rest.

    • Randomize stimulus in sequences and items, which UVM reseeds by name, rather than in plain objects in a long-lived test thread: the experiments above show which one survives an edit.

    Note

    In IEEE 1800.2: the seeding is the same as in UVM 1.2. The standard adds uvm_coreservice_t::get().get_uvm_seeding() and set_uvm_seeding(); the 1.2-style static uvm_object::use_uvm_seeding is kept for compatibility and is what those calls read and write.

    What breaks: the debug edit that hid the bug

    This is the hook, and it happens on real teams every month. A failing seed is rerun, it fails, and an engineer adds a few lines to the test's run_phase to dump state: a temporary object, a randomized helper, a $urandom to pick which register to read. The rerun passes. The conclusion "it was a flaky failure" goes into the ticket, and the bug ships.

    The scratch runs show the mechanism. One debug register read at the top of the test turned job 1 from len=10 into len=7 and changed every key; one extra $urandom, or deleting one config_db::get, did the same. The bug needed the original stimulus, and the edit removed it. The fix is a habit: debug a replay only with edits that draw nothing from the stimulus thread, and if you must change the thread, first save the failing transactions (keys, lengths, data) from the log and drive them with a directed test.

    Interview

    "You add a coverage collector to the environment and some failing seeds start passing. Is that expected?"

    A weak answer: "Yes, adding anything to the testbench changes the random sequence." That is the pre-UVM intuition, and it is mostly wrong for a UVM bench.

    A strong answer: "Not if the collector only adds a component. UVM seeds each component's phase threads from the phase and the full name, and reseeds every sequence and item from its type, full name and a counter, so a new sibling does not move anyone's stimulus. If seeds changed, something consumed randomness in an existing thread: the collector's code runs in the test's thread, it constructs objects there, it renamed a sequence, or someone turned use_uvm_seeding off. I'd diff the first random values of a known seed before and after, then look for the thread that drew more."

    Follow-ups: What does a seed need to replay on another machine? (the same build, command line and tool version) and Why do items keep their data when a test's job lengths change? (items are reseeded by name and count, not from the test's thread).

    Check yourself A test starts the same axis_pkt_seq named pkt three times in a loop. A colleague renames it to $sformatf("pkt%0d", j) for clearer logs. What happens to the data words, and is it a problem?

    The items' full names change, so their seeds change and every data word differs for the same seed. That is not a bug, but every recorded failing seed from before the rename now produces different stimulus. Make such changes deliberately, and re-baseline the fixed gate seeds afterwards.

    Check yourself A failing seed replays on the engineer's machine but not in the regression farm, with the same source revision. List three things to compare.

    The simulator version (values for a seed are tool and version specific), the compile options and defines (a different build is a different program), and the full command line, including plusargs that change which code runs. Also check that the farm really used the recorded seed, not random.

    Check yourself Why does dma_job_cfg's randomization move when the test thread draws one more random number, while axis_item data does not?

    dma_job_cfg is a plain object created and randomized in the test's run_phase thread; it takes its seed from that thread's RNG at construction, so an extra draw shifts it. Items are reseeded by set_item_context when they are started, from a hash of their type and full name plus a counter, independent of the creating thread.

    Next: a seed replays only on the same library. UVM 1.2 to IEEE 1800.2: the delta table lists what changes when the library does.

    Related topics

    • Constraints and randomize() with

    • The replay bundle

    • Debug toolkit

    Previous

    171 / 280

    Next: UVM 1.2 to IEEE 1800.2: The Delta Table

    Up next

    Up nextUVM 1.2 to IEEE 1800.2: The Delta Table

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Callbacks Done Right
    • Performance at Scale