Part 3 · Constraint Randomization · Intermediate

Array Constraint Cookbook

Six classic interview problems with complete solutions: sorted array, exactly-K matches, no adjacent equal, one-hot array, alternating pattern, bounded sum.

How to use this cookbook

Every problem below is a real interview question. Each solution composes the same three building blocks from the previous lessons: a size bound , foreach element/relation constraints (with boundary guards), and aggregate equations (often the sum-of-booleans counting idiom). Before writing any constraint, run the two feasibility checks: does the value space have enough room (pigeonhole), and can the element bounds reach the aggregate target (min/max arithmetic)? Interviewers award as much credit for stating these checks as for the constraint itself.

diagram
COOKBOOK DECISION GUIDE

  "ordered / sorted"        -> foreach + guarded neighbor relation
  "exactly K / at most K"   -> sum() with (int'(condition)) == K
  "all different"           -> unique { arr }  (check pigeonhole!)
  "no two adjacent ..."     -> foreach + guarded i / i-1 relation
  "one-hot / one special"   -> counting sum == 1  (+ rand index variant)
  "total/budget/checksum"   -> sum() with (int'(item)) == N
                               (check: size*min <= N <= size*max)

Problems 1-3: sorted, exactly-K, no adjacent equal

1. Sorted (non-decreasing) array

systemverilog
class p1_sorted;
  rand bit [7:0] a[];
  constraint c {
    a.size() inside {[5:10]};
    foreach (a[i]) if (i > 0) a[i] >= a[i-1];   // non-decreasing
    // strictly increasing variant: a[i] > a[i-1]
    //   feasibility: needs >= size distinct values in range
  }
endclass

Non-decreasing (>=) is always feasible. Strict (>) adds the pigeonhole requirement — say this out loud in an interview.

2. Exactly K elements equal to V (sum-of-booleans idiom)

systemverilog
class p2_exactly_k;
  rand bit [7:0] a[10];
  parameter bit [7:0] V = 8'h5A;
  parameter int K = 3;
  constraint c {
    a.sum() with (int'(item == V)) == K;
    // optional: non-matching elements stay away from near-misses
    foreach (a[i]) (a[i] != V) -> !(a[i] inside {V-1, V+1});
  }
endclass

The comparison item == V is 1 or 0 per element; the widened sum counts matches. Variants: <= K for "at most", >= 1 for "at least one".

3. No two adjacent elements equal

systemverilog
class p3_no_adjacent;
  rand bit [3:0] a[];
  constraint c {
    a.size() inside {[6:12]};
    foreach (a[i]) if (i > 0) a[i] != a[i-1];
  }
endclass
// Feasible whenever the value space has >= 2 values:
// each element only needs to avoid ONE neighbor value.

Problems 4-6: one-hot, alternating, bounded sum

4. One-hot array (exactly one element nonzero)

systemverilog
class p4_onehot;
  rand bit [7:0] a[8];
  rand int unsigned hot_idx;

  // Style A: pure counting — position falls where it may
  constraint c_count { a.sum() with (int'(item != 0)) == 1; }

  // Style B: rand index — when the test wants to KNOW the position
  constraint c_idx {
    hot_idx < 8;
    foreach (a[i]) (i == hot_idx) ? (a[i] != 0) : (a[i] == 0);
  }
  // Use ONE style; both together are consistent but redundant.
endclass

Style B is the senior answer when follow-up constraints need the position ("the hot element is in the upper half": add hot_idx >= 4).

5. Alternating pattern (high/low or parity)

systemverilog
class p5_alternating;
  rand bit [7:0] a[];
  rand bit start_high;          // randomize which phase comes first
  constraint c {
    a.size() inside {[4:10]};
    foreach (a[i])
      if (i % 2 == int'(start_high))
        a[i] >= 8'h80;          // "high" slots
      else
        a[i] <  8'h80;          // "low" slots
  }
endclass
// Pure parity-of-index version: a[i][0] == (i % 2) forces
// alternating odd/even VALUES instead of high/low bands.

6. Sum in a range with bounded elements (feasibility math)

systemverilog
class p6_budget;
  rand bit [7:0] a[];
  constraint c {
    a.size() inside {[4:8]};
    foreach (a[i]) a[i] inside {[10:50]};
    a.sum() with (int'(item)) inside {[120:200]};
  }
endclass
// Feasibility check BEFORE trusting this:
//   size=4: total range [4*10 : 4*50] = [40:200]  -> overlaps [120:200] OK
//   size=8: total range [8*10 : 8*50] = [80:400]  -> overlaps OK
// If the target range missed every size's reachable window,
// randomize() would return 0 with no error message.

Walking the min/max arithmetic for each candidate size is exactly the verbal reasoning interviewers want to hear — it proves you can predict solver failures instead of debugging them.


Interview angle

How to perform on a live array puzzle

  1. Restate the problem as constraints out loud: size bound, element bounds, relations, aggregates.

  2. Run feasibility checks verbally: pigeonhole for uniqueness/strict order, min-max window for sums.

  3. Write the guarded foreach FIRST if relations exist — boundary guards are the most-watched detail.

  4. Reach for sum() with (int'(cond)) for any counting requirement — name it as the counting idiom.

  5. Mention the int'() cast on every sum over narrow elements, even if totals look small today.

  6. Offer a post_randomize() check or a $display loop to validate — shows verification mindset.

If you blank on syntax, describing the expanded constraint set ("I need a[i] != a[i-1] for every i from 1 to size-1") earns most of the credit — interviewers test the solution-space model more than keyword recall.

Key takeaways

  • Every classic array puzzle decomposes into size bound + guarded foreach relations + aggregate equations.

  • The counting idiom sum() with (int'(cond)) == K handles exactly/at-most/at-least in one form.

  • Always verbalize feasibility: pigeonhole for distinct/strict-order, min-max window for sum targets.

  • Prefer a rand index variable over pure counting when later constraints need to reference the special position.

  • Validate generated arrays procedurally after randomize — constraints and intent can disagree silently.

Common pitfalls

  • Unguarded a[i-1]/a[i+1] at boundaries — the most common live-coding error in this topic.

  • Sum targets outside the reachable [size*min : size*max] window — silent randomize() failure.

  • Missing int'() cast on sums of narrow elements — mod-2^width wraparound corrupts the equation.

  • Mixing two redundant styles (counting AND indexed one-hot) without realizing one suffices — signals shaky understanding.

  • Forgetting that strictly-increasing + narrow element type + large size is pigeonhole-unsatisfiable.