Once you understand that constraints narrow down the space of legal random values, the natural next question is: where do constraints actually live, and how are they structured? The answer is constraint blocks — named, scoped declarations that sit inside (or alongside) a class and tell the solver what rules to follow.

14 min read | Intermediate Level

What You'll Learn

  • Why constraint blocks are class members, and what that implies for inheritance and runtime control
  • How multiple constraint blocks in the same class combine, and what happens when they conflict
  • How to declare a constraint's body outside the class using external constraints
  • How a subclass can override a parent's constraint entirely by redeclaring it with the same name

Constraint Blocks Are Class Members

A constraint block is a first-class member of a SystemVerilog class — just like a variable, function, or task. It has a name, it belongs to a class, and it participates in inheritance. This isn't just a cosmetic detail; understanding that constraints are members explains a lot of their behavior:

  • They're inherited by subclasses
  • They can be overridden in subclasses
  • They can be individually enabled or disabled at runtime
  • Multiple constraint blocks in the same class all apply simultaneously — the solver sees them all at once

The constraint solver treats every active constraint block in a class as a single logical AND of all their expressions. There's no priority or ordering among blocks by default — if you have five constraint blocks, the solver must find values that satisfy all five simultaneously.

Syntax


constraint  <name_of_constraint> {    <expression 1>;
                                            // ...
                                            <expression N>;
                                        }

Each expression inside the braces is a condition the solver must satisfy. Multiple expressions within a single block are implicitly ANDed together. The block is terminated with a closing }, not with begin/end — constraint blocks use curly braces because they're declarative, not procedural.

  • Constraint blocks must have a unique name within a class — declaring two blocks with the same name in the same class is a compile error.
  • Expressions can reference multiple variables; a single block can constrain several fields at once if their values are related.
  • An empty constraint block (just the name and empty braces) is legal. It compiles and runs without error, but has no effect on randomization — sometimes useful as a placeholder.

It isn't necessary to have a single constraint for every variable, nor is a constraint block restricted to conditions on only one variable. You can't, however, have conflicting constraints spread across multiple blocks, unless they're turned off using constraint_mode() — covered in SystemVerilog Disable Constraints.

Simple Constraint Block

Here's the simplest possible constraint block, to anchor the concept:


// Let's declare a new class called "Packet" with a single variable that
// can be randomized. We want this variable to have values between 2 and 6
// when randomized. This is achieved by a constraint called "c_mode" (you
// can name it anything else).
class Packet;
    rand bit [3:0] mode;

    constraint c_mode {
        mode > 2;
        mode <= 6;
    }
endclass

c_mode restricts mode to the range (2, 6] — meaning values 3, 4, 5, and 6 are valid. The 4-bit variable can naturally hold 0–15, so without the constraint, all 16 of those values would be legal. With it, only 4 are.


module tb;
    Packet pkt = new();

    initial begin
        for (int i = 0; i < 5; i++) begin
            assert(pkt.randomize()) else $fatal(1, "Randomization failed");
            $display("mode = %0d", pkt.mode);
        end
    end
endmodule

Output

Output
mode = 6
mode = 6
mode = 5
mode = 4
mode = 5

Every value is within the legal range. Notice the assert wrapping randomize() — if the solver can't satisfy the constraints, randomize() returns 0 instead of 1. Wrapping it catches that failure immediately, rather than silently continuing with stale variable values.

Quick Check

Q: Would pkt.mode == 2 ever be a legal result from this constraint?

Show Answer

No. mode > 2 is a strict inequality, so 2 itself is excluded — only 3, 4, 5, and 6 satisfy both expressions in c_mode.

Multiple Constraint Blocks


class BusTransaction;
    rand bit [31:0] addr;
    rand bit [7:0]  data;
    rand bit [2:0]  burst_len;
    rand bit        read_write;  // 0 = read, 1 = write

    // Address must be 4-byte aligned
    constraint c_addr_align {
        addr[1:0] == 2'b00;
    }

    // Address must be within a valid MMIO window
    constraint c_addr_range {
        addr >= 32'hF000_0000;
        addr <= 32'hFACE_FACE;
    }

    // Burst length is 1 to 4 beats
    constraint c_burst {
        burst_len >= 1;
        burst_len <= 4;
    }

    // Read transactions carry no write data
    constraint c_read_data {
        (read_write == 0) -> (data == 8'h0);
    }
endclass

Constraint blocks are enclosed in curly braces { } instead of begin end, reflecting their declarative nature. Keeping constraints separated this way has real practical benefits:

  • Readability: each block has a focused purpose, and its name tells you what it enforces.
  • Selective disabling: you can turn off c_addr_range independently to test out-of-range addresses without touching the other constraints.
  • Inheritance and override: a subclass can override just c_addr_range to work with a different memory map while inheriting the alignment and burst-length rules unchanged.

How the Solver Handles Multiple Blocks

All active constraint blocks in a class are solved simultaneously — the solver doesn't process them in order or give any one block higher priority.

Additive constraints work fine: two blocks that both restrict the same variable further narrow the valid range. In the BusTransaction example, c_addr_align and c_addr_range both apply to addr — the solver generates an address that's both aligned and within the window.

Conflicting constraints cause a solver failure. If two blocks make contradictory demands on the same variable, randomize() returns 0 — a runtime failure, not a compile error, since the simulator has no way to catch it until it actually tries to solve:


constraint c_high { addr >= 32'hFACE_FFFF; }  // addr must be above facefffh
constraint c_low  { addr <= 32'hF000_0000; }  // addr must be below f0000000h
// These two can never both be true at once - randomize() fails

Non-conflicting additions are fine. If a second block further restricts — but doesn't contradict — an existing one, the solver simply works within the tighter window:


constraint c_range  { addr >= 32'hF000_0000; addr <= 32'hFACE_FACE; }
constraint c_tighter { addr <= 32'hF400_0000; }
// Valid: the solver picks from [F000_0000, F400_0000], a subset of the first range

Quick Check

Q: If c_high were changed to a strict inequality (addr > 32'hFACE_FFFF), would it still conflict with c_low?

Show Answer

Yes. Making c_high even more restrictive can't create overlap where none existed — if the original ranges never touched, a narrower version of one of them still won't touch the other.

External Constraint Blocks

SystemVerilog also allows a constraint block's body to be defined outside the class, after the class declaration ends — called an external constraint. The mechanism is analogous to declaring a function prototype inside a class and defining it externally using the scope resolution operator ::.

Why Use External Constraints?

In large verification environments, you often want to keep the class declaration compact and push the actual constraint logic into a separate file or section. This is particularly common in UVM-based testbenches, where a base sequence class might declare constraint prototypes, and different test environments fill in the actual expressions. External constraints also let you modify constraint logic without editing the class body itself — useful when the class is owned by a shared library you don't want to touch.

Implicit External Constraint

Declared as an empty constraint (no extern keyword) inside the class:


class ABC;
    rand bit [3:0] mode;
    constraint c_implicit;  // No "extern" keyword - implicit prototype
endclass

// External definition using :: operator
constraint ABC::c_implicit { mode > 2; }

An implicit prototype without a matching external definition isn't a compile error, but most simulators issue a warning, and the constraint simply has no effect if never defined.

Explicit External Constraint

Declared with the extern keyword inside the class:


class ABC;
    rand bit [3:0] mode;
    extern constraint c_explicit;  // "extern" keyword - explicit prototype
endclass

// External definition - required if "extern" was used
constraint ABC::c_explicit { mode <= 6; }

With extern, failing to provide the external definition is a compile error. The extern keyword is a promise to the compiler that the definition will exist somewhere — a stricter form that catches missing definitions early.

External Constraint Example


// Let's declare a new class called "ABC" with a single variable that
// can be randomized. We want this variable to have values between 2 and 6
// when randomized. This is achieved by a constraint called "c_mode" (you
// can name it anything else).
class ABC;
    rand bit [3:0] mode;

    constraint c_implicit;              // Implicit: won't error if body is missing
    extern constraint c_explicit;       // Explicit: will error if body is missing
endclass

// This is an external constraint because it is outside
// the class-endclass body of the class. The constraint
// is referenced using ::
constraint ABC::c_implicit { mode > 2; }
constraint ABC::c_explicit { mode <= 6; }

module tb;
	ABC abc;

	initial begin
      	// Create a new object with this handle
		abc = new();

      	// In a for loop, lets randomize this class handle
      	// 5 times and see how the value of mode changes
        // A class can be randomized by calling its "randomize()"
		for (int i = 0; i < 5; i++) begin
			abc.randomize();
          $display ("mode = 0x%0h", abc.mode);
		end
	end
endmodule

Output

Output
mode = 0x3
mode = 0x5
mode = 0x6
mode = 0x4
mode = 0x6

Both constraints apply simultaneously — mode is always greater than 2 and at most 6, regardless of whether the blocks were defined inside or outside the class. Note: it's illegal to provide multiple constraint blocks for a single prototype, or to write another block with the same name as one already used in an external prototype declaration.

Quick Check

Q: If c_explicit's external definition were accidentally left out entirely, what would happen?

Show Answer

A compile error. extern is a stricter promise than the implicit form — omitting c_implicit's body only produces a warning and a no-op constraint, but omitting c_explicit's body is caught at compile time.

Constraint Blocks and Inheritance

Because constraints are class members, they participate in SystemVerilog's inheritance model. A subclass inherits every constraint block from its parent, and can add new blocks or override an existing one by redeclaring a constraint with the same name — the child's version replaces the parent's entirely:


class BaseTransaction;
    rand bit [7:0] addr;

    constraint c_addr { addr inside {[8'h00 : 8'hFF]}; }  // full range
endclass

class RestrictedTransaction extends BaseTransaction;
    // Override: only allow the lower quarter of the address space
    constraint c_addr { addr inside {[8'h00 : 8'h3F]}; }
endclass

In RestrictedTransaction, the parent's c_addr is replaced entirely, not narrowed further — 0x3F is 63, so [8'h00 : 8'h3F] covers exactly 64 of the 256 possible 8-bit values, precisely a quarter of the full range. This is a powerful pattern for building test-specific transaction types without duplicating code: define the general structure in the base class and specialize it in subclasses.

Where Is This Used?

External constraint prototypes and subclass constraint overriding are both heavily used in UVM base sequence and sequence-item classes — a shared verification component defines the structure of a transaction once, and individual tests or environments override just the value ranges they care about, without touching or duplicating the base class itself.

Common Beginner Mistakes

Mistake #1: Duplicate constraint names in the same class

Two constraint blocks with the same name in one class body is a compile error. Why? If related expressions need grouping, put them all in one block, or give each block a genuinely distinct name.

Mistake #2: Assuming constraint order matters

The solver treats every active block as one simultaneous system — writing block A before block B in the source doesn't mean A is solved first. Why? If ordering actually matters (like solving one variable before using its value to constrain another), that's handled by the dedicated solve...before construct, not by source order.

Mistake #3: Ignoring the randomize() return value

A solver failure from conflicting constraints returns 0 silently. Why? Without checking the return value, a test continues with whatever the variables last held — zero, or leftover values from a previous call — and you can spend hours debugging a test that was never exercising what you thought it was.

Mistake #4: Leaving a constraint block empty unintentionally

An empty constraint block is valid but has no effect at all. Why? If it was declared as a placeholder and never filled in, the solver won't warn you — it just quietly does nothing for that block, silently narrowing nothing.

Practice Exercise

Challenge: Override, Don't Narrow

Write a class BaseCmd with rand bit [3:0] opcode and a constraint c_op allowing the full range [0:15]. Write a subclass RestrictedCmd that overrides c_op to only allow [0:3]. If RestrictedCmd is randomized many times, what's the full range of values opcode could ever show?

Hint

Remember that redeclaring a constraint with the same name in a subclass replaces the parent's version entirely — it doesn't combine with it.

Solution

class BaseCmd;
  rand bit [3:0] opcode;
  constraint c_op { opcode inside {[0:15]}; }
endclass

class RestrictedCmd extends BaseCmd;
  constraint c_op { opcode inside {[0:3]}; }
endclass

opcode can only ever be 0, 1, 2, or 3 — never anything from 4 to 15. Since RestrictedCmd's c_op completely replaces BaseCmd's version rather than adding to it, the parent's full-range constraint has no effect at all once overridden.

Summary

In this tutorial, you learned:

  • Constraint blocks are named class members, defined with the constraint keyword and curly-brace bodies
  • All active constraint blocks in a class are solved simultaneously, as a single AND of all their expressions
  • Conflicting constraints across blocks cause randomize() to fail at runtime, not at compile time
  • External constraints (using ::) let a constraint's body live outside the class, with extern enforcing that the definition is actually provided
  • A subclass inherits every constraint block from its parent, and overriding one by name replaces it entirely rather than narrowing it further
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5
Cron Job Starts