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.

systemverilog
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; }
endclass

Why 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

systemverilog
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.

systemverilog
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]}; }
endclass

Why 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

systemverilog
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 logic

Follow-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.

systemverilog
class gray_drill;
  rand bit [7:0] a, b;

  constraint c_gray { $countones(a ^ b) == 1; }   // or $onehot(a ^ b)
endclass

Why 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

systemverilog
constraint c_hamming { $countones(a ^ b) <= 3; }  // within distance 3

Problem 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.

systemverilog
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]; }
endclass

Why 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.