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. Null Handles and Object Arrays
On this page

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

    Null Handles and Object Arrays

    Nested rand objects, null handles that are silently skipped, and arrays of objects built before the solve.

    A nested config object was never built, so its fields never changed. randomize() returned 1 every time, and the crash came thousands of cycles later in the scoreboard.

    After this lesson you can: build nested rand objects in new(), explain why a null handle is skipped, and build object arrays before the solve.

    The previous lesson showed that one solve covers a whole tree of objects. This lesson is about what the solver will not do for you: it never builds an object, and it skips a handle that is null.

    Null handles and object arrays

    A rand class handle is never re-pointed by the solver. randomize recurses into the referenced object and folds its fields and constraints into the one flattened solve.

    Outer constraints may reference inner fields directly. The header and packet below are co-determined in a single solve, not randomized in two passes.

    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
    class header;
      rand bit [3:0] version;
      rand bit [7:0] len;
      constraint c_ver { version inside {4, 6}; }
    endclass
    
    class packet;
      rand header   h;             // rand handle: the solver recurses
      rand bit [7:0] data[];
    
      // cross-object constraint: outer references inner field
      constraint c_size { data.size() == h.len; }
    
      function new();
        h = new();                 // construct in new: never null
      endfunction
    endclass
    
    module top;
      packet p = new();
      initial begin
        assert (p.randomize());
        // ONE solve chose version, len, data size and contents,
        // with data.size() == h.len holding by construction
        if (p.data.size() != p.h.len || !(p.h.version inside {4, 6}))
          $fatal(1, "FAIL: size=%0d len=%0d version=%0d",
                 p.data.size(), p.h.len, p.h.version);
        $display("PASS: data.size() == h.len == %0d after one solve", p.h.len);
        $finish;
      end
    endmodule
    Terminal
    PASS: data.size() == h.len == 29 after one solve

    A null rand handle is silently skipped, and randomize still returns 1. Nothing forces an error unless a constraint dereferences the handle, so the bug surfaces far from its cause.

    A forgotten new() is silent at randomize time and loud much later.
    1. Prevent it: construct every nested rand object in new().

    2. Catch it early: check the handle in pre_randomize(), as the bench below does.

    3. Model optional parts with an enable bit and if constraints, never with a handle that is sometimes null.

    Verilator 5.048 follows this only when the handle's class has no constraints of its own. When it has some, the run stops with "Null pointer dereferenced" at the randomize() call: louder, and closer to the cause, but not what the standard says. Build every rand handle in new() and both tools agree.

    Skipping instead of erroring exists for genuinely optional sub-objects. The cost is that a forgotten construction looks identical, so null should mean bug, never mode.

    Arrays of objects follow the same rule: the solver randomizes fields of every non-null element but never constructs elements. Size the array procedurally, construct each element, then randomize.

    A rand dynamic array can still have its size solved under constraints. Only object creation is off limits to the 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
    // not-run: Verilator 5.048 fails to build a constraint over an array of class handles (C++ compile error)
    class beat;
      rand bit [31:0] data;
      rand bit        last;
    endclass
    
    class burst;
      rand beat beats[];           // array of handles
      rand bit [3:0] blen;
    
      constraint c_last {          // only the final beat has last == 1
        foreach (beats[i])
          beats[i].last == (i == beats.size() - 1);
      }
    
      // Size and construct in a controlled step BEFORE the solve:
      function void build(int n);
        beats = new[n];
        foreach (beats[i]) beats[i] = new();
      endfunction
    endclass
    
    module top;
      burst b = new();
      initial begin
        b.build(8);                  // 1. size plus construct every element
        assert (b.randomize());      // 2. one solve: all beats data and last bits
      end
    endmodule

    With eight constructed elements, the foreach expands to eight equality constraints in the flattened problem. A null element would be skipped while the constraint dereferencing it blows up instead.

    top.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // snippet: b is the burst object above
    // When the element COUNT itself should be random, use two passes:
    module top;
      int n;
      initial begin
        assert (std::randomize(n) with { n inside {[1:16]}; });   // pass 1: size
        b.build(n);                                               // construct
        assert (b.randomize());                                   // pass 2: fields
      end
    endmodule
    // Trade-off: constraints cannot couple n with beat contents in one
    // solve. The solver never creates objects, so this split is the price.

    Testbench

    The null defense pinned into pre_randomize: the handle is built in new(), checked before the solve, and still there after it.

    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
    class config_c;
      rand bit [2:0] qos;
    endclass
    
    class txn_checked;
      rand config_c cfg;
      rand bit [7:0] addr;
      function new();
        cfg = new();
      endfunction
      function void pre_randomize();
        if (cfg == null) $fatal(1, "FAIL: cfg is null before the solve");
      endfunction
    endclass
    
    module tb_pre_null;
      txn_checked t = new();
      initial begin
        if (!t.randomize()) $fatal(1, "FAIL: randomize failed");
        if (t.cfg == null) $fatal(1, "FAIL: cfg null after a successful solve");
        $display("PASS: cfg was constructed in new() and survived the solve");
        $finish;
      end
    endmodule
    Terminal
    PASS: cfg was constructed in new() and survived the solve

    A rand handle that is null is skipped and randomize still returns 1. Build every nested object in new(), and put a null check in pre_randomize while the class is young.

    Nested-object questions come as short scenarios.

    • A null rand handle is skipped: the call returns 1 and the inner fields never change.

    • Nested rand objects join one flattened solve, so an outer constraint can use an inner field.

    • The solver sizes a dynamic array but never constructs its elements: build them first, then randomize.

    Check yourself An outer constraint references an inner object's field. How many solver calls produce the pair?

    One. Nested rand objects join a single flattened solve, so the cross-object rule holds by construction.

    Check yourself When is a null rand handle the right model for an optional sub-object?

    Never. A null handle is silently skipped, which is indistinguishable from a forgotten construction. Use a non-rand enable bit with if-constraints.

    • A rand handle left null is skipped and the call still returns 1.

    • Nested rand objects join one flattened solve, so constraints can span them.

    • The solver never calls new(): construct every array element first.

    Rule of thumb: construct every nested rand object in new().

    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: a constraint over an array of class handles; Verilator 5.048 ignored each element's own constraints in our probe.

    Related topics

    • Next: Random Stability and Seeding

    Previous

    68 / 275

    Next: Random Stability and Seeding

    Up next

    Up nextRandom Stability and Seeding

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • rand vs randc
    • The randomize() Call
    • Choosing a Randomize Tool