Take every rand field in one randomize() call and form the tuple of their values. Each active constraint removes the tuples that break it. What remains is the solution space, and the solver returns one tuple from it.
Two 3-bit fields give 8 × 8 = 64 tuples. The rule x + y <= 4 leaves 15 of them, and x != y removes three more on the diagonal, leaving 12.
A uniform pick over tuples is not a uniform pick per field. A value that appears in many legal tuples comes up more often: here x = 0 should come up four times as often as x = 4, with no dist anywhere.
x + y <= 4; x != y; on two 3-bit fields, what share of results should have x = 0 under the standard's uniform rule? And x = 4?4 of 12 and 1 of 12. Count the legal tuples in each column: 4, 3, 2, 2 and 1.
x < y; y < 10; on 4-bit fields. Why is x = 0 far more likely than x = 8?x = 0 pairs with nine values of y, x = 8 with one. Of 45 legal tuples, nine have x = 0, so the uniform share is 9/45 against 1/45. Verilator gave 1079 and 56 out of 4500, more skewed than uniform but in the same order.
Constraints combine into one system, so their order inside a block, and the order of blocks, changes nothing. { x < y; y < 10; } and { y < 10; x < y; } describe the same 45 tuples.
One randomize() call combines more than the class text. All of these go into the same system:
Active blocks of the object and its parents. A child block with the same name replaces the parent's.
Constraints of rand child objects, which are solved together with the parent.
The inline with clause, for that call only.
Plain fields and frozen rand fields, read as constants at the moment of the call.
Each field's own type range, and the randc cycle state.
The bench below checks both halves of this lesson. It solves the 12-tuple class 3000 times, asserts every result is legal and that the shares follow the tuple counts, and then solves a child class whose replaced block meets an inline with.
class two_var;
rand bit [2:0] x, y; // 8 x 8 = 64 tuples
constraint c { x + y <= 4; x != y; } // 12 tuples survive
endclass
class base_txn;
rand bit [7:0] len;
constraint c_len { len inside {[1:64]}; }
endclass
class small_txn extends base_txn;
constraint c_len { len inside {[1:8]}; } // same name: replaces the parent block
endclass
x=0: 4 tuples, uniform pick 1000 of 3000, measured 1104
x=1: 3 tuples, uniform pick 750 of 3000, measured 811
x=2: 2 tuples, uniform pick 500 of 3000, measured 452
x=3: 2 tuples, uniform pick 500 of 3000, measured 398
x=4: 1 tuples, uniform pick 250 of 3000, measured 235
joint set {1,2,3,5,6,7,8}: 7 values seen
PASS: every tuple legal, x shares ordered by tuple count, and the joint set is exactly 7 valuesNotice what the bench asserts and what it only prints. Legality and the order of the shares are asserted. The exact counts are printed beside the uniform numbers, because no tool promises them: Verilator's x = 3 came out about 20% under the uniform share.
randomize() returns 0. What happened to the fields, and what must the caller do?The space was empty, so the fields kept their previous values. The caller checks the return value instead of sending stale data.
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.
Over 3000 calls of the 12-tuple class, Verilator gave 1104, 811, 452, 398 and 235 for x = 0 to 4, against 1000, 750, 500, 500 and 250 for a uniform pick.