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 Here6
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive36
    • Randomization Basics: rand, randc & randomize()
      • rand vs randc
      • The randomize() Call
      • Hooks and Nested Objects
      • Random Stability and Seeding
    • Constraint Operators & Expressions
    • Dynamic Constraint Control
    • Randomizing Arrays and Queues
    • How the Constraint Solver Works
    • Debugging Randomization Failures
    • Interview Drills — Constraints and Randomization Q&A
  • SystemVerilog Assertions — Interview-Critical Deep Dive40
  • Functional Coverage in SystemVerilog22
  • Testbench Architecture in SystemVerilog23
  • Advanced Language & Integration25
  • Senior & Interview Preparation21
  • Principal-Level Verification28
Loading lesson

‹Randomization Basics: rand, randc & randomize()
  1. Topics
  2. SystemVerilog
  3. Constraint Randomization — Interview-Critical Deep Dive
  4. Randomization Basics: rand, randc & randomize()
  5. Random Stability and Seeding

Part 3 · Constraint Randomization · Intermediate

Random Stability and Seeding

Seed hierarchy, creation-order shifts, reseeding, stream snapshots and failure replay.

On this page

    Why this matters

    A failing seed stopped failing after one debug line was added. The edit created an object earlier, every later object got a different seed, and the bug went into hiding.

    After this lesson you can: explain how seeds flow to threads and objects, reproduce a failure from its seed, and pin a stream with srandom or a saved randstate.

    A hierarchy of streams

    SystemVerilog defines random stability: values come from a hierarchy of generators, not one global stream. The simulation seed starts the root, each thread gets its own generator seeded at creation, and each object gets one seeded by the constructing thread.

    Every obj.randomize call draws from the object generator. $urandom, $urandom_range and std::randomize draw from the calling thread generator instead.

    Each object and thread draws from its own stream, seeded in creation order.

    One global stream would make any tiny edit shift every later value. The hierarchy localizes the damage: an object stream depends only on the chain of seedings that built it.

    Call

    Draws from

    obj.randomize()

    The object generator, seeded when new() ran

    $urandom and $urandom_range

    The calling thread generator

    std::randomize

    The calling thread generator

    Creation order moves seeds

    Seeds are dealt from the parent generator in creation order. Adding, removing or reordering a thread creation or an object construction shifts the seeds of everything created after it in the same parent.

    Already-created siblings are untouched. Only the later ones change, which is exactly why an unrelated edit can flip a regression result.

    top.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // snippet: monitor_task() stands for any task the debug edit forks
    class pkt;
      rand bit [7:0] addr;
    endclass
    
    module top;
      initial begin
        pkt a, b;             // declare first: an initializer here would run at
                              // time 0, before any statement of this block
        a = new();            // seeded with parent draw #1
        // ---- edit: someone adds a debug fork here ----
        fork
          monitor_task();     // NEW thread: consumes parent draw #2
        join_none
        // ----------------------------------------------
        b = new();            // was draw #2, NOW seeded with draw #3
                              // every b.randomize result changes
        assert (a.randomize());   // UNCHANGED: a was sealed at its new()
        assert (b.randomize());   // different values than before the edit
      end
    endmodule

    The fork consumed one parent draw, so b was constructed with a different seed. The same happens for an inserted new, an extra $urandom call, or reordered declarations.

    • Construct objects and spawn helper threads in a stable order, early, before conditional or debug paths.

    • Give long-lived components a seed derived from their hierarchical name, decoupling them from creation order entirely.

    • Keep debug $urandom calls out of shared threads, or snapshot and restore the stream around them.

    Pinning a stream

    Three manual controls override automatic seeding. srandom reseeds an object or thread generator, and get and set randstate snapshot and restore the full generator state as an opaque string.

    A snapshot captures the mid-stream position, which is finer than a seed. Name-based seeding is the production pattern because hierarchical names survive edits that creation order does not.

    Predict first A debug fork is inserted between a = new() and b = new(). Do a.randomize() results change? Do b.randomize() results?

    a is unchanged: its generator was seeded when it was built. b changes: the fork consumed one draw from the parent, so b was seeded differently.

    The bench checks each control: a reseed replays a draw, a snapshot rewinds one, and a thread reseed replays $urandom.

    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
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    class gen;
      rand bit [15:0] v;
    endclass
    
    module tb_pin_stream;
      gen g = new();
      string s;
      bit [15:0] first, x, again;
      int unsigned u1, u2;
      process p;
      initial begin
        // 1. Deterministic object stream, immune to creation order:
        g.srandom(32'hDEAD_BEEF);
        if (!g.randomize()) $fatal(1, "FAIL: randomize failed");
        first = g.v;
        g.srandom(32'hDEAD_BEEF);          // same seed again
        if (!g.randomize()) $fatal(1, "FAIL: randomize failed");
        if (g.v !== first) $fatal(1, "FAIL: reseed did not replay the draw");
    
        // 2. Snapshot and replay an exact point in the stream:
        s = g.get_randstate();
        if (!g.randomize()) $fatal(1, "FAIL: randomize failed");
        x = g.v;                           // draw X
        g.set_randstate(s);                // rewind the object generator
        if (!g.randomize()) $fatal(1, "FAIL: randomize failed");
        again = g.v;                       // draws X again: identical
        if (again !== x) $fatal(1, "FAIL: set_randstate did not rewind");
    
        // 3. Reseed the current THREAD ($urandom stream):
        p = process::self();
        p.srandom(1234);
        u1 = $urandom;
        p.srandom(1234);
        u2 = $urandom;
        if (u1 !== u2) $fatal(1, "FAIL: thread reseed did not replay $urandom");
    
        // 4. Name-based seeding decouples the stream from creation order:
        //    g.srandom(string_hash("env.agent0.gen"));
        $display("PASS: srandom replayed, set_randstate rewound, thread reseed replayed");
        $finish;
      end
    endmodule
    Terminal
    PASS: srandom replayed, set_randstate rewound, thread reseed replayed

    Reproducing a failure

    Every regression run logs its simulation seed. A failing seed is re-run with the tool flag on unchanged code, which replays the identical streams for deterministic debugging.

    Tool

    Seed flag

    VCS

    +ntb_random_seed=N

    Xcelium

    -svseed N

    Questa

    -sv_seed N

    Verilator

    +verilator+seed+N, given to the built simulation at run time

    1. The nightly regression runs many seeds and logs the seed of every run.

    2. Take the failing seed and rerun it with the flag above on unchanged code: same stimulus, same failure.

    3. Add displays or waves and rerun. If it still fails, debug it.

    4. If it no longer fails, the edit consumed randomness (a new object, thread or $urandom call shifted later seeds). Pin the stream with srandom or a saved randstate, or debug from waves only.

    The right-hand branch is the fork effect from the second section. Passive instrumentation such as waves or displays of existing values is safe, but anything that consumes randomness can erase the reproduction.

    Common pitfall

    Debug edits that construct objects or call $urandom in shared threads shift downstream seeds and the failure evaporates. Reseeding mid-test without recording the seed destroys reproducibility instead of improving it.

    Interview

    Stability questions test whether edits and streams are connected.

    • A display cannot shift seeds by itself, but the edit around it may add an object, thread or $urandom call that does.

    • obj.randomize draws from the object generator seeded at construction; $urandom draws from the calling thread generator.

    • Long-lived components decouple from build order through name-based reseeding of each generator.

    • srandom restarts a stream from a seed; set_randstate restores an exact mid-stream snapshot.

    • Two identical objects built in one thread get different seeds: each new consumes a parent draw.

    Check yourself A failing seed stops failing after instrumentation is added. What most likely happened?

    The instrumentation consumed randomness: a new thread, object construction or $urandom call shifted creation-order seeds downstream. Re-check with passive waves only, or pin the streams first.

    Check yourself What is the difference between srandom and set_randstate?

    srandom restarts a generator from a chosen seed value. set_randstate restores the exact mid-stream position previously captured by get_randstate.

    Remember

    • obj.randomize() draws from the object's own generator; $urandom from the thread's.

    • Generators are seeded in creation order: insert one and every later one shifts.

    • srandom restarts a stream; get_randstate and set_randstate save and restore one.

    Rule of thumb: log every seed, and never let debug code consume randomness.

    How this was checked

    Run with Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown. Verilator prints a harmless WIDTHTRUNC warning for if (!obj.randomize()), because randomize() returns an int, and an UNSATCONSTR warning when a solve fails on purpose. Icarus 13 does not support constraints.

    The Verilator seed flag was checked on 2026-10-18: +verilator+seed+7 gave the same values on two runs and +verilator+seed+8 gave different ones.

    Related topics

    • Next topic: Constraint Operators & Expressions

    Previous

    68 / 259

    Next: Constraint Operators & Expressions

    Up next

    Up nextConstraint Operators & Expressions

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • rand vs randc
    • The randomize() Call