Part 3 · Constraint Randomization · Intermediate

solve...before & Distribution Shaping

What solve...before changes (probability, not the solution set), the classic flag->value skew, and restrictions.

What solve...before actually changes

solve a before b; is the most misunderstood line in SystemVerilog constraints. It does not add or remove any legal solution — the solution set is identical with or without it. What it changes is probability : the solver first picks a uniformly over a's legal values, then picks b uniformly among values consistent with that choice of a. Without the hint, the solver is uniform over complete (a, b) solution pairs — so a variable whose values pair with very different numbers of partner values gets skewed.

Memorize the one-liner: solve...before reshapes the distribution, never the legality. If randomize() fails, solve...before can never fix it; if a value is illegal, solve...before can never produce it.


The classic flag -> value example, before and after

systemverilog
class skew_demo;
  rand bit       flag;
  rand bit [3:0] val;        // 16 values: 0..15

  constraint c { flag -> val == 9; }
  // WITHOUT solve...before:
  //   Solutions: (flag=1, val=9)            →  1 pair
  //              (flag=0, val=0..15)        → 16 pairs
  //   Uniform over 17 pairs → P(flag==1) = 1/17 ≈ 5.9%
endclass

class shaped_demo;
  rand bit       flag;
  rand bit [3:0] val;

  constraint c { flag -> val == 9; }
  constraint order_c { solve flag before val; }
  // WITH solve flag before val:
  //   Step 1: pick flag uniformly over its legal values {0,1} → 50/50
  //   Step 2: pick val given flag:
  //           flag==1 → val must be 9
  //           flag==0 → val uniform over 0..15
  //   P(flag==1) = 1/2 — same 17 legal pairs, different weights.
endclass
diagram
SAME SOLUTION SET, TWO DISTRIBUTIONS

  Solution pairs:   (1,9)  (0,0) (0,1) (0,2) ... (0,15)
                      │      └────────── 16 pairs ─────┘
  WITHOUT solve...before (uniform over pairs):
     each pair = 1/17
     P(flag=1) = 1/17 ≈ 5.9%        P(flag=0) = 16/17

  WITH solve flag before val:
     flag picked first: P(flag=1) = 1/2
     (1,9)        = 1/2
     (0,k) each   = 1/2 × 1/16 = 1/32
     P(flag=1) = 50%

  ► The set of reachable pairs is IDENTICAL.
  ► Only the probability mass moved.

Notice step 1 says “uniformly over flag's legal values.” If some flag value had no consistent val at all, that flag value would not be legal in the first place — solve...before never resurrects impossible combinations.


When you actually need it

Reach for solve...before when a control variable selects between solution sub-spaces of very different sizes and you want the control to stay balanced. Typical cases: an error/normal flag where the error sub-space is tiny, an opcode that gates wide payload ranges, or a length-class selector feeding a length field.

systemverilog
class bus_txn;
  rand bit        inject_err;
  rand bit [31:0] addr;

  // Errors only at one magic address; normal traffic anywhere else
  constraint err_c {
    inject_err -> addr == 32'hDEAD_BEEF;
    solve inject_err before addr;
  }
  // Without the hint: P(inject_err) ≈ 1 / 2^32 — you would never see an error.
  // With the hint:    P(inject_err) = 1/2 — every other txn injects.
  // (In practice you would also dist inject_err to taste, e.g. 10%.)
endclass

Combine with dist for fine control: solve the flag first, and put a dist on the flag for the exact ratio you want. The two mechanisms are orthogonal — dist sets the flag's own distribution, solve...before stops the partner variable from drowning it.


Restrictions and rules

  • Only rand variables may appear in solve...before — randc is forbidden, because randc variables are always implicitly solved before all rand variables anyway.

  • It is a hint about ordering, not a functional constraint — tools must honor the distribution effect, but it cannot make an unsolvable set solvable.

  • Circular orderings (solve a before b; solve b before a;) are illegal.

  • You can list several variables: solve a, b before c, d; — partial order, not a total program order.

  • No effect when the variables are independent — if a and b share no constraint, ordering changes nothing.


Interview angle

What interviewers ask

  • “Does solve...before change the legal solutions?” — No. Same set, different probability. Lead with this sentence.

  • “Walk me through P(flag) with and without it” — 1/17 vs 1/2 for the flag/val==9 example; count the pairs out loud.

  • “Can you use it with randc?” — no; randc is implicitly solved first and cannot appear in solve...before.

  • “If randomize() is failing, will solve...before help?” — never; failures mean an empty solution set, which ordering cannot fix.

  • “How is it different from dist?” — dist weights one variable's own values; solve...before removes skew caused by a partner variable's pair counts.

Key takeaways

  • solve...before changes probability only — the solution set is bit-for-bit identical.

  • Default solving is uniform over complete solution tuples, which skews flags gating large spaces.

  • Picking the flag first restores its 50/50 (or dist-specified) distribution.

  • randc cannot appear in solve...before; randc is implicitly solved before all rand variables.

  • It cannot fix randomize() failures — an empty set stays empty under any ordering.

Common pitfalls

  • Using solve...before to “fix” a contradiction — it cannot; the failure is a set problem, not an order problem.

  • Expecting the flag/val skew to be small — for a 32-bit partner it is 1 in 4 billion, effectively never.

  • Adding solve...before everywhere “for safety” — it constrains the solver's freedom and can slow solving.

  • Putting a randc variable in the ordering — compile error.

  • Creating circular solve...before chains across constraint blocks — illegal and tool-flagged.