Part 3 · Constraint Randomization · Intermediate
Even/Odd, Alignment & Divisibility
Drills: even/odd three ways, address alignment, size-dependent alignment, divisible by non-power-of-2, NOT a multiple of N.
Problem 1 — Constrain a value to be even (three ways)
Problem
“You have rand bit [7:0] x;. Write a constraint so that x is always even. Now show me two more ways to do it.”
Think it through
Evenness is a property of the LSB: a binary number is even iff bit 0 is zero. That gives the cheapest form. Modulo expresses the same arithmetic fact, and a shift-right-then-left round-trip drops the LSB — equal to the original only when the LSB was already 0. All three are pure relations, so all three are legal constraints.
class pkt;
rand bit [7:0] x;
// Way 1: bit slice — cheapest for the solver, idiomatic
constraint c_even_slice { x[0] == 1'b0; }
// Way 2: modulo — readable, generalizes to any divisor
// constraint c_even_mod { x % 2 == 0; }
// Way 3: shift round-trip — proves you understand the bit mechanics
// constraint c_even_shift { (x >> 1) << 1 == x; }
endclassWhy this works
All three forms describe the identical solution set: the 128 even values of an 8-bit space. The bit-slice form is preferred in production because it is a single-bit equality — trivially cheap and impossible to misread. The modulo form generalizes to any divisor, which sets up Problem 3.
Variation — make it odd
constraint c_odd { x[0] == 1'b1; } // or: x % 2 == 1;Follow-up the interviewer will ask: “which form would you put in a base transaction class?” Answer: the bit-slice form, named clearly (c_even_slice), so subclasses can disable it by name with constraint_mode(0).
Common wrong answers
x = x & ~1; — an assignment, not a relation. Constraints contain expressions, not statements; this does not compile.
x & 1 == 0; — operator precedence trap: parses as x & (1 == 0) which is x & 0, always 0, i.e. always FALSE. randomize() fails every time. Parenthesize: (x & 1) == 0.
post_randomize() { x[0] = 0; } — works mechanically but skews the distribution (two random values map to each even value is fine here, but the interviewer wants a constraint, and the habit breaks when constraints interact).
Problem 2 — Address aligned to 4, then alignment that depends on size
Problem
“Constrain a 32-bit addr to be 4-byte aligned. Now make the alignment depend on a rand size field that can be 1, 2, 4, or 8 bytes — the address must be naturally aligned to the size.”
Think it through
Fixed alignment to 4 means the two LSBs are zero. For size-dependent alignment, natural alignment to a power-of-2 size means addr & (size - 1) == 0 — the mask trick. It only works because size is a power of 2, so size - 1 is a mask of the low bits. Both size and addr are rand, and the solver handles the dependency in one pass — no ordering needed.
class mem_txn;
rand bit [31:0] addr;
rand bit [3:0] size; // bytes: 1, 2, 4, or 8
// Part A: fixed 4-byte alignment
constraint c_align4 { addr[1:0] == 2'b00; } // or: addr % 4 == 0;
// Part B: natural alignment to a power-of-2 size
constraint c_size { size inside {1, 2, 4, 8}; }
constraint c_align { (addr & (size - 1)) == 0; }
endclassWhy this works
For size 8, size - 1 is 4'b0111, so the constraint forces the three low address bits to zero. The solver picks (size, addr) pairs jointly: it can pick size first and a matching addr, or an addr and any size that divides it — constraint solving is bidirectional, a point interviewers probe immediately after this drill.
Variation — alignment with non-power-of-2 size
“What if size could be 3?” The mask trick breaks (3 - 1 = 2'b10 is not a low-bit mask). Fall back to modulo:
constraint c_align_any { addr % size == 0; } // works for any size > 0Common wrong answers
addr & ~(size - 1); — an expression with no comparison; as a constraint it means “this value is non-zero”, which is not alignment at all.
Using the mask trick without constraining size to powers of 2 — silently produces misaligned addresses for size 3, 5, 6, 7.
addr % size == 0 without size > 0 — if size can be 0, modulo by zero is undefined and the solve fails or errors.
Problem 3 — Divisible by a non-power-of-2
Problem
“Constrain rand bit [15:0] x; to be divisible by 5. Can you do it without the modulo operator?”
Think it through
Modulo is the direct statement of the property. The no-modulo form introduces a helper rand variable k and states x == 5 * k — turning a divisibility test into a multiplicative construction. The helper must be bounded so 5*k cannot exceed or wrap the 16-bit range.
class div_drill;
rand bit [15:0] x;
rand bit [15:0] k; // helper — constructs x rather than testing it
// Direct form
// constraint c_div5 { x % 5 == 0; }
// Constructive form (no modulo)
constraint c_build { x == 5 * k;
k <= 16'd13107; } // 5 * 13107 = 65535, no wrap
endclassWhy this works
Every multiple of 5 in [0:65535] is 5*k for exactly one k in [0:13107], so the constructive form covers the same solution set with uniform probability over k. The bound on k matters: without it, 5*k wraps modulo 65536 and produces values like 5*13108 = 65540 mod 65536 = 4 — not divisible by 5. Width and wraparound traps recur throughout constraint interviews.
Variation — value is NOT a multiple of N
constraint c_not_mult3 { x % 3 != 0; }Follow-up: “can you do NOT-a-multiple constructively?” Yes — x == 3 * k + r with r inside {1, 2} and k bounded; but the negated modulo is shorter and equally solver-friendly, so say you would use it.
Common wrong answers
x == 5 * k with unconstrained k — wraparound produces non-multiples; the classic width trap.
x / 5 * 5 == x — actually correct (integer division truncates), worth knowing, but easy to get backwards; interviewers accept it if you explain the truncation.
Trying randc to cycle multiples — randc cycles all values of its type, not multiples of 5.
Key takeaways
Even/odd: constrain the LSB — one bit, three equivalent spellings, know them all.
Power-of-2 alignment: addr & (size-1) == 0; non-power-of-2: fall back to modulo.
Constructive helpers (x == N*k) must bound k to prevent width wraparound.
Common pitfalls
x & 1 == 0 without parentheses — precedence makes it always false; randomize() fails.
Mask alignment trick with a size that is not a power of 2 — silently wrong addresses.
Helper-variable construction without an upper bound — wrapped products break divisibility.
Writing assignments (x = ...) inside constraint blocks — does not compile; relations only.