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
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.
endclassSAME 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.
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%.)
endclassCombine 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.