Part 3 · Constraint Randomization · Intermediate

inside, Ranges & Set Membership

Set membership with inside, range syntax, negation, $ open ranges, and arrays as constraint sets.

inside defines a membership set

The inside operator is the workhorse of constraint writing: it restricts a variable to a set built from individual values, ranges, or both. To the solver, x inside {[1:5], 10} means exactly x ∈ {1, 2, 3, 4, 5, 10} — six legal values. Because the default solver distribution is uniform over the solution space, each of those six values has probability 1/6. That last sentence is the part interviewers check: inside changes the set , and the set size determines each value's probability.

systemverilog
class pkt;
  rand bit [7:0] len;
  rand bit [7:0] kind;

  // Ranges and discrete values mix freely in one set
  constraint len_c  { len inside {[1:8], 16, 32, [64:67]}; }

  // Negation: kind may be anything EXCEPT 0 and the range 200..255
  constraint kind_c { !(kind inside {0, [200:255]}); }
endclass

Here len has 8 + 1 + 1 + 4 = 14 legal values, each at 1/14. The negated form inverts the set: kind may take any of the 256 possible values except the 57 excluded ones, leaving 199 legal values. Negation is how you express “anything but reserved encodings” without listing the legal side.


Open ranges with $

Inside a range, $ stands for the minimum or maximum value of the variable's type. [$:100] means “from the type's smallest value up to 100” and [100:$] means “from 100 up to the type's largest value.” This makes constraints robust against width changes — widen the field and the open range follows.

systemverilog
class addr_txn;
  rand bit [15:0]      addr;   // unsigned: $ low = 0,      $ high = 65535
  rand int             delta;  // signed:   $ low = -2^31,  $ high = 2^31-1

  constraint hi_region { addr  inside {[16'h8000:$]}; }  // 0x8000..0xFFFF
  constraint neg_only  { delta inside {[$:-1]}; }        // any negative int
endclass

Note the signedness subtlety: for the unsigned addr, $ on the low side is 0; for the signed delta, it is the most negative representable value. Using $ on a signed type when you meant “down to zero” is a classic source of surprise negative values.


Arrays as the membership set

The set in an inside expression does not have to be a literal — it can be an array (fixed, dynamic, or queue). The solver treats the array's current element values as the membership set at the moment randomize() is called. This is the standard pattern for “pick one of these runtime-configured values,” such as legal addresses loaded from a memory map.

systemverilog
class cfg_txn;
  int legal_ids[$] = '{3, 7, 11, 42};   // non-rand state: a runtime knob
  rand int id;

  constraint id_c { id inside {legal_ids}; }
endclass

module t;
  initial begin
    cfg_txn tx = new();
    void'(tx.randomize());          // id is one of 3, 7, 11, 42 (1/4 each)
    tx.legal_ids = '{100, 200};     // reconfigure at runtime
    void'(tx.randomize());          // id is now 100 or 200 (1/2 each)
  end
endmodule

Because the array is a non-rand state variable, the solver reads its values rather than solving for them. Reconfiguring the queue between calls changes the solution space with zero constraint edits — this is how reusable transactions get per-test legal sets.


Visualizing the solution space

It helps to picture every inside constraint as carving regions out of the variable's full value line. Intersecting constraints (multiple constraint blocks, or inline with) shrink the legal region further — the solver picks uniformly from whatever remains.

diagram
SOLUTION SPACE for: len inside {[1:8], 16, 32, [64:67]};

  value line 0 ........................................... 255
              |#[1──8]#......#16......#32......#[64─67]#....|
                  8 vals      1        1          4 vals
                        total = 14 legal values
                        P(each value) = 1/14  (uniform default)

  Add a second constraint:  len % 2 == 0;   (intersection!)

              |.#2,4,6,8#....#16......#32......#64,66#......|
                  4 vals      1        1         2 vals
                        total = 8 legal values
                        P(each value) = 1/8

  Constraints INTERSECT — they never relax each other.

The intersection rule is universal: every additional active constraint can only keep or shrink the space. If the intersection is empty, randomize() fails and returns 0 — it never “picks the closest legal value.”


Interview angle

What interviewers ask

  • “What is the probability of each value in x inside {[1:5], 10}?” — 1/6 each; they are checking you know the default is uniform over the SET, not over the syntax items (not 1/2 for the range and 1/2 for the value).

  • “How do you exclude a range?” — !(x inside {[a:b]}). Follow-up: what is the resulting set size?

  • “Can the set come from a variable?” — yes, arrays/queues are read as state at solve time; this is the runtime-configurable legal set pattern.

  • “What does inside {[100:$]} mean for a signed type?” — up to the type maximum; tests whether you know $ is type-relative.

A strong answer always names the set size and the uniform-over-set default before discussing syntax. If they want non-uniform probability, that is the cue to bring up dist — the next sub-topic.

Key takeaways

  • inside builds a membership set from values, ranges, and arrays; solver picks uniformly over that set.

  • !(x inside {...}) inverts the set — the standard way to exclude reserved encodings.

  • $ in a range bound means the type's min or max — beware signed types.

  • Arrays in the set are read as current state at solve time, enabling runtime-configured legal sets.

  • Multiple constraints intersect; an empty intersection makes randomize() return 0.

Common pitfalls

  • Assuming each syntax item gets equal weight — a range of 1000 values vs a single value is 1000:1, not 1:1.

  • Using $ on signed fields and getting unintended negative values in the set.

  • Forgetting that an empty array in inside {arr} yields an empty set — randomize() fails.

  • Writing x != 5 chains instead of one negated inside — verbose and easy to get wrong.

  • Expecting a later constraint to widen an earlier one — constraints only intersect.