Part 3 · Constraint Randomization · Intermediate

Ranges With & Without inside

Drills: bounded ranges without inside, disjoint ranges, excluding a range, and dynamic bounds where lo and hi are themselves rand.

Problem 1 — Value in [100:200] without using inside

Problem

“Constrain rand int unsigned x; to lie in [100:200]. Now do it again without the inside operator.”

Think it through

A closed range is the conjunction of two relational facts: x is at least 100 AND at most 200. Constraints in one block are implicitly ANDed, so two separate expressions work; so does one expression with &&. The trap is the chained-comparison spelling from math notation.

systemverilog
class range_drill;
  rand int unsigned x;

  // With inside — preferred in production
  // constraint c_range { x inside {[100:200]}; }

  // Without inside — two ANDed relations
  constraint c_range_rel { x >= 100;
                           x <= 200; }
endclass

Why this works

Every expression inside a constraint block must independently hold; the solver intersects them. Note that inside and the relational pair give the same uniform distribution here — inside is set membership, not a distribution weighting, a distinction the interviewer will pivot to (see the dist drills).

Variation — the chained-comparison trap

systemverilog
// WRONG — looks like math, is not:
constraint c_bad { 100 <= x <= 200; }
// Parses as (100 <= x) <= 200 → (0 or 1) <= 200 → always TRUE.
// The constraint is vacuous: x is completely unconstrained.

This compiles cleanly and never errors — the worst kind of bug. Symptom in practice: out-of-range values appear in regression and the constraint “looks right” in review.


Problem 2 — Multiple disjoint ranges, then excluding a range

Problem

“Constrain x to lie in [10:20] or [40:50] or be exactly 99. Then: constrain it to be anywhere in [0:1000] EXCEPT [100:200].”

Think it through

Disjoint sets are exactly what inside is for — its braces take a mix of scalars and ranges. Exclusion is negated membership: !(x inside {...}) ANDed with the outer bound. Without inside, both become OR-chains of relational pairs.

systemverilog
class set_drill;
  rand int unsigned x;

  // Disjoint union — inside takes scalars and ranges together
  constraint c_union { x inside {[10:20], [40:50], 99}; }

  // Without inside: explicit OR of conjunctions
  // constraint c_union_rel {
  //   (x >= 10 && x <= 20) || (x >= 40 && x <= 50) || (x == 99);
  // }
endclass

class hole_drill;
  rand int unsigned x;

  // Bounded with a hole
  constraint c_hole { x inside {[0:1000]};
                      !(x inside {[100:200]}); }
endclass

Why this works

The two expressions in c_hole are ANDed: x must be in the big range AND not in the hole. Equivalently you could enumerate x inside {[0:99], [201:1000]} — say both; the negation form scales better when the hole is parameterized.

Variation — exclusion driven by a state variable

systemverilog
class hole_dyn;
  rand bit [31:0] addr;
  bit [31:0] excl_lo, excl_hi;   // NOT rand — state, set by the test

  constraint c_hole { !(addr inside {[excl_lo:excl_hi]}); }
endclass

State variables are read as constants at the moment randomize() is called — the test sets the hole before randomizing. Interviewers use this to check you know the rand/state distinction.

Common wrong answers

  • x != [100:200] — ranges are not values; != cannot take a range. Does not compile.

  • Writing the OR-chain with single & and | — bitwise operators on multi-bit operands give arithmetic garbage, not logical OR of conditions.

  • x inside {[0:99]} || x inside {[201:1000]} as two separate constraint expressions on separate lines — separate expressions are ANDed, so x must be in both sets: contradiction, randomize() fails.


Problem 3 — Dynamic bounds: lo < x < hi where lo and hi are also rand

Problem

“All three of lo, x, hi are rand 8-bit values. Constrain them so that lo < x < hi always holds. What can go wrong?”

Think it through

State the relations directly — the solver is simultaneous, so it will choose all three together. The subtle question is solution-space existence: lo < x < hi requires hi >= lo + 2 (there must be room for x strictly between). If other constraints squeeze hi and lo together, randomize() fails. A senior answer adds the gap guarantee explicitly.

systemverilog
class window_drill;
  rand bit [7:0] lo, x, hi;

  constraint c_window { lo < x;
                        x < hi; }

  // Senior addition: guarantee the window is wide enough to be useful
  constraint c_gap    { hi >= lo + 2;          // room for at least one x
                        hi <= 8'd255; }        // explicit, documents intent
endclass

Why this works

There is no “order of solving” to worry about — the solver treats {lo, x, hi} as one constraint satisfaction problem and picks a jointly-legal triple. Note the distribution effect: x values near the middle of [0:255] have more legal (lo, hi) pairs around them, so x is not uniform — it humps toward the center. Mentioning this unprompted is a strong senior signal.

Variation — interviewer follow-up

“Make x uniform regardless of the window.” Answer: decouple the solves — solve lo, hi before x does NOT make x uniform overall (it makes the window uniform first, x uniform within each window). True global uniformity on x requires randomizing x alone first (e.g. std::randomize(x)) and then constraining lo/hi around the now-state value — explain the trade, don’t just name a keyword.

Common wrong answers

  • lo < x < hi as one chained expression — same vacuous-parse trap as Problem 1.

  • “You must solve lo and hi before x or it won’t work” — false; the solver is simultaneous. solve...before only reshapes probability, never legality.

  • Forgetting that lo=254, hi=255 leaves no legal x — if a test later pins lo and hi, randomize() returns 0 and an unchecked call silently keeps stale values.

Key takeaways

  • A range is two ANDed relations; inside is set membership over scalars and ranges.

  • Exclusion is !(x inside {...}) ANDed with the outer bound.

  • rand-on-rand bounds solve simultaneously — but guarantee the window has room, and know the distribution is not uniform.

Common pitfalls

  • 100 <= x <= 200 — parses as ((100<=x)<=200), always true, constraint vacuous.

  • Two inside expressions on separate lines hoping for OR — separate expressions AND together.

  • Bitwise &/| in place of logical &&/|| inside compound range conditions.

  • Never checking randomize() return — an infeasible window silently leaves stale values.