SystemVerilog · Module 10
Solve Before Constraints
Distribution-shaping directive (not legality) for asymmetric implication branches; two-phase pick-uniform-then-constrain semantics; the canonical mode/payload pair coverage fix.
Module 10 · Page 10.15
The Problem — Correlated Variables Have Skewed Distributions
When two rand variables are linked by an implication constraint, the solver picks uniformly from all valid solution tuples, not from each variable independently. If one branch of the implication has far more valid solutions than the other, the solver will heavily favour that branch — even if you intended both to be equally likely.
class BurstTxn;
rand bit mode; // 0 = single, 1 = burst
rand bit [7:0] length; // transfer length
constraint mode_length {
mode == 0 -> length == 1; // single: only 1 valid length
mode == 1 -> length inside {[2:255]}; // burst: 254 valid lengths
}
endclass
// Total valid (mode, length) tuples:
// mode=0: 1 tuple → (0, 1)
// mode=1: 254 tuples → (1,2), (1,3), ..., (1,255)
// Total: 255 tuples
// Solver picks uniformly from all 255:
// P(mode=0) = 1/255 ≈ 0.4% ← almost NEVER single transfers!
// P(mode=1) = 254/255 ≈ 99.6%
// You wanted 50/50. You got 0.4/99.6.The Fix — solve … before
solve A before B tells the solver to randomly assign A first (uniformly over its own valid range), then use that value as a fixed constant when solving B. This changes the distribution of A from "proportional to how many B solutions exist for each A value" to "uniform over A's own values."
class BurstTxn;
rand bit mode;
rand bit [7:0] length;
constraint mode_length {
mode == 0 -> length == 1;
mode == 1 -> length inside {[2:255]};
}
// Step 1: solve mode uniformly (50% each value)
// Step 2: solve length given the chosen mode
constraint ordering { solve mode before length; }
endclass
// With solve mode before length:
// Step 1: mode = 0 with P=50%, mode = 1 with P=50%
// Step 2: if mode=0 → length = 1 (the only option)
// if mode=1 → length uniform in [2:255]
// P(mode=0) = 50% ✓ as intended
// P(mode=1) = 50% ✓ as intendedWhat Changes and What Doesn't
| Aspect | Without solve before | With solve before A B |
|---|---|---|
| Legal solutions | Identical — same set of valid tuples | Identical — same set of valid tuples |
| randomize() return value | Same | Same |
| Distribution of A | Weighted by number of valid B values for each A | Uniform over A's own valid range |
| Distribution of B given A | Implicit joint | Uniform over B's valid range for the chosen A |
| Constraint correctness | All constraints satisfied | All constraints satisfied |
Syntax — Forms and Placement
solve … before is a statement inside a constraint block. It can name individual variables or comma-separated lists. Multiple solve before statements create a partial ordering.
class SolveDemos;
rand bit [1:0] op;
rand bit [7:0] addr;
rand bit [7:0] data;
rand bit write;
// ── Single variable before single variable ─────────────────────
constraint c1 { solve op before addr; }
// ── Multiple variables before another ─────────────────────────
constraint c2 { solve op, write before data; }
// ── Chained ordering: op → addr → data ────────────────────────
constraint c3 {
solve op before addr;
solve addr before data;
}
// ── Can live in the same block as other expressions ───────────
constraint full {
op inside {0, 1, 2}; // regular constraint
solve op before addr; // ordering hint in same block
}
endclassPractical Patterns
Pattern 1 — Transaction type controls payload range
typedef enum bit [1:0] { SETUP=0, DATA=1, ACK=2, NAK=3 } pkt_type_e;
class UsbPkt;
rand pkt_type_e ptype;
rand bit [9:0] len;
constraint len_by_type {
ptype == SETUP -> len == 8; // 1 valid len
ptype == DATA -> len inside {[0:512]}; // 513 valid lens
ptype == ACK -> len == 0; // 1 valid len
ptype == NAK -> len == 0; // 1 valid len
}
// Without this: DATA would be chosen ~513/516 ≈ 99.4% of the time
// With this: each type chosen 25% of the time
constraint fair_type { solve ptype before len; }
endclassPattern 2 — Opcode drives the number of operands
class Instruction;
rand bit [3:0] opcode;
rand bit [7:0] operand_a;
rand bit [7:0] operand_b;
// NOP (opcode 0): operands must be zero
// Single-operand ops (1–3): operand_b must be zero, operand_a free
// Dual-operand ops (4–15): both operands free
constraint operand_rules {
opcode == 0 -> { operand_a == 0; operand_b == 0; }
opcode inside {[1:3]} -> operand_b == 0;
}
// Solve opcode first so each of the 16 opcodes is equally likely
constraint order_c { solve opcode before operand_a, operand_b; }
endclass
// Without solve before:
// NOP: 1 × 1 = 1 tuple → ~0.002% of solutions
// Single (1-3): 3 × 256 = 768 tuples → ~1.5%
// Dual (4-15): 12 × 256² = 786432 tuples → ~98.5%
//
// With solve before:
// Each of 16 opcodes chosen uniformly → 6.25% eachWhen to Use solve before
Implication with asymmetric consequence ranges
Any time A -> B_range where different values of A lead to dramatically different numbers of valid B values. The "controlling" variable A should be solved before the "dependent" variable B.
Mode/type fields that gate other fields
Whenever a small field (opcode, type, mode) determines the valid range of a large field (length, operand, payload), solve the small field before the large one to get a fair distribution over modes.
Enum fields linked to data ranges
Protocol transaction type enums are a classic case. Without solve before, the type with the widest payload range dominates. With it, all types appear with equal (or weighted) frequency.
When you can predict the skew
Do a quick mental count: if one branch of your implication has N times more valid values than another, expect it to appear N times more often without solve before. If N > 2 and that's not your intent, add it.
Common Pitfalls
Creating a cycle — A before B and B before A
solve A before B combined with solve B before A is a circular ordering — a compile or runtime error. Ensure the ordering forms a directed acyclic graph (DAG).
Using it where dist is the right answer
If you simply want a weighted distribution (70% reads, 30% writes), dist is cleaner and more explicit. Use solve before only when the problem is an accidental skew caused by correlated variables.
Applying it when no skew exists
If all values of A lead to the same number of valid B values, the distribution of A is already uniform without solve before. Adding it is harmless but unnecessary. Only add it when you have identified an actual asymmetry.
Expecting solve before to change valid solutions
solve before only affects distribution — which valid tuple the solver picks. It cannot generate values that violate other constraints. If A=0 has no legal B value, the solver still fails (or backtracks).
Quick Reference
// ── Basic form ────────────────────────────────────────────────
constraint c { solve A before B; }
// ── Multiple variables before ─────────────────────────────────
constraint c { solve A before B, C; } // A before both B and C
constraint c { solve A, B before C; } // both A and B before C
// ── Chained ordering ──────────────────────────────────────────
constraint c {
solve A before B;
solve B before C; // A → B → C
}
// ── Mixed with other expressions ─────────────────────────────
constraint c {
A inside {0, 1, 2};
solve A before B;
}
// ── What it does ──────────────────────────────────────────────
// Without: P(A=x) ∝ number of valid B values when A=x
// With: P(A=x) = uniform over A's own valid range
// then B solved uniformly given the chosen A
// ── What it does NOT do ───────────────────────────────────────
// ✗ Does not change which tuples are legal
// ✗ Does not override other constraints
// ✗ Cannot create a cycle (A before B AND B before A → error)
// ── When to add it ────────────────────────────────────────────
// Add when: A → B_range and different A values have very different
// numbers of valid B values, and you want A to be uniformVerification Usage — Where solve before Pays Off in Real Tests
Three patterns recur across production environments. Each is a specific scenario where the solver's natural choice produces a heavily-skewed distribution that solve before corrects.
Mode/payload pairs with asymmetric ranges
The canonical case: a "mode" field selects between two payload ranges of vastly different sizes. Without solve before, the mode with the larger satisfying set dominates the random draw; with it, both modes get uniform coverage.
<code>class xact;
rand bit kind; // 0=SHORT, 1=LONG
rand bit [15:0] data;
constraint c_data {
kind == 0 -> data inside {[0:15]}; // SHORT: 16 values
kind == 1 -> data inside {[0:65535]}; // LONG: 65,536 values
solve kind before data; // ← uniform 50/50 between SHORT and LONG
}
endclass</code>Opcode/operand combinations in CPU testbenches
An instruction's opcode determines which operand encodings are legal. Without solve before, operand-rich opcodes dominate; with it, every opcode gets fair representation.
<code>class instr;
randc bit [3:0] op; // 16 opcodes; randc cycles them
rand bit [7:0] operand;
constraint c {
op == OP_NOP -> operand == 0; // NOP: 1 value
op == OP_LDI -> operand inside {[0:255]}; // LDI: 256 values
// ... many more opcodes ...
solve op before operand; // each opcode gets equal turn (via randc)
// operand then fills appropriately
}
endclass</code>Burst length / data array size dependencies
A burst transaction's length determines its data array size. Without solve before, short-length bursts are over-represented because they have smaller satisfying sets for the data array; with it, lengths distribute uniformly.
<code>class burst;
rand bit [3:0] len; // 0-15 → 1-16 beats
rand bit [31:0] data[];
constraint c {
data.size() == len + 1;
foreach (data[i]) data[i] inside {[1:'hFFFE]};
solve len before data; // uniform burst-length distribution
}
endclass</code>Simulation Behavior — How the Solver Honours solve before
Two-phase satisfaction
Without solve before, the solver does one joint solve over all rand fields — finding a satisfying assignment from the full Cartesian product of legal value combinations. The resulting distribution is naturally weighted by satisfying-set sizes: variables with more legal values are picked more often.
With solve a before b, the solver: (1) picks a uniformly from its own valid range, ignoring how b would constrain it; (2) given that a, picks b from its constrained range. The result: a is uniformly distributed; b's distribution is conditional on a.
Chains and cycles
solve a before b before c chains the same logic: pick a uniformly, then b given a, then c given a and b. Cycles (solve a before b; solve b before a;) are illegal and rejected by the compiler.
Legality is unchanged
The set of legal value combinations is identical with or without solve before. Only the probability distribution over that set changes. A class where every constraint set has exactly one satisfying assignment will produce the same single value regardless of solve before directives.
Cost characteristics
The two-phase solve typically costs more than a single joint solve — the solver runs effectively two sub-problems instead of one. For simple classes the overhead is negligible (microseconds). For complex constraint sets with many solve before directives, the overhead can be measurable but still usually justified by the distribution payoff.
<code>class xact;
rand bit kind;
rand bit [15:0] data;
constraint c {
kind == 0 -> data < 16;
kind == 1 -> data < 65536;
solve kind before data;
}
endclass
// After 1000 calls:
// kind=0 → ~500 (50%)
// kind=1 → ~500 (50%)
// SHORT and LONG both exercised</code><code>class xact;
rand bit kind;
rand bit [15:0] data;
constraint c {
kind == 0 -> data < 16;
kind == 1 -> data < 65536;
// No solve before
}
endclass
// After 1000 calls:
// kind=0 → ~0 (0.025%)
// kind=1 → ~1000 (99.975%)
// SHORT virtually never tested</code>Waveform Analysis — Verifying Distribution Matches Intent
solve before doesn't produce any waveform signal of its own — it shapes the distribution of constrained fields. The way to verify it's working is to instrument post_randomize with the "solved-first" field and aggregate over many calls.
The instrumentation pattern
<code>class xact extends uvm_sequence_item;
rand bit kind;
rand bit [15:0] data;
constraint c {
kind == 0 -> data inside {[0:15]};
kind == 1 -> data inside {[0:65535]};
solve kind before data;
}
function void post_randomize();
`uvm_info("RAND", $sformatf("kind=%0d data=0x%h", kind, data), UVM_HIGH)
endfunction
endclass</code>ASCII view — distribution validation
<code>After 1000 randomize() calls with `solve kind before data;`:
Coverpoint Hits Expected Status
----------------------------------------------------
kind=0 504 500 ✓ (~50%)
kind=1 496 500 ✓ (~50%)
data <= 0xF 504 500 ✓ matches kind=0 count
data > 0xF 496 500 ✓ matches kind=1 count
----------------------------------------------------
Distribution: balanced — solve before working correctly</code>Comparing with and without solve before
The most powerful verification: run the same test twice — once with solve before, once without — and compare the kind-distribution coverage reports. The difference between the two runs is exactly what the directive is buying you. If they look the same, the constraint set may not actually have the asymmetry you thought; if they differ dramatically, the directive is doing its job.
Industry Insights — Hard-Won Lessons About solve before
Debugging Academy — Five Real Bugs From solve before Misuse
"Coverage shows SHORT path never hit despite kind being random"
DEBUGA class has kind as a 1-bit rand field but coverage shows kind=0 fires only 0.02% of the time after 50,000 iterations.
<code>class xact;
rand bit kind;
rand bit [15:0] data;
constraint c {
kind == 0 -> data < 16; // SHORT mode: 16 satisfying values
kind == 1 -> data < 65536; // LONG mode: 65,536 satisfying values
}
// BUG: no solve before; solver picks (kind, data) jointly
// → kind=1 dominates because its satisfying set is 4096× larger
endclass</code>Without solve before, the solver picks uniformly over the joint satisfying space. Since (kind=1, any data) has 65,536 satisfying pairs and (kind=0, any data) has 16, the ratio is 4096:1. kind=0 almost never fires.
Add solve kind before data; to the constraint. Now the solver picks kind uniformly from {0, 1} first, then picks data within the appropriate range. 50/50 split achieved; both modes exercised.
"solve before doesn't change the distribution"
DEBUGA developer adds solve a before b; but the distribution coverage shows the same skew as before. The directive seems to have no effect.
<code>class xact;
rand bit [3:0] a;
rand bit [3:0] b;
constraint c {
a == 7 -> b == 3;
a != 7 -> b == 5;
solve a before b; // added by developer
}
endclass
// After 1000 calls: a=7 fires ~6.25%, expected ~6.25% — no change!</code>Here both implication branches have the same satisfying-set size for b (one value each). The solver picks a uniformly with or without solve before — the joint solver's "weighted by satisfying-set" preference doesn't favour either a value, so the distribution is already 6.25% per a value. solve before doesn't change anything because there was nothing to fix.
Diagnose first: print the satisfying-set sizes per branch before assuming solve before will help. If branches have equal sizes, the natural joint solve is already balanced. solve before only shifts distributions when the natural joint distribution would skew the variable.
"Compiler rejects cyclic solve before"
DEBUGCompilation fails with "cyclic solve-before relationship detected." The developer wrote two solve directives that reference each other.
<code>class xact;
rand bit [3:0] a;
rand bit [3:0] b;
constraint c {
a + b == 10;
solve a before b; // a first, then b
solve b before a; // b first, then a — BUG: cycle
}
endclass</code>solve before establishes a partial order between random variables. Two directives that form a cycle (a before b and b before a) are contradictory — the solver cannot honour both.
Pick one direction based on which variable you want uniformly distributed (the one solved first gets uniform distribution over its own range). If you want symmetric treatment, remove both directives — the joint solver will treat them equally without explicit ordering.
"solve before applied to non-rand variable silently does nothing"
DEBUGA developer writes solve mode before payload; where mode is not a rand field; the directive appears to have no effect on payload distribution.
<code>class xact;
bit mode; // not rand! set externally by test layer
rand bit [15:0] payload;
constraint c {
mode == 0 -> payload < 16;
mode == 1 -> payload < 65536;
solve mode before payload; // mode is not random; directive irrelevant
}
endclass</code>solve before orders the random variables. mode isn't random — its value is fixed before randomize by the test layer. The solver treats it as a constant input regardless of solve before; the directive doesn't change anything.
Either (a) make mode a rand field if you want it randomised with solve before applying, or (b) recognise that with mode non-rand the distribution of payload is already deterministic per call (depends only on what mode the test layer set). solve before is meaningful only when the variable to be solved first is actually being solved at all.
"solve before doesn't help when constraint set is over-determined"
DEBUGA developer adds solve a before b; but the distribution still looks identical to the without-directive version.
<code>class xact;
rand bit [3:0] a;
rand bit [3:0] b;
constraint c {
a == b; // tight bidirectional
a inside {[0:5]};
solve a before b;
}
endclass
// a and b are forced equal; "solve a first" makes no difference
// because b is fully determined by a once a is picked</code>The constraint set fully determines b given a (b == a means one satisfying value of b per value of a). The joint solver's distribution is already uniform over the 6 satisfying pairs; solve before doesn't change anything.
solve before is useful when the constraint set leaves multiple satisfying values for the consequent variable per antecedent choice — that's when the satisfying-set-size weighting can cause skew. When the consequent is fully determined, the directive is a no-op (but harmless). Verify whether the situation you're modelling has the asymmetric multiplicity before adding the directive.
Interview Q&A — Twelve Questions You Will Be Asked
It tells the constraint solver: when randomising, first pick a value for a uniformly from a's own valid range, then pick a value for b from the range constrained by the chosen a. Without the directive, the solver picks all rand fields jointly, which can produce skewed distributions when satisfying-set sizes differ between branches.
Best Practices — Ten Rules for solve before Discipline
- Use
solve beforewhen implication branches have asymmetric satisfying-set sizes. The pattern: mode/payload pairs where one mode has many legal values, another has few. - Document why each
solve beforeexists. A comment naming the distribution problem prevents "tidying up" later by removing what looks like noise. - Verify the directive's effect with per-mode coverage. One coverpoint per branch with the expected uniform-distribution bins. The coverage proves the directive is doing its job.
- Don't add
solve beforespeculatively for performance. The directive typically costs a small amount of solver time; the benefit is distribution shaping, not raw speed. - Recognise that
solve beforeis a no-op when the consequent has only one legal value per antecedent. Equal satisfying-set sizes → same distribution regardless. - Never form cycles between
solve beforedirectives. The compiler rejects them; rethink the constraint structure. - Apply
solve beforeto rand variables only. Non-rand variables are treated as constants; the directive is silently meaningless. - Compare distributions with and without the directive during validation. Run the same test twice (with/without) and compare coverage reports; the difference is exactly what the directive buys.
- Statistical analysis requires many iterations — at least 1000. Don't judge distribution correctness from short runs; random fluctuation dominates the signal.
- Pair
solve beforewith regression-quality coverage on the constrained variables. The directive's value is in the coverage closure rate; the coverage report is the verification.