Skip to content

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.

SystemVerilog — the bias problem with correlated variables
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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."

SystemVerilog — solve before fixes the distribution
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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 intended

What Changes and What Doesn't

AspectWithout solve beforeWith solve before A B
Legal solutionsIdentical — same set of valid tuplesIdentical — same set of valid tuples
randomize() return valueSameSame
Distribution of AWeighted by number of valid B values for each AUniform over A's own valid range
Distribution of B given AImplicit jointUniform over B's valid range for the chosen A
Constraint correctnessAll constraints satisfiedAll 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.

SystemVerilog — solve before syntax variants
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
    }
endclass

Practical Patterns

Pattern 1 — Transaction type controls payload range

SystemVerilog — type controls length, solve before ensures fair type split
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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; }
endclass

Pattern 2 — Opcode drives the number of operands

SystemVerilog — opcode determines operand validity
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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% each

When 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

SystemVerilog — solve before cheat sheet
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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 uniform

Verification 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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.

✅ With solve before — uniform mode
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
❌ Without solve before — skewed
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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       500matches kind=0 count
  data > 0xF      496       500matches kind=1 count
  ----------------------------------------------------
  Distribution: balancedsolve 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

1

"Coverage shows SHORT path never hit despite kind being random"

DEBUG
Symptom

A 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.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
Root Cause

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.

Fix

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.

2

"solve before doesn't change the distribution"

DEBUG
Symptom

A developer adds solve a before b; but the distribution coverage shows the same skew as before. The directive seems to have no effect.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
Root Cause

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.

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.

3

"Compiler rejects cyclic solve before"

DEBUG
Symptom

Compilation fails with "cyclic solve-before relationship detected." The developer wrote two solve directives that reference each other.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
Root Cause

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.

Fix

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.

4

"solve before applied to non-rand variable silently does nothing"

DEBUG
Symptom

A developer writes solve mode before payload; where mode is not a rand field; the directive appears to have no effect on payload distribution.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
Root Cause

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.

Fix

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.

5

"solve before doesn't help when constraint set is over-determined"

DEBUG
Symptom

A developer adds solve a before b; but the distribution still looks identical to the without-directive version.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
Root Cause

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.

Fix

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

  1. Use solve before when implication branches have asymmetric satisfying-set sizes. The pattern: mode/payload pairs where one mode has many legal values, another has few.
  2. Document why each solve before exists. A comment naming the distribution problem prevents "tidying up" later by removing what looks like noise.
  3. 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.
  4. Don't add solve before speculatively for performance. The directive typically costs a small amount of solver time; the benefit is distribution shaping, not raw speed.
  5. Recognise that solve before is a no-op when the consequent has only one legal value per antecedent. Equal satisfying-set sizes → same distribution regardless.
  6. Never form cycles between solve before directives. The compiler rejects them; rethink the constraint structure.
  7. Apply solve before to rand variables only. Non-rand variables are treated as constants; the directive is silently meaningless.
  8. 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.
  9. Statistical analysis requires many iterations — at least 1000. Don't judge distribution correctness from short runs; random fluctuation dominates the signal.
  10. Pair solve before with regression-quality coverage on the constrained variables. The directive's value is in the coverage closure rate; the coverage report is the verification.