The SystemVerilog constraint solver, by default, tries to give a uniform distribution across the entire space of valid combinations — not necessarily a uniform distribution for each individual variable on its own. solve...before can change that, shifting probability so certain corner cases get chosen far more often than they otherwise would. This article compares an example with and without it.
14 min read | Intermediate Level
What You'll Learn
- Why the solver aims for a uniform distribution across the whole solution space by default, not independently for each variable
- How that default behavior can make one variable's value far less likely than you might expect, just because it happens to leave fewer combinations available
solve...before, and how it gives a variable an independent, even distribution instead- The restrictions on which variables can be ordered this way
Random Distribution Example
A 3-bit random variable b has 8 legal values, and the probability of b landing on any one of them is the same:
class ABC;
rand bit [2:0] b;
endclass
module tb;
initial begin
ABC abc = new;
for (int i = 0; i < 10; i++) begin
abc.randomize();
$display ("b=%0d", abc.b);
end
end
endmodule
Output
ncsim> run b=7 b=7 b=2 b=1 b=6 b=4 b=2 b=4 b=0 b=1 ncsim: *W,RNQUIE: Simulation is complete.
Repeated values in the output don't mean anything special — a previous randomization has no effect on the current one, so the randomizer is free to pick any of the 8 values regardless of what came before. Every one of the 8 legal values has an equal chance of being chosen.
Without solve...before
Now consider two random variables, where the constraint ensures b gets 0x3 whenever a is 1:
class ABC;
rand bit a;
rand bit [1:0] b;
constraint c_ab { a -> b == 3'h3; }
endclass
module tb;
initial begin
ABC abc = new;
for (int i = 0; i < 8; i++) begin
abc.randomize();
$display ("a=%0d b=%0d", abc.a, abc.b);
end
end
endmodule
Output
ncsim> run a=0 b=0 a=0 b=1 a=0 b=0 a=0 b=1 a=0 b=2 a=1 b=3 a=0 b=3 a=0 b=3 ncsim: *W,RNQUIE: Simulation is complete.
a and b are determined together, not one after the other. When a is 0, b can take any of 4 values — 4 valid combinations. When a is 1, b can only be 3 — just 1 valid combination. That's 5 valid (a, b) pairs total, and by default the solver treats all 5 as equally likely:
| a | b | Probability |
|---|---|---|
| 0 | 0 | 1/5 (20%) |
| 0 | 1 | 1/5 (20%) |
| 0 | 2 | 1/5 (20%) |
| 0 | 3 | 1/5 (20%) |
| 1 | 3 | 1/5 (20%) |
Because only 1 of those 5 equally-likely combinations has a=1, the overall probability of a ending up 1 is just 1/5, or 20% — noticeably less than the 50/50 you might expect from a single-bit variable on its own.
Quick Check
Q: If b were 3 bits wide (8 possible values) instead of 2, with the same a -> b == 7 style constraint, would P(a=1) go up or down compared to this 20% example?
Show Answer
Down — to 1/9, about 11%. There would be 8 valid combinations with a=0 (one per value of b) plus 1 with a=1, for 9 total. A wider b means more combinations share the "pie," shrinking a=1's relative share even further.
With solve...before
SystemVerilog lets you order variables so one is chosen independently of another, using the solve keyword:
class ABC;
rand bit a;
rand bit [1:0] b;
constraint c_ab { a -> b == 3'h3;
// Tells the solver that "a" has
// to be solved before attempting "b"
// Hence value of "a" determines value
// of "b" here
solve a before b;
}
endclass
module tb;
initial begin
ABC abc = new;
for (int i = 0; i < 8; i++) begin
abc.randomize();
$display ("a=%0d b=%0d", abc.a, abc.b);
end
end
endmodule
Output
ncsim> run a=1 b=3 a=1 b=3 a=0 b=1 a=0 b=0 a=0 b=0 a=0 b=1 a=1 b=3 a=0 b=2 ncsim: *W,RNQUIE: Simulation is complete.
Compare this against the previous output — a=1 now shows up far more often. a is solved first, and whatever it gets determines how b is solved next:
| a | b | Probability |
|---|---|---|
| 0 | 0 | 1/2 × 1/4 = 1/8 (12.5%) |
| 0 | 1 | 1/2 × 1/4 = 1/8 (12.5%) |
| 0 | 2 | 1/2 × 1/4 = 1/8 (12.5%) |
| 0 | 3 | 1/2 × 1/4 = 1/8 (12.5%) |
| 1 | 3 | 1/2 (50%) |
Because a is now solved on its own, the probability of it landing on either 0 or 1 is a flat 50%. Only once a is settled does the solver work out b — freely among 4 values if a=0 (each getting a quarter of that 50%, or 12.5% overall), or forced straight to 3 if a=1 (keeping the whole 50%, since there's nothing left to choose). P(a=1) has jumped from 20% to 50%, exactly the shift solve...before is meant to produce.
Quick Check
Q: With solve a before b, does the constraint a -> b == 3'h3 still hold in every generated combination?
Show Answer
Yes. solve...before only changes the probability weighting among already-legal combinations — it never makes an illegal combination possible or removes a legal one. Every row in both probability tables still satisfies the original constraint.
Restrictions
A few restrictions apply to solve...before:
randcvariables aren't allowed, since they're always solved first regardless- Variables should be integral values
- There must be no circular dependency in the ordering, such as
solve a before bcombined withsolve b before a
Where Is This Used?
solve...before is commonly used to boost the probability of a specific, interesting corner case — like an error-injection condition — that would otherwise get drowned out by the sheer number of "normal" combinations in the total solution space. Exactly the a=1 (error case) versus a=0 (normal case) pattern demonstrated in this article.
Common Beginner Mistakes
Mistake #1: Assuming every legal value is independently equally likely by default
As the "Without solve...before" example shows, the solver aims for uniform distribution across the entire joint solution space, not per variable. Why? A variable's value can end up much rarer than expected if it happens to correspond to fewer overall valid combinations than its alternatives.
Mistake #2: Assuming solve...before changes which values are legal
As the second Quick Check shows, it doesn't — the constraint still holds in every case. Why? solve...before only re-weights the probability among combinations that were already valid; it has no effect on legality at all.
Mistake #3: Trying to order a randc variable with solve...before
This isn't allowed. Why? A randc variable is always solved first regardless of any ordering hint, since its cyclic behavior depends on being resolved independently of everything else.
Mistake #4: Creating a circular solve...before ordering
solve a before b combined with solve b before a is illegal. Why? The solver needs a well-defined order to resolve variables in — a circular dependency gives it no valid starting point.
Practice Exercise
Challenge: Compute the Probability Shift
Given rand bit a;, rand bit [2:0] b; (3 bits this time), and constraint c { a -> b == 3'h7; }, compute P(a=1) both without and with solve a before b;.
Hint
Without solve...before, count the total valid (a,b) combinations and treat them as equally likely. With it, a is solved independently first.
Solution
Without solve...before: P(a=1) = 1/9 ≈ 11.1%. There are 8 valid combinations with a=0 (one per value of b) and 1 with a=1, for 9 total, all equally likely.
With solve a before b;: P(a=1) = 50%. Once a is solved on its own with a flat 50/50 split, b is resolved afterward — forced to 7 whenever a=1, so that entire 50% stays intact regardless of how many values b could otherwise take.
Summary
In this tutorial, you learned:
- By default, the constraint solver aims for a uniform distribution across the entire joint solution space, not independently for each variable
- This can make a variable's value much less likely than expected if it corresponds to fewer overall valid combinations than its alternatives
solve a before b;givesaan independent, even distribution, then resolvesbbased on whateveraturned out to besolve...beforechanges probability weighting only — it never changes which combinations are actually legalrandcvariables can't be ordered withsolve...before, and circular orderings are illegal