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()
    • Constraint Operators & Expressions
    • Dynamic Constraint Control
    • Randomizing Arrays and Queues
    • How the Constraint Solver Works
    • Debugging Randomization Failures
      • Randomization Failure Taxonomy
      • Finding Contradictions
      • Wrong Distribution Debugging
      • The Randomization Debug Checklist
    • 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

‹Debugging Randomization Failures
  1. Topics
  2. SystemVerilog
  3. Constraint Randomization — Interview-Critical Deep Dive
  4. Debugging Randomization Failures
  5. The Randomization Debug Checklist

Part 3 · Constraint Randomization · Intermediate

The Randomization Debug Checklist

Seed replay, the silent trio, loud wrappers, and the ten-step debug procedure.

On this page

    Why this matters

    A DMA test failed on a few seeds, mid-test, for a week. The cause was a rand_mode(0) left over from an earlier phase, found in an hour once the seed was replayed and the object dumped.

    After this lesson you can: replay a failing seed, check the three silent causes, make every failure report itself, and follow a ten-step procedure to a fix.

    Reproduce before debugging

    A random failure without a seed is folklore. The highest-value habit is printing the seed at the top of every log, unconditionally, and recording test, seed, simulator version, and commit hash per regression row.

    Replay means the same binary, options, and seed reproduce bit-identical streams. Debug edits that add object construction or thread draws upstream of the failure shift streams and lose the repro.

    A regression runs many seeds and logs each one. A failing seed is replayed with the same binary and options; if the failure repeats, debug it deterministically, otherwise find what broke stability first.
    Seed capture and replay.

    The fields below are unconstrained, so no constraint solver is needed. The next listing runs under Verilator in this course's checks.

    top.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
    class txn;
      rand bit [31:0] addr;
    endclass
    
    module top;
      initial begin
        txn a, b;
        bit [31:0] first;
        int unsigned seed;
        if (!$value$plusargs("SEED=%d", seed)) seed = 1;
        $display("SEED: %0d (replay with the same value)", seed);
        // Manual seeding detaches an object from positional seeding.
        // srandom works right after construction, or inside new()
        // (the LRM's own srandom example seeds in the constructor).
        a = new();
        a.srandom(32'hDEAD_BEEF);
        if (!a.randomize()) $fatal(1, "FAIL: a.randomize");
        first = a.addr;
        b = new();                 // an extra construction upstream...
        b.srandom(32'h1234);
        a.srandom(32'hDEAD_BEEF);  // ...cannot move a re-anchored object
        if (!a.randomize()) $fatal(1, "FAIL: a.randomize");
        if (a.addr !== first) $fatal(1, "FAIL: srandom did not pin a");
        $display("PASS: the same srandom seed gives a the same addr");
        $finish;
      end
    endmodule
    Terminal
    SEED: 1 (replay with the same value)
    PASS: the same srandom seed gives a the same addr

    Adding a display call is replay-safe because it draws nothing. Adding construction or sampling calls upstream is not, so pin the suspect object with srandom while debugging around it.

    The silent trio

    The worst bugs return 1. Three language rules produce most silent failures: a null nested handle is skipped without warning, rand_mode(0) persists on the object, and randc cycle state interacts with mid-cycle state changes.

    The module below randomizes only txn2, which has no constraints, so it runs under Verilator in this course's checks without a constraint solver.

    top.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
    class payload;
      rand bit [7:0] data[];
      constraint sz_c { data.size() inside {[1:64]}; }
    endclass
    
    class frame;
      rand bit [7:0] hdr;
      rand payload   pl;
      function new();
        pl = new();   // without this, randomize silently skips pl
      endfunction
    endclass
    
    class txn2;
      rand bit [31:0] addr;
      rand bit [7:0]  len;
    endclass
    
    module top;
      initial begin
        txn2 t;
        t = new();
        // rand_mode(0) is sticky: addr becomes state until re-enabled.
        t.addr = 32'h4000_0000;
        t.addr.rand_mode(0);
        repeat (100) if (!t.randomize()) $fatal(1, "FAIL: txn2 randomize");
        // ... phases later, addr is still pinned. Audit at boundaries:
        if (t.addr !== 32'h4000_0000) $fatal(1, "FAIL: addr moved while rand_mode was 0");
        if (t.addr.rand_mode() == 0)
          $display("addr rand_mode is OFF entering the next phase");
        t.addr.rand_mode(1);
        if (t.addr.rand_mode() != 1) $fatal(1, "FAIL: rand_mode(1) did not re-enable addr");
        $display("PASS: rand_mode(0) held addr for 100 calls until re-enabled");
        $finish;
      end
    endmodule
    Terminal
    addr rand_mode is OFF entering the next phase
    PASS: rand_mode(0) held addr for 100 calls until re-enabled

    A randc field cycles per object, and constraints filter the cycle. Treat any state change touching a randc field's constraints as invalidating the cycle, and build explicit shuffled queues where exact permutation order matters.

    Make failure loud

    A fatal wrapper with an object dump turns every failure into a self-contained repro report. The dump prints the object's fields through convert2string, which shows the live state values, and the seed completes the kit.

    A post_randomize validity check guards the output side. It catches disabled constraints and mode leftovers regardless of which silent mechanism produced them.

    The bench below wraps the call in a plain if, so it still runs when assertions are disabled, and builds the report from convert2string. A first call solves; then the test sets a state bit that makes the same request impossible.

    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
    class dma_desc;
      rand bit [12:0] nbytes;
      bit             small_ring;                 // state, set by the test
      int             post_calls;
      constraint ring_c { small_ring -> nbytes <= 1024; }
      function void post_randomize();
        post_calls++;
      endfunction
      virtual function string convert2string();
        return $sformatf("nbytes=%0d small_ring=%0d", nbytes, small_ring);
      endfunction
    endclass
    
    // A plain if, so it still runs when assertions are disabled.
    function automatic bit randomize_or_report(string who, dma_desc d, output string report);
      if (d.randomize() with { nbytes >= 4000; }) return 1;
      report = $sformatf("RANDOMIZE REPORT (%s): %s", who, d.convert2string());
      return 0;
    endfunction
    
    module tb_loud;
      dma_desc d = new();
      string report;
      bit [12:0] prev_nbytes;
      initial begin
        if (!randomize_or_report("seq0", d, report)) $fatal(1, "FAIL: first call should solve");
        prev_nbytes = d.nbytes;
        d.small_ring = 1;                          // the state change that breaks the next call
        if (randomize_or_report("seq1", d, report)) $fatal(1, "FAIL: second call should fail");
        $display("%s", report);
        $display("nbytes after the failure: %0d (the value from the earlier success: %0d)", d.nbytes, prev_nbytes);
        $display("post_randomize ran %0d times for 1 success and 1 failure", d.post_calls);
        if (d.nbytes != prev_nbytes) $fatal(1, "FAIL: a failed call changed a rand field");
        $display("PASS: the report names the caller and the state, and the rand field kept its stale value");
        $finish;
      end
    endmodule
    Terminal
    %Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-K1WcxI/t52.sv:5: Unsatisfied constraint: 'constraint ring_c { small_ring -> nbytes <= 1024; }'
    %Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-K1WcxI/t52.sv:16: Unsatisfied constraint: 'if (d.randomize() with { nbytes >= 4000; }) return 1;'
    RANDOMIZE REPORT (seq1): nbytes=5294 small_ring=1
    nbytes after the failure: 5294 (the value from the earlier success: 5294)
    post_randomize ran 2 times for 1 success and 1 failure
    PASS: the report names the caller and the state, and the rand field kept its stale value

    Three things in that output matter in a real debug. The report names the caller and the live state, small_ring=1, which is the cause. The rand field shows the stale value from the earlier success, not anything the failed call chose. And post_randomize ran twice: Verilator 5.048 also runs it after a failed call, so a check inside it cannot tell a fresh result from a stale one unless the caller passes the return value along.

    The checklist and a war story

    Everything compresses into one ordered procedure. Each step either resolves the problem or narrows the next step's search space.

    1. Confirm the failure is visible: check the return value first, with a fatal wrapper if the call is bare.

    2. Classify the symptom: return 0 means contradiction, unchanged fields mean silent skip, skew means distribution, and intermittent means seed.

    3. Lock the repro: replay the exact test, seed, and build triple and confirm a bit-identical failure.

    4. Dump the solver inputs: print the object state at the failure site, including mode getter status.

    5. For return 0, isolate: read the tool's UNSAT report, run a deletion filter over named blocks, and pin suspects with an inline with.

    6. For return 0, corner it: dump the solver core on the reduced case and build a minimal standalone repro.

    7. For wrong values, check the silent trio first, then histogram the distribution over many calls.

    8. For skew, apply solver semantics: count solutions, check ordering, look for eaten soft defaults and clipped ranges.

    9. Fix at the right layer: state bugs in the test, intent conflicts via soft, disable, or subclass, never by deleting legality.

    10. Harden: leave a fatal wrapper, a post_randomize check, and coverage on the affected fields behind.

    A DMA descriptor fails on a few seeds, always mid-test. Replay locks it, the object dump shows a small ring level as state, and hand-solving finds a leftover rand_mode(0) pinning the wrap flag from an earlier phase.

    Steps

    What happened

    1 and 2

    The return value was checked: 0, so the contradiction branch. It failed on only some seeds, so a state dependency too.

    3

    Replaying seed 8841 failed at the same iteration every time.

    4

    The object dump showed the rand fields at their previous values, because failed calls assign nothing, and the state ring_bytes_left at 1492.

    5

    Hand-solving: the inline nbytes >= 4096, the ring rule with the wrap flag pinned to 1, and 1492 bytes left give an empty set. Switching off the ring block let the call solve: core found.

    9

    The leftover rand_mode(0) was fixed at the phase boundary, not in the legality rule and not in the test's intent.

    10

    Left behind: the loud wrapper, a cross-check in post_randomize, a mode audit at phase boundaries, and a coverage bin.

    Common pitfall

    Declaring victory when the rerun passes. Without hardening, the same failure class returns silently. Misreading the post-failure dump is the matching trap: rand fields show stale values, only state values are current.

    Interview

    Deliver the four beats, not ten numbered steps: verify and classify, lock the repro, isolate with the named tools, then fix at the right layer and harden. Close with one concrete war story, because a specific debugged failure beats enumerated theory.

    Check yourself The rerun passes after your fix. Are you done?

    No. The exit criterion is hardening: a loud wrapper, a validity check, and coverage so the failure class cannot hide again.

    Check yourself The dump after a failure shows nbytes 7300. Is 7300 the problem?

    Not necessarily. Failed calls assign nothing, so rand fields show previous values. Chase the live state values first.

    Remember

    • Log the seed on line one of every run.

    • Null handles, sticky rand_mode and randc cycle state fail silently.

    • A loud wrapper prints the caller and the state, then stops.

    Rule of thumb: a fix is done when the failure class can no longer hide.

    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.

    Related topics

    • Randomization Failure Taxonomy

    • Wrong Distribution Debugging

    Previous

    91 / 259

    Next: Interview Drills — Constraints and Randomization Q&A

    Up next

    Up nextInterview Drills — Constraints and Randomization Q&A

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Randomization Failure Taxonomy
    • Finding Contradictions