Skip to content

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 { }.

SystemVerilog — constraint block anatomy
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
    }
endclass

Operators Inside Constraint Blocks

Most SystemVerilog expressions are legal inside constraints. The table below lists the ones you will use daily.

Operator / FormMeaningExample
==  !=Equal / not equalsrc != 8'hFF
<  <=  >  >=Relational comparisonlen >= 64
inside { set }Value is a member of the setopcode inside {3'h0, 3'h1, 3'h4}
inside { [lo:hi] }Value is within a closed rangelen inside {[64:1518]}
! (negation)Logical NOT — negate any constraint expression!(src inside {[0:9]})
&&  ||Logical AND / OR within a single expressionx > 0 && x < 100
->Implication (if → then) — covered in page 10.9write -> (addr[1:0] == 0)
Arithmetic + - * /Arithmetic on rand variablesend_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).

SystemVerilog — inside operator variants
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
endclass

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

SystemVerilog — multiple constraint blocks combined
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.

SystemVerilog — AND vs OR in constraints
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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]};
    }
endclass

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

SystemVerilog — non-random variable used as a constraint bound
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
endmodule

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

SystemVerilog — constraint inheritance and override
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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–9000

Why 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

SystemVerilog — constraint pitfall examples
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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
endclass

Quick Reference

SystemVerilog — constraint block cheat sheet
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ── 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);   // ON

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

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

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

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

✅ Constraints jointly satisfiable
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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>
❌ Constraints jointly infeasible
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<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

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

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

                                       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

1

"{ a == 0; a != 0; } compiles but always fails to solve"

DEBUG
Symptom

A constraint block compiles cleanly; every randomize() call returns 0; UVM fatals on every test invocation.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class xact;
  rand bit [3:0] a;
  constraint c { a == 0; a != 0; }   // BUG: jointly unsatisfiable
endclass</code>
Root Cause

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.

Fix

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.

2

"a == b + 1; b == a + 1; compiles but never solves"

DEBUG
Symptom

A developer writes "circular" constraints expecting sequential resolution; every solve returns 0.

Buggy Code
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
<code>class xact;
  rand int a, b;
  constraint c { a == b + 1;
                 b == a + 1; }   // BUG: misread as sequential
endclass</code>
Root Cause

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.

Fix

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.

3

"Disabling one constraint accidentally disables another"

DEBUG
Symptom

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

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

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.

Fix

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.

4

"Non-rand variable in constraint behaves as if it were a constant"

DEBUG
Symptom

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

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

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.

Fix

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.

5

"Inherited constraint silently broken by child redefining a field"

DEBUG
Symptom

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

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

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.

Fix

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

  1. 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.
  2. Name every block by purpose, not position. c_addr_align, c_burst_len, c_error_injection — never c1, c2. The name is documentation that survives every refactor.
  3. Separate legality from shaping. Legality blocks (always required) live in the base class; shaping blocks (per-scenario) live in sequences via inline with.
  4. Remember constraints are jointly AND-ed. { a == 5; b == 10; } requires both — not "first then." Don't read constraint blocks as procedural code.
  5. 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.
  6. Reach for solve … before … to influence distribution, not legality. The directive biases search order; it doesn't add or remove legal solutions.
  7. Pair every interesting constraint with a coverage point. The coverpoint proves the constraint fires and catches silent constraint_mode(0) regressions.
  8. Don't shadow inherited fields. Renaming or rewidthing a parent's rand field silently breaks any constraint that referenced it.
  9. Treat non-rand variables as constants in constraints. The solver snapshots them at call time. If you want them to vary, assign before randomize or mark them rand.
  10. 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.