Part 3 · Constraint Randomization · Intermediate

Constraint Anti-Patterns Review

Interviewer-bait wrong answers compiled: procedural thinking, over-constrained base classes, inside vs dist confusion, missing solve-before, width/sign traps, and randc misuse — broken code, symptom, fix.

How interviewers use anti-patterns

Senior interviews often flip the script: instead of “write the constraint”, you get broken code and “what’s wrong with this?” Each anti-pattern below shows the broken code, the runtime symptom (because half of these compile cleanly), and the fix. Knowing the symptom is what separates candidates who have debugged constraints from candidates who have read about them.

diagram
Legend: [TRAP]

  ANTI-PATTERN SYMPTOM TABLE                                  [TRAP]

  Anti-pattern                  Compiles?   Symptom
  ─────────────────────────────────────────────────────────────────
  Assignment in constraint      NO          compile error (the lucky one)
  Chained comparison a<x<b      YES         constraint vacuous, no error
  Over-constrained base class   YES         randomize() returns 0 in subclass
  inside used for weighting     YES         flat distribution, coverage holes
  Missing solve...before        YES         skewed distribution, rare-arm starvation
  Width/sign wrap in expr       YES         illegal values accepted as legal
  randc misuse                  varies      no cycling effect / compile error

  Worst bugs are the YES rows — only distribution analysis
  or coverage holes ever reveal them.

Anti-pattern 1 — Procedural thinking: assignments in constraints

systemverilog
// BROKEN — statements, not relations
constraint c_bad {
  x = y + 1;              // assignment: compile error
  if (mode == FAST)
    delay = 0;            // same disease
}

// FIX — equality relations; the solver "assigns" by solving
constraint c_good {
  x == y + 1;
  (mode == FAST) -> delay == 0;
}

Symptom: compile error — the kind anti-pattern, because it cannot ship. The deeper version of the disease survives compilation: thinking constraints execute top to bottom , asking “which line runs first?”. None runs first; all hold at once. Every later anti-pattern is this misconception wearing a different coat.


Anti-pattern 2 — Over-constraining the base class

systemverilog
// BROKEN — base txn nails fields tests will need to move
class base_txn;
  rand bit [31:0] addr;
  rand bit [3:0]  len;
  constraint c_addr { addr inside {[32'h1000:32'h1FFF]}; }  // "our block's range"
  constraint c_len  { len == 4; }                            // "typical length"
endclass

// Subclass for an error test:
class long_txn extends base_txn;
  constraint c_long { len inside {[8:15]}; }   // CONTRADICTS c_len
endclass
// Symptom: long_txn::randomize() returns 0 every call.

Fix hierarchy, best first: make base constraints soft so any hard subclass/inline constraint overrides them; or give them names and let subclasses constraint_mode(0) them; or override by redefining the same-named constraint in the subclass (same name replaces, new name ANDs — the detail interviewers fish for). The principle: base classes define legality , tests and subclasses define policy .

systemverilog
class base_txn_fixed;
  rand bit [3:0] len;
  constraint c_len { soft len == 4; }   // default, yields to any hard constraint
endclass

Anti-pattern 3 — inside where dist was needed (and vice versa)

systemverilog
// Spec: "mostly small packets, occasionally jumbo"
// BROKEN — inside is membership, every value equally likely
constraint c_size_bad { size inside {[64:128], 9000}; }
// P(size == 9000) = 1/66 ≈ 1.5% ... and each small size only ~1.5% too —
// but "jumbo" was supposed to be ~5%, "small" ~95%. No knob exists here.

// FIX — dist expresses the weighting the spec described
constraint c_size { size dist { [64:128] :/ 95, 9000 :/ 5 }; }

Symptom: nothing fails — coverage just closes slowly or unevenly, and someone eventually plots the size histogram. Reverse confusion also appears: using dist with equal weights where plain inside was meant — harmless but verbose, and on a whiteboard it signals fuzziness about what each operator is for : inside legislates legality, dist shapes probability.


Anti-pattern 4 — Missing solve...before under implication

systemverilog
// Spec: "error packets about half the time"
// BROKEN — uniform over (err, code) PAIRS, not over err
class pkt;
  rand bit        err;
  rand bit [7:0]  code;
  constraint c_code { err -> code inside {[1:255]};   // 255 legal pairs
                      !err -> code == 0; }            // 1 legal pair
endclass
// Symptom: err is 1 in ~255 of 256 solves — error path nearly always,
// or in the mirrored version, almost never. Distribution, not legality.

// FIX — pick err first, uniformly; then solve code within the arm
constraint c_order { solve err before code; }

This is the canonical solver-semantics interview question. The key sentence: the default solver is uniform over legal solution tuples , so an arm with more solutions soaks up probability; solve...before partitions the solve so the early variable is uniform over ITS values. It changes probability only — the legal set is identical with or without it, and it cannot rescue an infeasible constraint.


Anti-pattern 5 — Signed/width traps in constraint expressions

systemverilog
// BROKEN 1 — signed comparison surprise
class s1;
  rand int  delta;                 // SIGNED
  constraint c { delta < 10; }     // intended "small delay"
endclass
// Symptom: delta = -2 billion appears. "< 10" includes all negatives.
// FIX: delta inside {[0:9]}; — or declare int unsigned.

// BROKEN 2 — width wrap in arithmetic (the recurring classic)
class s2;
  rand bit [7:0] a, b;
  constraint c { a + b <= 200; }   // 8-bit add wraps at 256
endclass
// Symptom: a=200, b=100 → 300 wraps to 44 → "passes". Illegal pair accepted.
// FIX: int'(a) + int'(b) <= 200;

// BROKEN 3 — mixed sign comparison
class s3;
  rand int          x;
  rand bit [31:0]   y;
  constraint c { x > y - 100; }    // y-100 is UNSIGNED: y=5 → huge value
endclass
// Symptom: solve fails or x forced enormous when y < 100.
// FIX: x > int'(y) - 100;

One habit prevents all three: before writing any arithmetic constraint, say the operand types and widths out loud, and widen or sign-cast at the operands — casting the result is too late, the wrap already happened inside the expression.


Anti-pattern 6 — randc misuse

systemverilog
// BROKEN 1 — expecting randc to make array elements unique in one call
class r1;
  rand randc bit [3:0] arr[8];   // many tools reject; concept wrong anyway
endclass
// FIX: constraint { unique {arr}; }

// BROKEN 2 — dist on a randc variable
class r2;
  randc bit [1:0] sel;
  constraint c { sel dist { 0 := 8, [1:3] :/ 2 }; }   // ILLEGAL
endclass
// randc = exhaustive cycling; dist = weighting. Mutually exclusive.

// BROKEN 3 — expecting cross-object cycling
repeat (16) begin
  pkt p = new();        // NEW object each time
  void'(p.randomize()); // each p's randc starts its own fresh cycle
end
// Symptom: repeats appear; "randc isn't working".
// FIX: one object, randomize() repeatedly — cycling is per-instance state.

// BROKEN 4 — wide randc
// randc bit [31:0] huge;  → 4-billion-entry permutation; tools cap randc
// width and error or silently degrade. Keep randc narrow (≤ 8-16 bits).

The mental model that fixes all four: randc is per-variable, per-instance permutation state advanced by successive randomize() calls on the same object . It is not uniqueness within a call, not weightable, not shared across instances, and not free for wide vectors.


Drill yourself

Spot-the-bug checklist — run it on any constraint you write

  1. Any assignments or statement-thinking? Relations only.

  2. Any chained comparison (a < x < b)? Split it.

  3. Any arithmetic that can exceed operand width? Cast operands up.

  4. Any signed/unsigned mixing in comparisons? Make signedness explicit.

  5. Any implication missing its other arm? Cover the else.

  6. Distribution intended anywhere? inside won’t weight it — dist will.

  7. Implication + “the rare arm never fires”? solve...before.

  8. Base-class constraint a test will fight? soft it or name it for constraint_mode.

  9. randc anywhere? Confirm cycling-across-calls is really what is needed.

  10. Is randomize() return checked? Infeasibility must fail loudly.

Key takeaways

  • Constraints are simultaneous relations — every anti-pattern is procedural thinking in disguise.

  • The dangerous bugs compile: vacuous comparisons, width wraps, skewed implications.

  • Base classes legislate legality; tests set policy — soft defaults keep the hierarchy flexible.

  • randc: per-instance cycling across calls; never uniqueness within a call, never weightable.

Common pitfalls

  • Reviewing constraints by reading top-to-bottom like code — order is meaningless; review as a relation set.

  • Trusting a clean compile — the worst constraint bugs only show up in distribution plots.

  • Fixing distribution skew by adding more constraints instead of solve...before — changes legality by accident.

  • Leaving randomize() unchecked while debugging any of these — the failure signal is thrown away.