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. foreach Constraints

Part 3 · Constraint Randomization · Intermediate

foreach Constraints

One constraint per element, guarded neighbour rules, and multidimensional iteration.

On this page

    Why this matters

    An ascending-address constraint read past the end of its array at the last index. Nothing failed loudly; the run just stopped producing legal bursts.

    After this lesson you can: write per-element and neighbour rules with the right guard, iterate matrices, queues and associative arrays, and keep a payload consistent with its length.

    Replication, not a loop

    Inside a constraint block, foreach (arr[i]) expr copies the expression once per element with i replaced by a constant. For a 4-element array, foreach (a[i]) a[i] < 10 hands the solver four constraints, all solved together.

    The index is not random. It can appear in arithmetic and conditions, such as i % 2 == 0, and the number of copies follows the size, which is solved first.

    Three constraints from one line.
    Check yourself A 4-element array meets foreach (a[i]) a[i] < 10. How many constraints does the solver see?

    Four: one per index, a[0] < 10 through a[3] < 10.

    Guard the boundary

    Neighbour relations such as a[i] > a[i-1] name an index that does not exist at the array edge. Guard the relation so it applies only where both elements exist. The low-side guard if (i > 0) keeps the random size out of the guard.

    Guard style

    Form

    Note

    Guard low

    if (i > 0) a[i] > a[i-1]

    Preferred: size stays out of the guard

    Guard high

    if (i < a.size() - 1) a[i] < a[i+1]

    Equivalent, mixes the random size into the guard

    Three cookbook patterns use the same guarded form. These short fragments are illustrations, so the course checks skip them.

    design.sv
    1
    2
    3
    4
    5
    6
    // snippet
    foreach (a[i]) if (i > 0) a[i] >= a[i-1];   // sorted, non-decreasing
    foreach (a[i]) if (i > 0) a[i] != a[i-1];   // no two adjacent equal
    foreach (a[i])                              // alternating bands
      if (i % 2 == int'(start_high)) a[i] >= 8'h80;
      else                           a[i] <  8'h80;
    Predict first sorted_arr asks for 5 to 10 strictly ascending values, each below 1000. Can that ever be unsatisfiable? What if the values were bit [2:0]?

    Not with values below 1000: ten distinct values fit easily. With bit [2:0] there are only 8 values, so a size of 9 or 10 has no strictly ascending answer.

    Every dimension, queue and key set

    Naming every dimension visits every cell: foreach (m[i, j]) visits all row and column pairs. The standard separates the indexes with commas; some tools also accept m[i][j], but Verilator rejects it. Queues iterate like dynamic arrays.

    Associative arrays iterate over existing keys only. randomize() never creates or removes keys, so the testbench adds them before the call.

    The bench below runs a printable alternating frame, a strictly ascending array, and a class mixing a 4 by 4 matrix, a queue and an associative array, checking every result.

    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
    class frame;
      rand bit [7:0] data[];
      rand bit       start_high;
      constraint c_size { data.size() inside {[4:16]}; }
      constraint c_elem {
        foreach (data[i]) data[i] inside {[8'h20:8'h7E]};      // printable ASCII
        foreach (data[i])                                       // alternating bands
          if (i % 2 == int'(start_high)) data[i] >= 8'h50;
          else                           data[i] <  8'h50;
      }
    endclass
    
    class sorted_arr;
      rand int unsigned a[];
      constraint c_size { a.size() inside {[5:10]}; foreach (a[i]) a[i] < 1000; }
      constraint c_up   { foreach (a[i]) if (i > 0) a[i] > a[i-1]; }   // guarded on the low side
    endclass
    
    class grid;
      rand bit [7:0] m[4][4];
      rand int       q[$];
      rand bit [7:0] cfg[string];
      constraint c {
        foreach (m[i, j]) m[i][j] inside {[0:99]};
        foreach (m[i, j]) if (i != j) m[i][i] > m[i][j];        // diagonal biggest in its row
        q.size() inside {[2:6]};
        foreach (q[k]) q[k] inside {[-100:100]};
        foreach (cfg[s]) cfg[s] != 8'hFF;                       // existing keys only
      }
      function new();
        cfg["mode"] = 8'hFF;                                    // keys exist before randomize()
        cfg["speed"] = 8'hFF;
      endfunction
    endclass
    
    Terminal
    frame: 14 bytes, start_high 0, first bytes 5b 3a
    sorted: 6 values, 496 .. 779
    grid: row 0 = 95 31 31 61; queue holds 6; keys 2
    PASS: printable alternating frames, strictly sorted arrays, and matrix, queue and key rules all hold
    • Constraints cannot add queue entries or associative keys; they only shape what exists.

    • The foreach index is not random; a separate rand index field picks a position.

    • Functions called on elements inside constraints see solve-start values and must have no side effects.

    Check yourself An associative array is empty when randomize() runs. What does foreach constrain?

    Nothing. There are no keys to visit, and the solver creates none.

    PacketFIFO: payload bytes that match len

    Project step

    PacketFIFO sends only the first len payload bytes. Constrain pkt_txn so that bytes 0 to len-1 are never zero and the bytes past len are always zero, so a stale byte can never hide in the unused tail. Check 1000 packets, and print how often each length appeared.

    One way to write 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
    25
    26
    27
    28
    29
    30
    31
    32
    typedef enum bit [1:0] {DATA = 2'd0, CTRL = 2'd1, ERR = 2'd2} kind_e;
    
    class pkt_txn;
      rand kind_e    kind;
      rand bit [1:0] dest;
      rand bit [3:0] len;
      rand bit [7:0] payload[8];
      constraint legal_c { len inside {[1:8]}; dest inside {[0:2]}; }
      constraint payload_c {
        foreach (payload[i])
          if (i < len) payload[i] != 8'h00;          // used bytes never zero
          else         payload[i] == 8'h00;          // bytes past len are zero
      }
    endclass
    
    module tb_pkt_payload;
      pkt_txn p = new();
      int lens[int];
      initial begin
        repeat (1000) begin
          if (!p.randomize()) $fatal(1, "FAIL: randomize failed");
          foreach (p.payload[i])
            if ((i < p.len) != (p.payload[i] != 0)) $fatal(1, "FAIL: len %0d, payload[%0d] = %0h", p.len, i, p.payload[i]);
          lens[p.len]++;
        end
        $display("last packet: len %0d, payload %p", p.len, p.payload);
        $display("len counts: 1=%0d 2=%0d 3=%0d 4=%0d 5=%0d 6=%0d 7=%0d 8=%0d",
                 lens[1], lens[2], lens[3], lens[4], lens[5], lens[6], lens[7], lens[8]);
        $display("PASS: 1000 packets, bytes 0 to len-1 nonzero and the rest zero");
        $finish;
      end
    endmodule
    Terminal
    last packet: len 8, payload '{'hfb, 'hfe, 'hdf, 'h7f, 'h7f, 'haf, 'hff, 'h6f}
    len counts: 1=0 2=3 3=5 4=1 5=25 6=56 7=538 8=372
    PASS: 1000 packets, bytes 0 to len-1 nonzero and the rest zero

    Every packet is legal, but look at the length counts. With len deciding which bytes must be zero, Verilator 5.048 gave lengths 7 and 8 about 91% of the time and length 1 almost never. The standard asks for no particular distribution, and a solver that favours long packets leaves short ones untested.

    The fix is to say what you want: a dist on len, or a coverage point that shows the hole. Functional Coverage builds that check on this exact packet.

    Common pitfall

    An unguarded edge reference, a foreach index treated as random, or a strict order wider than the value type are the three fast ways to fail this lesson. A fourth, quieter one: legal results that are badly spread.

    Interview

    Expect “write an ascending constraint” and “is foreach a loop”. The answers are the low-side guard, and replication handed to one joint solve.

    Remember

    • foreach copies the rule once per index; it is not a loop.

    • Guard neighbour rules low: if (i > 0) a[i] > a[i-1].

    • Associative arrays: only existing keys are visited, none are created.

    Rule of thumb: every rule that names i-1 or i+1 carries a guard.

    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 rejects foreach (m[i][j]); the bench uses the standard comma form m[i, j]. With len gating the payload rules, lengths 7 and 8 came out 91% of the time over 1000 packets.

    Related topics

    • Constraining Array Size

    Previous

    79 / 259

    Next: sum(), Aggregate Constraints

    Up next

    Up nextsum(), Aggregate Constraints

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • unique Constraints
    • Arrays of rand Objects