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
      • Constraining Array Size
      • foreach Constraints
      • sum(), Aggregate Constraints
      • unique Constraints
      • Arrays of rand Objects
    • 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

‹Randomizing Arrays and Queues
  1. Topics
  2. SystemVerilog
  3. Constraint Randomization — Interview-Critical Deep Dive
  4. Randomizing Arrays and Queues
  5. Constraining Array Size

Part 3 · Constraint Randomization · Intermediate

Constraining Array Size

How solvers pick array sizes before elements, and why every rand array needs a bound.

On this page

    Why this matters

    A payload test sent empty packets for a week. The array was rand but nothing constrained its size, so the solver never resized it and it stayed at 0.

    After this lesson you can: bound a dynamic array or queue size, tie it to another field, and spot size rules that leave no legal elements.

    Size is part of the random state

    Declaring rand byte data[] makes every element random. The size is random only when a constraint names data.size(): an unconstrained dynamic array keeps its current size (IEEE 1800 18.4), so an empty one stays empty.

    Inside a constraint, arr.size() is an ordinary random expression. It can be bounded, tied to another rand field, or pinned with inside.

    Declaration

    Size random

    Elements random

    Rule

    rand byte data[]

    Only if data.size() is constrained

    Yes

    Constrain and bound data.size()

    rand byte q[$]

    Only if q.size() is constrained

    Yes

    Same rule as dynamic arrays

    rand byte f[8]

    No, part of the type

    Yes

    Constrain values only

    Predict first risky has rand bit [7:0] payload[] and no constraint at all. A fresh object is randomized. What is payload.size() afterwards? And after payload = new[8] and another call?

    0, then 8. Without a size constraint the solver never resizes the array; it only randomizes the elements that exist.

    The bench below runs both classes. risky shows the size staying where it was; safe bounds the payload to 4 to 16 bytes and ties a queue's length to burst_len.

    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
    class risky;
      rand bit [7:0] payload[];                       // no size constraint
    endclass
    
    class safe;
      rand bit [7:0] payload[];
      rand int       lens[$];
      rand bit [3:0] burst_len;
      constraint c_size {
        payload.size() inside {[4:16]};               // hard bounds
        lens.size() == burst_len;                     // size tied to another rand field
        burst_len inside {[1:8]};                     // and still bounded
      }
    endclass
    
    module tb_sizes;
      risky r = new();
      safe  s = new();
      int seen[int];
      initial begin
        if (!r.randomize()) $fatal(1, "FAIL: risky solve failed");
        $display("risky, fresh: size %0d after randomize()", r.payload.size());
        if (r.payload.size() != 0) $fatal(1, "FAIL: an unconstrained array was resized");
        r.payload = new[8];                           // sized by hand
        if (!r.randomize()) $fatal(1, "FAIL: risky solve failed");
        $display("risky, new[8] first: size %0d after randomize()", r.payload.size());
        if (r.payload.size() != 8) $fatal(1, "FAIL: a hand-sized array changed size");
        repeat (500) begin
          if (!s.randomize()) $fatal(1, "FAIL: safe solve failed");
          if (s.payload.size() < 4 || s.payload.size() > 16) $fatal(1, "FAIL: size %0d out of bounds", s.payload.size());
          if (s.lens.size() != s.burst_len) $fatal(1, "FAIL: queue size %0d, burst_len %0d", s.lens.size(), s.burst_len);
          seen[s.payload.size()]++;
        end
        $display("safe: %0d different payload sizes seen in 500 calls", seen.num());
        if (seen.num() != 13) $fatal(1, "FAIL: some legal size never appeared");
        $display("PASS: no size constraint keeps the size; a bounded size covers 4 to 16 and the queue follows burst_len");
        $finish;
      end
    endmodule
    Terminal
    risky, fresh: size 0 after randomize()
    risky, new[8] first: size 8 after randomize()
    safe: 13 different payload sizes seen in 500 calls
    PASS: no size constraint keeps the size; a bounded size covers 4 to 16 and the queue follows burst_len
    Check yourself What does lens.size() == burst_len mean to the solver?

    One more equation over two random integers. The solver picks the length and the queue size together, and every result has them equal.

    Size is solved first

    Element rules cannot be listed until the size is known. IEEE 1800 18.5.8.1 fixes the order: size constraints are solved first, then the foreach constraints, with size() read as a constant inside them.

    That order can make randomize() fail. If the size picked first leaves no legal element values, the call returns 0, even though another size would have worked.

    Sizes 8 to 10 leave no legal element.
    Predict first The class below asks for 4-bit elements equal to twice the size, with the size in 1 to 10. In 300 calls, how many fail under the standard's order, roughly? What did Verilator 5.048 do?

    Under the standard, a size of 8, 9 or 10 picked first fails, so roughly 3 calls in 10. Verilator looked ahead: 0 of 300 failed, and it only ever chose sizes 1 to 7.

    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
    class coupled;
      rand bit [3:0] data[];                          // 4-bit elements: at most 15
      constraint c {
        data.size() inside {[1:10]};
        foreach (data[i]) data[i] == data.size() * 2; // sizes 8, 9 and 10 have no legal element
      }
    endclass
    
    module tb_coupled;
      coupled c = new();
      int sizes[int];
      int unsat = 0;
      initial begin
        repeat (300) begin
          if (!c.randomize()) begin unsat++; continue; end
          foreach (c.data[i])
            if (c.data[i] != c.data.size() * 2) $fatal(1, "FAIL: data[%0d] = %0d at size %0d", i, c.data[i], c.data.size());
          sizes[c.data.size()]++;
        end
        $display("unsatisfied calls: %0d of 300", unsat);
        begin
          int lo, hi;
          void'(sizes.first(lo));
          void'(sizes.last(hi));
          $display("sizes seen: %0d (smallest %0d, largest %0d)", sizes.num(), lo, hi);
        end
        if (sizes.exists(8) || sizes.exists(9) || sizes.exists(10)) $fatal(1, "FAIL: an infeasible size came back");
        $display("PASS: every solved array obeys data[i] == 2 * size, and only sizes 1 to 7 came back");
        $finish;
      end
    endmodule
    Terminal
    unsatisfied calls: 0 of 300
    sizes seen: 7 (smallest 1, largest 7)
    PASS: every solved array obeys data[i] == 2 * size, and only sizes 1 to 7 came back

    Verilator is kinder than the standard here, and a commercial solver may not be. Do not rely on either: bound the size so that every size in range has legal elements, here data.size() inside {[1:7]}.

    Check yourself With 4-bit elements, what happens under the standard if size 9 is picked first?

    No element value works, because twice 9 does not fit in 4 bits, so randomize() returns 0. Bound the size to 1 to 7 to make every pick feasible.

    Bound every size

    An unconstrained rand array is not resized, so a freshly built one stays empty. A size constraint with no upper bound is the opposite risk: one seed can ask for a huge array. Give every random size both bounds.

    With a size constraint, the array keeps the solver's size after randomize() returns, and the next call solves the size again. Without one, a manual new[N] survives every call, which is the usual way to fix a size by hand.

    Common pitfall

    A rand dynamic array or queue with no size constraint is never resized, so an empty one silently stays empty. A size constraint with no upper bound lets one seed allocate a huge array. Bound both ends.

    Interview

    Expect “size first, then elements”. That is the LRM rule (18.5.8.1): sizes are solved first, and size() is a constant inside the foreach. A size that leaves no legal elements can make randomize() return 0.

    Check yourself pre_randomize sized an array with new[8], and there is no size constraint. What size follows randomize()?

    8. Without a size constraint the array is not resized; only its elements are randomized.

    Remember

    • No size constraint: the array keeps its current size, often 0.

    • The standard solves the size first, then the elements with size() as a constant.

    • Bound both ends so every size in range has legal elements.

    Rule of thumb: every rand array size gets a lower and an upper bound.

    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.

    Verilator 5.048 solved 300 of 300 calls of the coupled class, using only sizes 1 to 7; the standard's size-first order would allow failures.

    Related topics

    • foreach Constraints

    Previous

    78 / 259

    Next: foreach Constraints

    Up next

    Up nextforeach Constraints

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • sum(), Aggregate Constraints
    • unique Constraints
    • Arrays of rand Objects