Part 3 · Constraint Randomization · Intermediate
Powers of 2, One-Hot & Bit Games
Drills: power of 2 two ways, exactly/at-most/at-least K bits set, one-hot and one-cold, gray-code pairs, palindrome bit patterns.
Problem 1 — x is a power of 2
Problem
“Constrain rand bit [31:0] x; to be a power of 2. Give me two different ways.”
Think it through
A power of 2 has exactly one bit set — that is the one-hot property, so $onehot(x) or $countones(x) == 1 state it directly. The constructive form picks a rand exponent k and builds x == 1 << k — but the literal 1 is a 32-bit int, so for wider x you must size the literal or the shift wraps.
class pow2_drill;
rand bit [31:0] x;
rand bit [4:0] k; // exponent 0..31
// Way 1: declarative — one bit set
// constraint c_pow2_oh { $onehot(x); } // or $countones(x) == 1
// Way 2: constructive — build from the exponent
constraint c_pow2 { x == 32'd1 << k;
k < 32; }
endclassWhy this works
Both forms admit exactly the 32 powers of 2. The constructive form gives you the exponent as a free by-product — handy when a later constraint needs it (e.g. “size is a power of 2 between 4 and 64” becomes k inside {[2:6]}). The width note matters at 64 bits: 1 << k with a plain int literal shifts in 32-bit context and is truncated before widening — use 64'd1 << k.
Variation — power of 2 in a bounded range
constraint c_pow2_rng { x == 32'd1 << k; k inside {[4:12]}; }
// x in {16, 32, ..., 4096}, uniform over exponents (NOT uniform over values)Common wrong answers
x % 2 == 0 — that is “even”, not “power of 2”; 6 is even but not a power of 2.
(x & (x-1)) == 0 alone — admits x == 0, which is not a power of 2. Add x != 0. (Knowing this classic and its hole is a plus.)
1 << k with unsized literal for a 64-bit x — high powers truncate to 0 in 32-bit context.
Problem 2 — Exactly K bits set; at most; at least
Problem
“Constrain a 16-bit mask so that exactly 4 bits are set. Then: at most 4. Then: at least 4 but no more than 8.”
Think it through
All variants are one system function away: $countones is legal in constraints and the solver handles it natively. The drill exists to check you know that — candidates who don’t reach for foreach-with-sum gymnastics or give up.
class mask_drill;
rand bit [15:0] mask;
constraint c_exact { $countones(mask) == 4; }
// constraint c_atmost { $countones(mask) <= 4; }
// constraint c_band { $countones(mask) inside {[4:8]}; }
endclassWhy this works
$countones returns the population count as an int; the constraint becomes an arithmetic relation on that count. Distribution note worth volunteering: the solver picks uniformly among legal mask values , and there are C(16,4) = 1820 masks with exactly 4 bits — each appears with equal probability.
Variation — one-hot and one-cold
constraint c_onehot { $onehot(mask); } // exactly one 1
constraint c_onecold { $onehot(~mask); } // exactly one 0
// Equivalent: $countones(mask) == 1 / $countones(mask) == 15
// Also know $onehot0(x): one bit set OR zero — common in grant logicFollow-up: “where would you use $onehot0?” — arbitration grants, where zero grants (idle) is legal but multiple grants is a bug.
Common wrong answers
Summing bits with a hand-written foreach over a packed vector — verbose, error-prone, and signals you don’t know $countones works in constraints.
$onehot(mask) == 4 hoping it counts — $onehot returns a 1-bit true/false, not a count.
~mask vs !mask confusion in the one-cold form — !mask is logical NOT (1 bit); you need bitwise ~.
Problem 3 — Gray-code pair
Problem
“Two rand 8-bit values a and b. Constrain them so b differs from a in exactly one bit position — a gray-code neighbor.”
Think it through
XOR exposes differing bits as 1s. “Differs in exactly one position” means the XOR is one-hot. Compose the two facts you already have.
class gray_drill;
rand bit [7:0] a, b;
constraint c_gray { $countones(a ^ b) == 1; } // or $onehot(a ^ b)
endclassWhy this works
The solver picks the pair jointly: any a, and b equal to a with one bit flipped — 256 × 8 legal pairs, uniform. This one-liner composes two drill primitives (XOR-as-difference, countones) and interviewers use it to see whether you compose or memorize.
Variation — Hamming distance at most D
constraint c_hamming { $countones(a ^ b) <= 3; } // within distance 3Problem 4 — Palindrome bit pattern
Problem
“Constrain an 8-bit value so its bit pattern reads the same forwards and backwards — bit 0 equals bit 7, bit 1 equals bit 6, and so on.”
Think it through
It is a set of pairwise equalities between mirrored positions. For a fixed small width, write them out; for parameterized width, use an unpacked bit array with foreach — the index arithmetic is the same.
class palin_drill;
rand bit [7:0] x;
// Fixed width: explicit mirrored pairs (only N/2 needed)
constraint c_palin { x[0] == x[7];
x[1] == x[6];
x[2] == x[5];
x[3] == x[4]; }
endclass
// Parameterized width: unpacked array + foreach
class palin_n #(int W = 8);
rand bit b[W];
constraint c_palin { foreach (b[i]) b[i] == b[W-1-i]; }
endclassWhy this works
Only the first half of the pairs constrain anything (the second half are the same equalities restated — harmless but redundant in the foreach form). Effective free bits: 4 of 8, so 16 legal palindromes, uniform. Saying “16 solutions” unprompted shows you reason about solution-space size — a habit interviewers reward.
Common wrong answers
x == {<<{x}} (streaming reverse) — the streaming operator on the rand variable inside its own constraint is correct in some tools but many candidates write the stream backwards or misapply it to packed slices; the explicit pairs are safer on a whiteboard.
foreach directly over a packed vector’s bits in a constraint — tool support varies; the unpacked-array form is the portable answer.
Mirroring all 8 pairs and claiming 8 degrees of freedom remain — solution-space counting error.
Key takeaways
$countones and $onehot are constraint-legal — they collapse most bit-games to one line.
Constructive forms (x == 1 << k) expose useful by-products like the exponent; size your literals.
Compose primitives: gray pair = countones over XOR; palindrome = mirrored equalities.
Common pitfalls
(x & (x-1)) == 0 without x != 0 — admits zero as a “power of 2”.
Unsized 1 << k on 64-bit targets — shift evaluates in 32-bit context and truncates.
$onehot returns a bit, not a count — comparing it to K is a type-think error.
Logical ! where bitwise ~ is needed (one-cold) — compiles, wrong solution set.