Now let's take a look at some of the common ways of writing constraint expressions inside a constraint block.
14 min read | Intermediate Level
What You'll Learn
- Why only one relational operator is allowed per expression, and how to fix a variable to an exact value using
== - How
insidecompares to writing out explicit relational bounds, and its inverted form - How
:=and:/produce genuinely different weighted distributions from similar-looking numbers - Why constraint blocks all apply simultaneously rather than being processed top-to-bottom, demonstrated with two blocks that together narrow a range further than either alone
Simple Expressions
There can be only one relational operator (< <= > >=) in a single expression:
class MyClass;
rand bit [7:0] min, typ, max;
// Valid expression
constraint my_range { 0 < min;
typ < max;
typ > min;
max < 128; }
// Use of multiple operators in a single expression is not allowed
constraint my_error { 0 < min < typ < max < 128; }
// This will set min to 16 and randomize all others
constraint my_min { min == 16; }
// This will set max to a random value greater than or equal to 64
constraint my_max { max >= 64; }
endclass
You can't make assignments inside a constraint block, since it only contains expressions. Instead, use the equivalence operator ==, as shown for my_min above, where min gets a value of 16 and every other variable is randomized normally. This is one way to fix a particular value to a variable, even while the solver randomizes everything else:
constraint my_min { min == temp.low * 9/5 + 32; }
You can also use a more complex expression like this one, where min represents a variable storing Fahrenheit and low is a variable within the class object temp holding a temperature in Celsius — letting one field stay mathematically derived from another, rather than being independently randomized.
Example
class myClass;
rand bit [3:0] min, typ, max;
rand bit [3:0] fixed;
constraint my_range { 3 < min;
typ < max;
typ > min;
max < 14; }
constraint c_fixed { fixed == 5; }
function string display ();
return $sformatf ("min=%0d typ=%0d max=%0d fixed=%d", min, typ, max, fixed);
endfunction
endclass
module tb;
initial begin
for (int i = 0; i < 10; i++) begin
myClass cls = new ();
cls.randomize();
$display ("itr=%0d %s", i, cls.display());
end
end
endmodule
The fixed variable is pinned to the value 5, while the others are randomized within the bounds of my_range:
run -all; # KERNEL: itr=0 min=7 typ=9 max=12 fixed= 5 # KERNEL: itr=1 min=4 typ=9 max=12 fixed= 5 # KERNEL: itr=2 min=7 typ=10 max=13 fixed= 5 # KERNEL: itr=3 min=4 typ=6 max=11 fixed= 5 # KERNEL: itr=4 min=8 typ=12 max=13 fixed= 5 # KERNEL: itr=5 min=5 typ=6 max=13 fixed= 5 # KERNEL: itr=6 min=6 typ=9 max=13 fixed= 5 # KERNEL: itr=7 min=10 typ=12 max=13 fixed= 5 # KERNEL: itr=8 min=5 typ=11 max=13 fixed= 5 # KERNEL: itr=9 min=6 typ=9 max=11 fixed= 5 # KERNEL: Simulation has finished. There are no more test vectors to simulate. exit
Every row satisfies all three conditions at once: min greater than 3, min < typ < max, and max under 14.
Quick Check
Q: Why is 0 < min < typ < max < 128 (as written in my_error) not legal, when each individual comparison looks valid on its own?
Show Answer
Chaining multiple relational operators together in a single expression isn't allowed — each comparison has to be its own separate expression, implicitly ANDed with the others, exactly as my_range writes them out individually.
inside Operator
You can specify a lower and upper limit as an alternative to writing out the relational expressions, using inside (covered in full depth in SystemVerilog Constraint 'inside'):
constraint my_range { typ > 32;
typ < 256; }
// typ >= 32 and typ <= 256
constraint new_range { typ inside {[32:256]}; }
// Choose from the following values
constraint spec_range { typ inside {32, 64, 128}; }
An inside range includes both its lower and upper limits. SystemVerilog gathers all the possible values and chooses among them with equal probability, unless some other constraint also applies to the same variable.
Inverted inside Operator
For any value outside a specific range, invert the constraint with !. This produces a random value from 0 to 7 (since typ is a 3-bit variable), excluding whatever falls within the specified range:
rand bit [2:0] typ;
constraint inv_range { ! (typ inside {[3:6]}); }
Repeated randomization gives every value except those falling within 3 through 6:
run -all; # KERNEL: itr=0 typ=7 # KERNEL: itr=1 typ=0 # KERNEL: itr=2 typ=7 # KERNEL: itr=3 typ=0 # KERNEL: itr=4 typ=0 # KERNEL: itr=5 typ=0 # KERNEL: itr=6 typ=7 # KERNEL: itr=7 typ=1 # KERNEL: itr=8 typ=1 # KERNEL: itr=9 typ=7 # KERNEL: Simulation has finished. There are no more test vectors to simulate. exit
Every value shown — 0, 1, and 7 — falls outside the excluded 3–6 range, exactly as the constraint requires.
Weighted Distributions
The dist operator creates weighted distributions so some values are chosen more often than others. := gives every listed value in a range that same weight individually; :/ splits a weight evenly across the values in a range.
:= Operator
rand bit [2:0] typ;
constraint dist1 { typ dist { 0:=20, [1:5]:=50, 6:=40, 7:=10}; }
In dist1, the weight of 0 is 20, 6 is 40, and 7 is 10, while each of 1 through 5 individually gets 50 — for a total weight of 20 + (5×50) + 40 + 10 = 320. The probability of choosing 0 is 20/320, and the combined probability of landing anywhere between 1 and 5 is 250/320:
class myClass;
rand bit [2:0] typ;
constraint dist1 { typ dist { 0:=20, [1:5]:=50, 6:=40, 7:=10}; }
endclass
module tb;
initial begin
for (int i = 0; i < 10; i++) begin
myClass cls = new ();
cls.randomize();
$display ("itr=%0d typ=%0d", i, cls.typ);
end
end
endmodule
Output
run -all; # KERNEL: itr=0 typ=5 # KERNEL: itr=1 typ=1 # KERNEL: itr=2 typ=6 # KERNEL: itr=3 typ=3 # KERNEL: itr=4 typ=2 # KERNEL: itr=5 typ=3 # KERNEL: itr=6 typ=0 # KERNEL: itr=7 typ=5 # KERNEL: itr=8 typ=1 # KERNEL: itr=9 typ=4 # KERNEL: Simulation has finished. There are no more test vectors to simulate. exit
Values 1 through 5 appear far more often than 0, 6, or 7 — 8 of the 10 draws land in that range, consistent with its dominant combined weight.
:/ Operator
rand bit [2:0] typ;
constraint dist2 { typ dist { 0:/20, [1:5]:/50, 6:/40, 7:/10}; }
This weight assignment (0:/20, [1:5]:/50, 6:/40, 7:/10) is what's actually used below, matching the full worked example. A version of this same snippet circulating elsewhere shows 6:/10, 7:/20 instead — those two numbers were swapped by mistake; the weights confirmed by both the worked example and its captured output are 6:/40 and 7:/10.
Here, the weight of 0 is 20, 6 is 40, and 7 is 10, while 1 through 5 share a total weight of 50 — 10 each — for a total of 20 + 50 + 40 + 10 = 120. The probability of choosing 6 is 40/120, the highest of any single value, and the probability of choosing 0 is 20/120:
class myClass;
rand bit [2:0] typ;
constraint dist2 { typ dist { 0:/20, [1:5]:/50, 6:/40, 7:/10}; }
endclass
module tb;
initial begin
for (int i = 0; i < 10; i++) begin
myClass cls = new ();
cls.randomize();
$display ("itr=%0d typ=%0d", i, cls.typ);
end
end
endmodule
Output
run -all; # KERNEL: itr=0 typ=5 # KERNEL: itr=1 typ=6 # KERNEL: itr=2 typ=4 # KERNEL: itr=3 typ=6 # KERNEL: itr=4 typ=6 # KERNEL: itr=5 typ=4 # KERNEL: itr=6 typ=2 # KERNEL: itr=7 typ=3 # KERNEL: itr=8 typ=4 # KERNEL: itr=9 typ=6 # KERNEL: Simulation has finished. There are no more test vectors to simulate. exit
6 appears more than any other single value — 4 out of 10 draws — consistent with its 40/120 weight being the highest of any individual value in the distribution.
Quick Check
Q: In dist2, is any single value in [1:5] more or less likely to be chosen than the value 7?
Show Answer
Equally likely. Each individual value in [1:5] gets 50/5 = 10 out of the shared range weight, and 7 gets 10 on its own — both work out to the same 10/120 individual probability, even though they were written using different operators.
Bidirectional Constraints
Constraint blocks aren't executed top to bottom like procedural code — every active block applies at the same time, exactly as covered in SystemVerilog Constraint Blocks. Here are two separate blocks on the same variable, combining to narrow the legal range further than either one alone:
class myClass;
rand bit [3:0] val;
constraint c1 { val > 3;
val < 12; }
constraint c2 {val >= 10; }
endclass
module tb;
initial begin
for (int i = 0; i < 10; i++) begin
myClass cls = new ();
cls.randomize();
$display ("itr=%0d typ=%0d", i, cls.val);
end
end
endmodule
c1 alone permits 4 through 11; c2 alone permits 10 and above. Together, they limit val to just 10 and 11:
run -all; # KERNEL: itr=0 typ=11 # KERNEL: itr=1 typ=11 # KERNEL: itr=2 typ=11 # KERNEL: itr=3 typ=10 # KERNEL: itr=4 typ=11 # KERNEL: itr=5 typ=10 # KERNEL: itr=6 typ=10 # KERNEL: itr=7 typ=10 # KERNEL: itr=8 typ=10 # KERNEL: itr=9 typ=10 # KERNEL: Simulation has finished. There are no more test vectors to simulate. exit
Where Is This Used?
Fixing one field to an exact value (or a value derived from another field, like the Fahrenheit-from-Celsius example above) while randomizing the rest is a common pattern for fields that need to stay mathematically consistent with something else in the same object, rather than being independently random on their own.
Common Beginner Mistakes
Mistake #1: Chaining multiple relational operators in one expression
As my_error shows, 0 < min < typ < max < 128 isn't legal, even though each comparison looks valid individually. Why? Each relational comparison has to be its own separate expression — write them out individually, and the solver ANDs them together automatically.
Mistake #2: Trying to use = instead of == to fix a value
A constraint block only contains expressions, not procedural assignments. Why? = denotes assignment, which has no meaning inside a declarative constraint — == is the equality expression that actually pins a variable to a specific value for the solver.
Mistake #3: Confusing := with :/
As the dist1/dist2 comparison shows, these produce genuinely different distributions from very similar-looking numbers. Why? := gives every listed value that same weight individually; :/ splits one weight across an entire range — mixing them up silently changes which values actually dominate the distribution.
Mistake #4: Assuming constraint blocks are applied in the order they're written
As the bidirectional example shows, c1 and c2 combine into a single narrower range regardless of which one appears first in the source. Why? All active constraint blocks are solved as one simultaneous system — there's no sequential processing to reason about.
Practice Exercise
Challenge: Combine Two Ranges
Given constraint c1 { val >= 5; val <= 20; } and constraint c2 { val <= 12; } on the same variable, what's the combined legal range for val?
Hint
Both blocks apply simultaneously — find the overlap between what each one permits on its own.
Solution
5 through 12. c1 alone permits 5 through 20; c2 alone permits anything up to 12. The only values satisfying both at once are 5 through 12 — the same range-intersection pattern demonstrated in this article's own c1/c2 example, just with different bounds.
Summary
In this tutorial, you learned:
- Only one relational operator is allowed per constraint expression; chained comparisons must be written as separate expressions
==, not=, is how you fix a variable to a specific value inside a declarative constraint block:=assigns a weight to each listed value individually;:/divides a weight across an entire range — a small difference in syntax with a large difference in resulting distribution- All active constraint blocks on a variable apply simultaneously, and together narrow the legal range to their intersection, regardless of the order they're written in