Part 3 · Constraint Randomization · Intermediate

Sum, Checksum & Distribution Drills

Drills: array sums to N with bounds and the sum() width trap, header+payload length fields, 60/30/10 dist, equal probability over disjoint sets, := vs :/ math.

Problem 1 — Array sums to N, elements bounded

Problem

“Constrain rand bit [7:0] arr[10]; so the elements sum to exactly 100, with every element between 1 and 50. There is a famous trap here — find it.”

Think it through

The relation is arr.sum() == 100 plus a foreach for the bounds. The trap: sum() computes in the element type’s width . Ten bytes summing in 8-bit arithmetic wrap at 256 — an array summing to 356 also “equals” 100. Fix by casting each item up with a with clause.

systemverilog
class sum_drill;
  rand bit [7:0] arr[10];

  // TRAP version: 8-bit accumulation — 356 wraps to 100 and "passes"
  // constraint c_sum_bad { arr.sum() == 100; }

  // Correct: widen each element before accumulating
  constraint c_sum { arr.sum() with (int'(item)) == 100; }
  constraint c_elem { foreach (arr[i]) arr[i] inside {[1:50]}; }
endclass

Why this works

The with (int'(item)) clause makes the accumulation 32-bit, so only true sums of 100 satisfy the constraint. Feasibility sanity check: 10 elements of at least 1 give a minimum sum of 10, and at most 50 each gives a maximum of 500 — 100 sits comfortably inside, so the solve succeeds. State that check out loud; interviewers listen for it.

Variation — checksum field

systemverilog
class csum_pkt;
  rand bit [7:0] payload[16];
  rand bit [7:0] csum;

  // Checksum = low byte of the payload sum (deliberate 8-bit truncation)
  constraint c_csum { csum == (payload.sum() with (int'(item))) % 256; }
endclass

Follow-up: “would you constrain the checksum or compute it in post_randomize()?” Senior answer: post_randomize() — the checksum is a pure function of solved fields, so burning solver effort on it is waste; constrain it only when you need to randomize corrupt checksums at a controlled rate.

Common wrong answers

  • arr.sum() == 100 raw — accepts wrapped sums (356, 612, ...); the most-asked sum trap in interviews.

  • Constraining the sum but no element lower bound when the problem says 1..50 — zeros appear and the distribution clusters.

  • Trying a procedural loop accumulating into a local — constraints take expressions; loops live in foreach, not bodies.


Problem 2 — Packet length = header + payload

Problem

“A packet has rand hdr_len (4 to 16), rand pay_len (0 to 1024), and rand total_len. Keep them consistent, and keep total_len at or under 1500.”

Think it through

State the structural equality and the bounds; the solver propagates the cap on total_len back into pay_len automatically — constraint solving is bidirectional, so you do NOT need to recompute pay_len’s effective ceiling by hand (but say what it is: 1500 - 4 = 1496 at most).

systemverilog
class pkt_len;
  rand int unsigned hdr_len, pay_len, total_len;

  constraint c_hdr   { hdr_len inside {[4:16]}; }
  constraint c_pay   { pay_len <= 1024; }
  constraint c_total { total_len == hdr_len + pay_len;
                       total_len <= 1500; }
endclass

Why this works

total_len is fully determined by the other two, yet declaring it rand and equating it is idiomatic — downstream constraints (and inline with clauses in tests) can then constrain total_len directly and the solver pushes the implication backward into hdr_len/pay_len. That backward push is exactly what a procedural compute-after cannot do.

Variation — interviewer follow-up

“A test does p.randomize() with { total_len == 64; }; — what happens?” The solver picks hdr_len in [4:16] and pay_len = 64 - hdr_len, both within bounds: it just works, BECAUSE total_len was rand and related by constraint. If total_len had been computed in post_randomize(), the inline constraint would be illegal (not a rand field) — this contrast is the whole point of the drill.


Problem 3 — Distribution drills: 60/30/10 and the := vs :/ math

Problem

“Make kind take READ 60% of the time, WRITE 30%, ERROR 10%. Then: in x dist { [1:10] :/ 50, 20 :/ 50 }, what is the probability of x == 5? And with := instead?”

Think it through

Percentages map to dist weights — they need only be in ratio 6:3:1. The range-bin question is pure weight arithmetic: :/ divides the weight ACROSS the range; := gives the weight TO EACH value in the range. Same syntax shape, wildly different totals.

systemverilog
typedef enum {READ, WRITE, ERROR} kind_e;

class dist_drill;
  rand kind_e kind;
  rand int unsigned x;

  constraint c_kind { kind dist { READ := 60, WRITE := 30, ERROR := 10 }; }

  // The math drill:
  constraint c_x { x dist { [1:10] :/ 50, 20 :/ 50 }; }
  // [1:10] shares 50 → each of the ten values gets 5.
  // P(x==5)  = 5  / (50 + 50) = 5%      P(x==20) = 50%
  //
  // With :=  : x dist { [1:10] := 50, 20 := 50 };
  // each of 1..10 gets 50 → total = 10*50 + 50 = 550
  // P(x==5)  = 50 / 550 ≈ 9.1%          P(x==20) = 50/550 ≈ 9.1%
endclass

Why this works

dist weights are relative, not percentages — 6:3:1 and 60:30:10 are identical. For the range bin: under :/ the bin [1:10] is one 50-weight bucket split ten ways; under := it is ten 50-weight buckets. Do the division on the whiteboard — interviewers ask for the number, not the rule.

Variation — equal probability over non-contiguous sets

systemverilog
// "Pick uniformly from {3, 7, [100:199]} — every VALUE equally likely."
constraint c_uniform_vals { x dist { 3 := 1, 7 := 1, [100:199] := 1 }; }
// := 1 per value → 102 values, each 1/102.

// "...every SET equally likely (1/3 each), uniform within the range."
constraint c_uniform_sets { x dist { 3 :/ 1, 7 :/ 1, [100:199] :/ 1 }; }
// each bucket 1/3; inside [100:199] each value gets 1/300.

This pair is the cleanest demonstration of := vs :/ — memorize it as the canonical example.

Common wrong answers

  • Using inside {[1:10], 20} and claiming a 50/50 split — inside is membership only; all 11 values are equally likely (20 gets 1/11, not 1/2).

  • Saying := and :/ “are basically the same” — only true for scalar (non-range) bins.

  • dist on a randc variable — illegal; randc is cycling, dist is weighting, they cannot combine.

Key takeaways

  • arr.sum() accumulates in the ELEMENT width — always cast with (int'(item)) for byte arrays.

  • Declare derived lengths rand and relate them by equality — bidirectional solving makes inline overrides work.

  • := weights each value in a range; :/ splits the weight across the range — do the arithmetic.

Common pitfalls

  • 8-bit sum wraparound silently accepting wrong totals — the single most-asked sum trap.

  • Computing derived fields in post_randomize() then trying to constrain them inline — not rand, illegal.

  • inside used where a weighted dist was required — uniform membership is not a distribution.

  • dist weights on randc variables — a compile error and a concept error at once.