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.

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

Why 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

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

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

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

systemverilog
constraint c_align_any { addr % size == 0; }   // works for any size > 0

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

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

Why 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

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