Part 3 · Constraint Randomization · Intermediate

sum(), Aggregate Constraints

arr.sum() with with-clauses, checksum and weight-budget problems, the 32-bit overflow trap and its cast fix, and product/and/or/xor reductions.

Aggregate constraints: one equation over many elements

Array reduction methods — sum(), product(), and(), or(), xor() — are legal inside constraints and create a single equation that couples every element at once . This is how you express budgets ("weights total 100"), checksums ("payload bytes sum to the checksum field"), and counting ("exactly K elements satisfy P"). The optional with clause transforms each element before reduction, where item names the current element and item.index its position.

systemverilog
class budget;
  rand bit [7:0] weight[4];

  // Classic: weights must total exactly 100
  constraint c_total {
    weight.sum() with (int'(item)) == 100;
    foreach (weight[i]) weight[i] inside {[5:60]};
  }
endclass

class checksum_pkt;
  rand bit [7:0] payload[];
  rand bit [7:0] checksum;

  constraint c {
    payload.size() inside {[4:16]};
    // payload bytes sum (mod 256) equals the checksum field
    checksum == payload.sum() with (int'(item)) % 256;
  }
endclass

Both directions work: the solver can pick payload bytes to hit a forced checksum, or derive the checksum from free payload bytes — aggregate equations are bidirectional like every other constraint.


The overflow trap: sum() width follows the element type

Per the LRM, arr.sum() computes in the element type's width . Summing bit [7:0] elements produces an 8-bit running sum — it silently wraps mod 256. The constraint payload.sum() == 1000 on byte elements is then unsatisfiable (an 8-bit value can never equal 1000) or, worse, satisfiable in surprising wrapped ways for smaller targets. The fix is to widen each item inside the with clause — int'(item) — so the reduction runs at 32-bit (or wider with explicit casts).

systemverilog
class overflow_demo;
  rand bit [7:0] b[8];

  // WRONG: 8-bit accumulation. sum wraps mod 256.
  // "b.sum() == 1000" can never be true in 8 bits -> randomize() fails.
  // "b.sum() == 8'd100" CAN be true via wraparound you did not intend
  // (e.g. true total 356 wraps to 100).
  constraint c_bad  { b.sum() == 8'd100; }
endclass

class overflow_fixed;
  rand bit [7:0] b[8];

  // RIGHT: widen each element BEFORE accumulation.
  constraint c_good { b.sum() with (int'(item)) == 1000; }

  // For very wide totals, cast to a wider type explicitly:
  // b.sum() with (longint'(item)) == 64'd5_000_000_000;
endclass
diagram
SUM WIDTH SEMANTICS

  rand bit [7:0] b[8];

  b.sum()                      b.sum() with (int'(item))
  ---------                    -------------------------
  8-bit accumulator            32-bit accumulator
  wraps mod 256                true arithmetic total
  +---+---+---+ ... wrap!      +-----------------+
  |255|+ 90|...| -> 8 bits     | 255 + 90 + ...  | -> 32 bits
  +---+---+---+                +-----------------+

  RULE OF THUMB:
  Any sum() over elements narrower than the target
  comparison value MUST widen via with (int'(item)).
  This is the single most common aggregate bug.

Interviewers love this trap precisely because the broken version compiles cleanly and sometimes "works" for small targets — it only bites when totals exceed the element width.


Counting and other reductions

The sum-of-booleans idiom turns counting problems into aggregate equations: a comparison inside the with clause yields 1 or 0 per element, and the sum is the count. This solves "exactly K elements equal V", "at most N errors", and similar problems in one line. The other reductions (product, bitwise and/or/xor) appear less often but follow identical width rules.

systemverilog
class counting;
  rand bit [7:0] data[10];
  rand bit [3:0] kind[8];

  constraint c {
    // exactly 3 elements equal to 8'h5A
    data.sum() with (int'(item == 8'h5A)) == 3;

    // at most 2 elements above 200
    data.sum() with (int'(item > 200)) <= 2;

    // index-aware: count of matches in the first half only
    data.sum() with (int'(item.index < 5 && item == 0)) == 1;

    // at least one element of each kind 0..3 (one sum per kind)
    kind.sum() with (int'(item == 0)) >= 1;
    kind.sum() with (int'(item == 1)) >= 1;
    kind.sum() with (int'(item == 2)) >= 1;
    kind.sum() with (int'(item == 3)) >= 1;
  }
endclass

class reductions;
  rand bit [7:0] mask[4];
  constraint c {
    // OR-reduction: at least one bit set somewhere
    mask.or() with (item) != 0;
    // XOR-reduction: overall parity is even
    mask.xor() with (item) == 8'h00;
  }
endclass

Note int'(item == V): the comparison is 1-bit, so widening it keeps the count accumulator at 32-bit — the same overflow rule applies to counting sums, though a count rarely exceeds 255 in practice. Casting is still the safe habit.


Interview angle

What interviewers probe

  • "Constrain 4 weights to total 100" — expected: weight.sum() with (int'(item)) == 100 plus per-element bounds; mentioning the cast unprompted signals real-world experience.

  • "Why does payload.sum() == 1000 fail on byte payloads?" — expected: sum accumulates in the element width (8-bit), wraps mod 256; widen with int'(item).

  • "Exactly K elements equal to V" — expected: the sum-of-booleans idiom, sum() with (int'(item == V)) == K.

  • "Checksum field equals payload sum" — expected: a single bidirectional equation; the solver can derive either side.

If asked to verify your constraint, the strong move is a quick post-randomize check loop — sum the elements procedurally with an int accumulator and assert it matches. It demonstrates you know the constraint and procedural worlds can disagree on width.

Key takeaways

  • Reduction methods create one equation coupling all elements — budgets, checksums, and counts in a single line.

  • sum() accumulates in the ELEMENT type's width — always widen with (int'(item)) when totals can exceed it.

  • Counting idiom: sum() with (int'(item == V)) == K expresses "exactly K matching elements".

  • Aggregate equations are bidirectional — the solver can derive the scalar from the array or shape the array to a forced scalar.

Common pitfalls

  • b.sum() == big_value on narrow elements without a cast — wraps in element width; unsatisfiable or wrongly satisfiable.

  • Forgetting per-element bounds alongside a sum — solver may pick one huge element and zeros, which is legal but useless stimulus.

  • Sum equation plus element bounds that cannot meet the total (e.g. 4 elements in [0:10] summing to 100) — over-constrained, randomize() fails.

  • Using item without a with clause or misnaming it — with (int'(item)) is the required form; item.index for position.

  • Assuming product() is practical on many elements — products explode numerically and choke solvers; prefer log-domain or restructure.