SystemVerilog · Module 10
Constraint Blocks
Named declarative protocol contracts, three architectural roles (legality/shaping/injection), joint-AND semantics with no semicolon sequencing, and the test-layer reach via constraint_mode.
Module 10 · Page 10.4
Anatomy of a Constraint Block
A constraint block is a named, declarative statement inside a class that tells the solver which values are legal for one or more rand variables. The keyword is constraint, followed by a name you choose, then a block of constraint expressions enclosed in { }.
class Packet;
rand bit [7:0] src;
rand bit [7:0] dst;
rand bit [15:0] len;
rand bit [1:0] prio;
// ── Constraint 1: single expression ───────────────────────────
constraint no_broadcast {
src != 8'hFF; // src can be anything except 0xFF
}
// ── Constraint 2: range using relational operators ────────────
constraint valid_length {
len >= 64;
len <= 1518; // both expressions must hold simultaneously
}
// ── Constraint 3: the inside operator (set membership) ────────
constraint valid_prio {
prio inside {0, 1, 2, 3}; // same as no constraint for 2-bit prio,
} // but useful once combined with exclusions
// ── Constraint 4: cross-variable relationship ─────────────────
constraint src_ne_dst {
src != dst; // source and destination must differ
}
endclassOperators Inside Constraint Blocks
Most SystemVerilog expressions are legal inside constraints. The table below lists the ones you will use daily.
| Operator / Form | Meaning | Example |
|---|---|---|
== != | Equal / not equal | src != 8'hFF |
< <= > >= | Relational comparison | len >= 64 |
inside { set } | Value is a member of the set | opcode inside {3'h0, 3'h1, 3'h4} |
inside { [lo:hi] } | Value is within a closed range | len inside {[64:1518]} |
! (negation) | Logical NOT — negate any constraint expression | !(src inside {[0:9]}) |
&& || | Logical AND / OR within a single expression | x > 0 && x < 100 |
-> | Implication (if → then) — covered in page 10.9 | write -> (addr[1:0] == 0) |
Arithmetic + - * / | Arithmetic on rand variables | end_addr == start_addr + len - 1 |
The inside operator in depth
inside is the most versatile constraint operator. Its set can mix individual values, ranges, and even other rand variables. Each element in the set is equally likely unless you use dist (covered in page 10.8).
class InsideExamples;
rand bit [7:0] x;
rand bit [7:0] lo, hi;
// Individual values — picks one of these four
constraint c1 { x inside {8'h00, 8'h0F, 8'hF0, 8'hFF}; }
// Closed range — picks any value from 16 to 31 inclusive
constraint c2 { x inside {[16:31]}; }
// Mixed: specific values AND a range
constraint c3 { x inside {0, 1, [10:20], 255}; }
// Negated inside — excludes the set
constraint c4 { !(x inside {[128:191]}); } // x NOT in 128–191
// Dynamic range using other rand variables
constraint c5 { x inside {[lo:hi]}; } // lo and hi are also rand
endclassMultiple Constraint Blocks — How They Combine
A class can have as many named constraint blocks as you need. The solver treats all active constraint blocks as a single combined constraint — it finds an assignment that satisfies every block simultaneously. There is no priority or ordering between blocks.
class BusTxn;
rand bit [31:0] addr;
rand bit write;
rand bit [2:0] burst;
// Block 1: address must be word-aligned
constraint aligned {
addr[1:0] == 2'b00;
}
// Block 2: address must be in a valid peripheral region
constraint in_range {
addr inside {[32'h4000_0000:32'h4FFF_FFFF]};
}
// Block 3: write transactions cannot use long bursts
constraint write_burst {
write == 1 -> burst <= 3'h2;
}
endclass
// The solver finds addr that is:
// ✓ word-aligned (last 2 bits zero)
// ✓ inside 0x4000_0000–0x4FFF_FFFF
// ✓ if write=1, burst is 0, 1, or 2
// All three rules hold at once — every call.Multi-Expression Blocks and OR Constraints
Inside a single block, separate statements are implicitly ANDed. To express an OR relationship — where either condition is acceptable — combine them with || in one expression or use inside.
class Opcode;
rand bit [3:0] op;
rand bit [7:0] payload;
// AND: both must hold
constraint both {
op < 8; // op in 0–7
payload != 0; // AND payload non-zero
}
// OR: either low-range or high-range is fine
constraint either_range {
(op inside {[0:3]}) || (op inside {[12:15]});
}
// Equivalent, cleaner OR via inside with two ranges
constraint either_range_v2 {
op inside {[0:3], [12:15]};
}
endclassUsing Non-Random Variables in Constraints
Constraints can reference any property in the class — not only rand ones. A non-random variable is treated as a fixed constant by the solver. This is how you make the solver adapt to test configuration you set manually.
class MemAccess;
rand bit [31:0] addr;
// These are NOT rand — set by the test before randomize()
bit [31:0] base_addr;
bit [31:0] region_size;
constraint in_region {
addr >= base_addr;
addr < base_addr + region_size;
}
endclass
module tb;
MemAccess m;
initial begin
m = new();
// Test A: randomise inside region 0
m.base_addr = 32'h0000_0000;
m.region_size = 32'h0001_0000; // 64 KB
assert(m.randomize());
// Test B: same object, different region — no class change needed
m.base_addr = 32'h4000_0000;
m.region_size = 32'h0010_0000; // 1 MB
assert(m.randomize());
end
endmoduleConstraints and Inheritance
When a class extends another, it inherits all of the parent's constraint blocks. The child can add new constraint blocks that narrow the solution space further, or it can override a parent constraint block by declaring a new one with the same name.
class BasePkt;
rand bit [7:0] len;
constraint valid_len {
len inside {[1:255]}; // base: 1–255
}
endclass
// ── Child adds a new constraint — narrows further ─────────────
class ShortPkt extends BasePkt;
constraint short {
len <= 64; // AND: now 1–64 only
}
// valid_len from BasePkt is still active
endclass
// ── Child overrides a constraint — replaces with same name ────
class JumboPkt extends BasePkt;
constraint valid_len { // same name as parent — overrides it
len inside {[1519:9000]}; // jumbo frame: 1519–9000
}
endclass
// ShortPkt.randomize() → len is 1–64
// JumboPkt.randomize() → len is 1519–9000Why Constraint Names Matter
Every constraint block must have a name, and that name is not just decoration. Names are used for three important operations:
constraint_mode()
Turn individual constraints on or off at runtime without modifying the class. You reference the constraint by name. Full details on page 10.6.
Override in a child class
A child class replaces a parent constraint by declaring a new block with the same name. Without the name, this would be impossible.
Simulator error messages
When the solver fails, most tools print the constraint names involved in the contradiction. Good names make debugging dramatically faster.
Common Constraint Pitfalls
class Pitfalls;
rand bit [3:0] x;
rand bit [3:0] y;
// ✗ PITFALL 1: Separate statements are AND — not OR
constraint bad_or {
x < 4; // AND
x > 10; // impossible — randomize() returns 0
}
// ✓ CORRECT: use || for OR
constraint good_or {
(x < 4) || (x > 10);
}
// ✗ PITFALL 2: Unsigned comparison wrap-around
constraint bad_unsigned {
x - 1 >= 0; // always true — bit[3:0] wraps at 0
} // x=0 gives 0-1 = 15 which is >= 0
// ✓ CORRECT: explicitly exclude 0
constraint good_positive {
x != 0;
}
// ✗ PITFALL 3: Constraint on a non-rand variable has no effect
int z; // NOT rand
constraint useless {
z > 5; // solver ignores this entirely
} // z stays whatever it was last assigned
endclassQuick Reference
// ── Declare a constraint block ────────────────────────────────
constraint name { <expr>; <expr>; } // all exprs AND'd
// ── Relational ────────────────────────────────────────────────
x >= 10; x != 0; x == y + 1;
// ── Set membership ────────────────────────────────────────────
x inside {1, 2, 5}; // exact values
x inside {[10:20]}; // closed range
x inside {0, [10:20], 255}; // mixed
!(x inside {[5:10]}); // negated (exclusion)
// ── OR within a single expression ────────────────────────────
(x < 5) || (x > 20);
// ── Cross-variable relationship ───────────────────────────────
x != y; end_addr == start + len - 1;
// ── Override parent constraint (same name) ────────────────────
class Child extends Parent;
constraint same_name_as_parent { ... } // replaces parent's
endclass
// ── Disable / enable by name (detail in page 10.6) ───────────
obj.constraint_name.constraint_mode(0); // OFF
obj.constraint_name.constraint_mode(1); // ONVerification Usage — How Constraint Blocks Carry Protocol Legality in Real VIP
Every production transaction class is, fundamentally, a constraint document. The class's rand fields say "this is the random space"; the constraint blocks say "this is the protocol-legal subset of it." Three architectural patterns recur in every reusable VIP.
The legality block — always-on protocol enforcement
The base transaction class carries one or more constraint blocks that encode universal protocol rules — address alignment, burst-length bounds, byte-strobe consistency, reserved-field zeroing. These blocks are never disabled; they are the contract every test must honour.
<code>class axi_xact extends uvm_sequence_item;
rand bit [31:0] addr;
rand bit [7:0] len;
rand bit [2:0] size;
rand bit [1:0] burst;
// Universal protocol legality — never disabled
constraint c_addr_align { addr[1:0] == 2'b00; }
constraint c_burst_len { burst == AXI_BURST_FIXED -> len <= 4'hF; }
constraint c_size_max { size <= 3'd5; }
endclass</code>The shaping block — per-scenario stimulus steering
Sequence classes extend the base transaction and add additional constraint blocks that narrow the random space to the scenario. The new blocks are added on top of the legality blocks, so every scenario remains protocol-legal by construction.
<code>class axi_aligned_write_seq extends uvm_sequence;
task body();
req = axi_xact::type_id::create("req");
if (!req.randomize() with { addr inside {[32'h4000_0000:32'h4000_FFFF]};
len == 0;
burst == AXI_BURST_FIXED; })
`uvm_fatal("SEQ", "scenario constraints infeasible")
`uvm_send(req)
endtask
endclass</code>The disable-able injection block — error tests only
Error injection ("cross legal boundaries to verify the DUT rejects them") needs the freedom to violate specific protocol rules. Putting those violations behind a dedicated constraint block — disabled in normal tests, enabled in error tests — keeps the design clean.
<code>class axi_xact extends uvm_sequence_item;
// ... legality blocks above ...
// Off by default; tests enable to generate misaligned addresses
rand bit allow_unaligned;
constraint c_alignment_relax { soft allow_unaligned == 0;
allow_unaligned -> addr[1:0] != 2'b00; }
endclass
// In error test:
req.c_alignment_relax.constraint_mode(1); // enable misaligned generation</code>Simulation Behavior — What the Solver Sees When You Call randomize()
Constraint set assembly
At call time, the solver collects every constraint block from every class in the inheritance chain that is currently enabled (the default — constraint_mode(0) can disable individual blocks). All non-rand class properties are snapshotted as constants. The inline with clause (if any) is added to the set.
Joint solve, not sequential evaluation
Every expression in every active block must hold simultaneously. The solver does not evaluate them in declaration order, in semicolon order, or in any user-visible order — it finds a single assignment of all rand/randc fields that satisfies the conjunction of every constraint. This is what makes "a == b + 1; b == a + 1;" unsatisfiable instead of "set a to 5 then compute b as 6."
Implication chains and solve ordering
Implication constraints (a -> b) are evaluated as logical implications: if the antecedent holds, the consequent must too. The solver may use a solve a before b directive to bias its search order (resolve a first, then constrain b) without changing the satisfaction semantics. The distribution of solutions changes with solve ordering; the legality of solutions does not.
Unsatisfiable detection
When no assignment satisfies the constraint set, the solver returns 0. Modern simulators print which constraint blocks are involved in the contradiction (sometimes which expression specifically) — this is enormously useful in diagnosis. Older simulators just return 0; your ``uvm_fatal` on the failed assertion needs to log the active constraint set itself for forensics.
<code>class xact;
rand bit [31:0] addr;
rand bit [3:0] len;
constraint c_addr { addr inside {[0:255]}; }
constraint c_align { addr[1:0] == 2'b00; }
constraint c_len { len <= 4'h7; }
endclass
// Solver finds e.g. addr=0x0C, len=3 every call</code><code>class xact;
rand bit [31:0] addr;
constraint c_a { addr inside {[0:7]}; }
constraint c_b { addr[1:0] == 2'b00; }
constraint c_c { addr != 0;
addr != 4; }
endclass
// Allowed: 0, 4 — but both excluded
// → randomize() returns 0</code>Waveform Analysis — Tracing Which Constraints Shaped Each Transaction
Constraint blocks themselves don't appear in the waveform — they govern what gets randomised, not what gets driven. The way to make their influence visible is to log per-transaction which constraint sets were active at solve time.
The bookkeeping pattern
<code>class apb_xact extends uvm_sequence_item;
rand bit [31:0] paddr;
rand bit pwrite;
constraint c_align { paddr[1:0] == 2'b00; }
constraint c_inject { soft paddr[0] == 0; } // can be disabled
function void post_randomize();
`uvm_info("RAND",
$sformatf("paddr=0x%08h pwrite=%0d c_inject=%s",
paddr, pwrite,
this.c_inject.constraint_mode() ? "ON" : "OFF"),
UVM_HIGH)
endfunction
endclass</code>ASCII view — log shows which constraints were active
<code> 100ns RAND paddr=0x4000_0010 pwrite=1 c_inject=ON
200ns RAND paddr=0x4000_0044 pwrite=0 c_inject=ON
300ns RAND paddr=0x4000_0014 pwrite=1 c_inject=ON
400ns RAND paddr=0x4000_0013 pwrite=1 c_inject=OFF ← misaligned!
↑
error-injection test
disabled c_inject before this call</code>Diagnosing "the DUT got an illegal transaction" failures
When a DUT failure log shows "received illegal transaction," the first question is: which constraint should have prevented this? Log constraint_mode state at solve time so the post-mortem answer is in the log line — not a guess about which test layer disabled which block when.
Industry Insights — Hard-Won Lessons About Constraint Blocks
Debugging Academy — Five Real Bugs From Constraint Block Misuse
"{ a == 0; a != 0; } compiles but always fails to solve"
DEBUGA constraint block compiles cleanly; every randomize() call returns 0; UVM fatals on every test invocation.
<code>class xact;
rand bit [3:0] a;
constraint c { a == 0; a != 0; } // BUG: jointly unsatisfiable
endclass</code>Two expressions in the same block are conjuncted — both must hold simultaneously. a == 0 AND a != 0 is a contradiction for any value of a. The compiler accepts the syntax because each expression alone is valid; the contradiction surfaces only at solve time.
Decide which constraint you actually meant. If the intent was "alternate between 0 and non-0 across calls," use a dist distribution: constraint c { a dist { 0 := 50, [1:15] := 50 }; }. If you wanted an OR (either constraint can hold), use parentheses with ||: constraint c { (a == 0) || (a != 0); } (though that's trivially satisfiable). Multiple expressions in one block are always AND-ed.
"a == b + 1; b == a + 1; compiles but never solves"
DEBUGA developer writes "circular" constraints expecting sequential resolution; every solve returns 0.
<code>class xact;
rand int a, b;
constraint c { a == b + 1;
b == a + 1; } // BUG: misread as sequential
endclass</code>The developer read "a == b + 1 then b == a + 1" as procedural assignment — first compute a from b, then re-compute b from a. The solver reads it as conjunction: find values where both equations hold simultaneously. Substituting: a == (a + 1) + 1, i.e., 0 == 2 — no solution.
Decide which sequencing you actually meant. If a drives b, drop the second equation. If the two should sample distinct values, use solve a before b directive plus a non-circular relation: constraint c { solve a before b; b == a + 1; } — now a is resolved first and b follows.
"Disabling one constraint accidentally disables another"
DEBUGAn error-injection test calls constraint_mode(0) to allow unaligned addresses; afterwards, the test's address generation is wildly out of the map — not just unaligned.
<code>class xact;
rand bit [31:0] addr;
// BUG: legality AND shaping mixed into one block
constraint c_addr {
addr inside {[32'h4000_0000:32'h4000_FFFF]}; // legality (always required)
addr[1:0] == 2'b00; // alignment (test wants to relax)
}
endclass
// In test:
tx.c_addr.constraint_mode(0); // disabling for alignment relax
// But also disabled the map check → addr can be anywhere</code>Legality and shaping concerns are conflated in a single block; disabling the block removes both. The "off" switch is too coarse — there is no way to disable alignment without also disabling the map check.
Split into two blocks: constraint c_addr_map { addr inside {[32'h4000_0000:32'h4000_FFFF]}; } and constraint c_addr_align { addr[1:0] == 2'b00; }. Now the error test disables only c_addr_align; map containment is preserved. Pattern: one block per protocol rule.
"Non-rand variable in constraint behaves as if it were a constant"
DEBUGA class has int base_addr (no rand) and a constraint addr inside {[base_addr:base_addr+255]}. The developer expects base_addr to "vary" — instead every call uses whatever it was last set to.
<code>class xact;
int base_addr = 0; // non-rand
rand bit [31:0] addr;
constraint c { addr inside {[base_addr:base_addr+255]}; }
endclass
task body();
xact tx = new();
for (int i = 0; i < 10; i++) begin
if (!tx.randomize()) `uvm_fatal("","");
$display("addr=0x%h base=%0d", tx.addr, tx.base_addr);
// All addr values cluster in [0:255] — base_addr never changes
end
endtask</code>The solver treats non-rand fields as constants — snapshot at the start of randomize() and used as fixed inputs. base_addr is never written by the solver, so it stays at 0 throughout. The mental model "non-rand fields might vary because they're inside a constraint" is wrong.
If the test wants to vary base_addr across iterations, either (a) assign it from the test layer between calls, or (b) mark it rand and let the solver vary it (now subject to constraints itself). Option (a) is the canonical "test layer fixes context, solver fills payload" pattern; option (b) lets the solver pick both fields jointly. Choose by what you want the test layer to control.
"Inherited constraint silently broken by child redefining a field"
DEBUGA child class re-declares a parent's rand field with a different name (or type), and the parent's constraint that referenced the field stops affecting the child's randomisation.
<code>class base_xact;
rand bit [31:0] addr;
constraint c_addr { addr[1:0] == 2'b00; } // alignment
endclass
class write_xact extends base_xact;
// BUG: shadowed parent's field with a wider one
rand bit [63:0] addr; // child's addr is 64-bit, parent's is 32-bit
endclass
write_xact tx = new();
tx.randomize();
$display("addr[1:0] = %b", tx.addr[1:0]); // sometimes non-zero</code>The child's addr shadows the parent's. The parent's c_addr constraint still references the parent's addr field (now hidden, no longer randomised). The child's addr is randomised independently with no alignment constraint.
Don't shadow inherited fields. If you need a wider address, either change the parent's addr width (preserving the constraint's reach) or rename the child's field (addr_wide) and add a child constraint that applies to it. Inheriting a constraint and silently breaking it is the kind of bug that hides for months.
Interview Q&A — Twelve Questions You Will Be Asked
A named declaration inside a class that lists one or more boolean expressions over the class's rand/randc fields (and any other in-scope variables). When randomize() is called, the solver finds an assignment of all random fields that makes every expression in every active constraint block true simultaneously. Constraints are declarative — they describe the legal space, they don't compute values procedurally.
Best Practices — Ten Rules for Constraint Block Discipline
- One protocol rule per constraint block. Address alignment, burst-length bounds, byte-strobe consistency — each in its own named block. Lets the test layer relax one rule without affecting others.
- Name every block by purpose, not position.
c_addr_align,c_burst_len,c_error_injection— neverc1,c2. The name is documentation that survives every refactor. - Separate legality from shaping. Legality blocks (always required) live in the base class; shaping blocks (per-scenario) live in sequences via inline
with. - Remember constraints are jointly AND-ed.
{ a == 5; b == 10; }requires both — not "first then." Don't read constraint blocks as procedural code. - Use
||within an expression for OR; use separate expressions for AND.(a == 1) || (a == 2)is legal;a == 1; a == 2;on separate lines is a contradiction. - Reach for
solve … before …to influence distribution, not legality. The directive biases search order; it doesn't add or remove legal solutions. - Pair every interesting constraint with a coverage point. The coverpoint proves the constraint fires and catches silent
constraint_mode(0)regressions. - Don't shadow inherited fields. Renaming or rewidthing a parent's
randfield silently breaks any constraint that referenced it. - Treat non-rand variables as constants in constraints. The solver snapshots them at call time. If you want them to vary, assign before
randomizeor mark themrand. - Log the active constraint state from
post_randomize. When something goes wrong, the log line tells you which blocks were on/off — no code-spelunking required.