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

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here5
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive39
    • Randomization Basics: rand, randc & randomize()
      • rand vs randc
      • The randomize() Call
      • Choosing a Randomize Tool
      • Hooks and Nested Objects
      • Null Handles and Object Arrays
      • 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 Dive41
  • Functional Coverage in SystemVerilog25
  • Testbench Architecture in SystemVerilog24
  • Advanced Language & Integration26
  • Senior & Interview Preparation23
  • Principal-Level Verification34
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. The randomize() Call
On this page

    Part 3 of 9, lesson 2 of 39Part 3 of 9/Constraint Randomization/Lesson 2 of 39Intermediate

    The randomize() Call

    The randomize contract, return checks, what gets solved, check-only mode and field lists.

    The design received the same packet twice. A randomize() call had failed, nobody checked its return value, and the test sent the previous packet again.

    After this lesson you can: check every randomize() call, say what a failed solve does to the object, and re-roll one field or check values with a field list.

    A solver call with a contract

    Every class implicitly gets a built-in randomize method. Its contract is precise: gather every active constraint, solve for every enabled rand field, then either commit a complete legal solution and return 1, or commit nothing and return 0.

    There is no partial success. The solve is atomic across all fields, which is why the return value is load-bearing.

    design.sv
    // snippet: the idiom, for any object t
    if (!t.randomize())
      $fatal(1, "randomize failed: constraints contradict");

    On success the solver found an addr and len pair satisfying all three constraint expressions at once. On failure the constraint set was unsatisfiable, and addr and len keep whatever values they held before the call.

    Verilator, for one, prints a WIDTHTRUNC warning for !t.randomize(), because randomize() returns an int; the warning is harmless.

    Some lint flows flag a discarded randomize return. The rule does not need the warning: check the return on every call regardless.

    Call

    Result

    What t holds

    1. t.randomize()

    Returns 1

    A new legal packet

    2. t.randomize()

    Returns 1

    Another new legal packet

    3. t.randomize() with {addr == 0;}, contradicting c_addr

    Returns 0, unchecked

    Still packet 2, so the test sends packet 2 again

    No error, no crash: the design simply receives a duplicate, and coverage counts a transaction that tested nothing new.

    What gets solved

    The solve set is every rand or randc field with rand mode on, recursing into any non-null rand class handle. Non-rand fields are state variables: the solver reads them but never writes them.

    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
    // not-run: Verilator 5.048 cannot constrain size() of a queue inside a nested rand object (it treats it as state), so randomize() fails
    class payload;
      rand bit [7:0] bytes_q[$];
      constraint c_sz { bytes_q.size() inside {[1:16]}; }  // without it the queue is not resized
    endclass
    
    class frame;
      rand bit [15:0] id;        // solved
      bit  [3:0]      rev;       // NOT rand: read-only state for the solver
      rand payload    pl;        // non-null: the solver recurses into bytes_q
      payload         dbg;       // not rand: never touched, even if non-null
    
      constraint c_rev { id[15:12] == rev; }  // constraining rand BY state
    endclass
    
    module top;
      frame f = new();
      initial begin
        f.pl  = new();             // construct before randomize
        f.rev = 4'h3;
        assert (f.randomize());
        // solver wrote: f.id with id[15:12]==3, plus f.pl.bytes_q
        // solver read : f.rev
        // untouched   : f.dbg
      end
    endmodule

    With rev held at 3, the top nibble of id is forced to 3. The solver descends into pl because pl is rand and non-null, sizing and filling the queue in the same global solve. Without c_sz the queue would not be resized: IEEE 1800 18.4 says an array whose size is not constrained keeps its size, so an empty queue stays empty.

    dbg stays invisible to the solver regardless of its contents.

    Check-only mode and partial solve

    Passing null as the argument list turns the call into a checker. No field changes, and the return value reports whether the current values satisfy all active constraints.

    top.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // not-run: fragment (bus_txn is the class in the testbench below); Verilator 5.048 also rejects randomize(null)
    module top;
      bus_txn t = new();
      initial begin
        // Hand-build a transaction, e.g. replaying a captured trace
        t.addr = 32'h0000_1404;
        t.len  = 4;
    
        if (t.randomize(null))
          $display("trace txn is legal per class constraints");
        else
          $error("trace txn VIOLATES constraints");
    
        // Also useful after mutating one field of a random txn:
        t.len = 200;                       // outside [1:16]
        assert (!t.randomize(null));        // correctly reports illegal
      end
    endmodule

    The solver pins every rand field to its current value and asks whether that exact point lies inside the solution space. Nothing is written either way, which also makes it a debug tool for isolating contradictory combinations.

    Calling t.randomize(addr) restricts the solve set to the listed variables. Only addr is treated as random; every other field, including other rand fields, is frozen at its current value as state.

    top.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // not-run: fragment (bus_txn is the class in the testbench below); Verilator 5.048 also re-randomizes len in t.randomize(addr), so the last assert fails
    module top;
      bus_txn t = new();
      bit [7:0] keep_len;
      initial begin
        assert (t.randomize());            // full solve: addr and len
        keep_len = t.len;
    
        assert (t.randomize(addr));        // re-roll ONLY addr
        // len is frozen at keep_len; the solver picks a new addr that still
        // satisfies c_addr and c_len with that fixed len
    
        assert (t.len == keep_len);        // holds: len was not in the solve set
      end
    endmodule

    Not every simulator follows this yet: one of the simulators used here also re-randomized len in t.randomize(addr). Where a field must stay put in this course's runnable listings, it is frozen with rand_mode(0) instead, which the dynamic-control topic teaches.

    Two semantics matter here. A listed variable is randomized even if it is not declared rand, and this is the clean fix for randc slot burning.

    Constraints are never relaxed; only the set of free variables changes.

    Testbench

    Predict first The bench below finishes with t.randomize() with { addr == 0; }, which contradicts c_addr. What does the call return, and what happens to addr and len?

    It returns 0, and both fields keep the values from the previous successful call. A failed solve commits nothing.

    A hundred checked solves, then one contradiction that must leave the object untouched.

    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
    class bus_txn;
      rand bit [31:0] addr;
      rand bit [7:0]  len;
      constraint c_addr { addr inside {[32'h0000_1000 : 32'h0000_1FFF]}; }
      constraint c_len  { len inside {[1:16]}; addr[1:0] == 0; }
    endclass
    
    module tb_contract;
      bus_txn t = new();
      bit [31:0] keep_addr;
      bit [7:0]  keep_len;
      int ok;
      initial begin
        // 1. The idiom: check every call. A success commits a legal pair.
        repeat (100) begin
          if (!t.randomize()) $fatal(1, "FAIL: randomize failed");
          if (t.addr < 32'h1000 || t.addr > 32'h1FFF || t.addr[1:0] != 0 || t.len < 1 || t.len > 16)
            $fatal(1, "FAIL: illegal txn addr=%h len=%0d", t.addr, t.len);
        end
        // 2. A contradiction returns 0 and commits nothing.
        keep_addr = t.addr;
        keep_len  = t.len;
        ok = t.randomize() with { addr == 0; };
        $display("contradiction: randomize returned %0d, addr %h -> %h, len %0d -> %0d", ok, keep_addr, t.addr, keep_len, t.len);
        if (ok != 0 || t.addr != keep_addr || t.len != keep_len) $fatal(1, "FAIL: a failed solve changed the object");
        $display("PASS: 100 legal solves, and a failed solve left the object untouched");
        $finish;
      end
    endmodule
    Terminal
    %Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t115.sv:4: Unsatisfied constraint: 'constraint c_addr { addr inside {[32'h0000_1000 : 32'h0000_1FFF]}; }'
    %Warning-UNSATCONSTR: /var/folders/41/dgs80y1569b4x8pwflv_w9jr0000gn/T/rtbtb-ZYw5Pz/t115.sv:23: Unsatisfied constraint: 'ok = t.randomize() with { addr == 0; };'
    contradiction: randomize returned 0, addr 0000142c -> 0000142c, len 9 -> 9
    PASS: 100 legal solves, and a failed solve left the object untouched

    Discarding the return value re-sends stale transactions with no error anywhere. And where assertion elision can remove calls, prefer the safe form: if (!t.randomize()) $error(...).

    Expect contract questions that separate the call shapes.

    • randomize returns 1 or 0, and on failure all fields keep prior values: the solve is atomic.

    • A hand-built transaction is validated without changes by randomize with a null argument.

    • One field is re-rolled while the rest freeze by naming it in the argument list.

    • A listed variable is randomized even when it is not declared rand.

    • randomize is a built-in method that cannot be overridden; pre and post hooks carry the customization.

    Check yourself randomize with a field list names a non-rand field. Is that field solved?

    Yes. The argument list overrides the declaration for that call: listed variables are randomized, and every other rand field freezes as state.

    Check yourself Why is assert(t.randomize()) unsafe in flows with aggressive assertion elision?

    Elision can remove the assert together with its call, so randomize never runs. The if (!t.randomize()) $error form keeps the call on every flow.

    • randomize() returns 1 with every field updated, or 0 with nothing changed.

    • Plain (non-rand) fields are read by the solver and never written.

    • randomize(addr) re-rolls only the listed field, and a listed field is solved even if it is not rand.

    Rule of thumb: no randomize() without a check.

    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.

    Not run: randomize(null) is rejected by Verilator 5.048 ("Unsupported"), and its randomize(addr) re-randomized len as well, so the field-list listing is shown but not run.

    Related topics

    • Next: Choosing a Randomize Tool

    Previous

    65 / 275

    Next: Choosing a Randomize Tool

    Up next

    Up nextChoosing a Randomize Tool

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Hooks and Nested Objects
    • Null Handles and Object Arrays
    • Random Stability and Seeding