SystemVerilog gives two constructs for declaring conditional relations between variables in a constraint: the implication operator, and if/else. Both express the same idea — "this rule only applies when some condition holds" — just with different syntax.

12 min read | Intermediate Level

What You'll Learn

  • How the implication operator (->) expresses "this constraint only applies when a condition holds"
  • Why a constrained variable is completely unconstrained — not forced to any default — whenever the antecedent condition is false
  • How to write the same conditional logic using if/else inside a constraint block, including nested branches
  • Why -> alone can't express an "otherwise" branch the way if/else can

Implication vs if-else: Two Equivalent Styles


// Implication operator "->" tells that len should be
// greater than 10 when mode is equal to 2
constraint c_mode {  mode == 2 -> len > 10; }

// Same thing can be achieved with "if-else" construct
constraint c_mode { if (mode == 2)
						len > 10;
				  }

mode doesn't have to equal 2 for len to be greater than 10 — the constraint only says len must be greater than 10 if mode is 2. When mode isn't 2, len is left completely unconstrained by this rule.

Example


class ABC;
  rand bit [2:0] mode;
  rand bit [3:0] len;
  
  constraint c_mode { mode == 2 -> len > 10; }
endclass

module tb;
  initial begin
    ABC abc = new;
    for(int i = 0; i < 10; i++) begin
      abc.randomize();
      $display ("mode=%0d len=%0d", abc.mode, abc.len);
    end
  end
endmodule

The results show mode doesn't need to be 2 for len to come out greater than 10:

Output
ncsim> run
mode=1 len=11
mode=6 len=3
mode=3 len=9
mode=7 len=11
mode=3 len=15
mode=2 len=12
mode=3 len=6
mode=2 len=12
mode=4 len=9
mode=7 len=13
ncsim: *W,RNQUIE: Simulation is complete.

The two rows where mode actually equals 2 both correctly show len > 10 (12 in both cases). Every other row has mode at some other value, and len is free to be anything — including values under 10, since nothing constrains it in that case.

Quick Check

Q: If mode randomizes to 5, what values can len take?

Show Answer

Any legal value in its full range, 0 through 15. Since the antecedent mode == 2 is false, c_mode imposes no requirement on len at all — it isn't forced toward any particular value, just left free.

Allowed values of len when the implication antecedent is true and false

Implication Operator

The implication operator -> shows a conditional relationship between two variables in a constraint expression. If the expression on the left-hand side is true, the expression on the right-hand side must be satisfied. If the left-hand side is false, the right-hand side isn't considered at all.

Example


class ABC;
  rand bit [3:0] mode;
  rand bit 		 mod_en;
  
  // If 5 <= mode <= 11, mod_en should be 1
  constraint c_mode {	mode inside {[4'h5:4'hB]} -> mod_en == 1; }

endclass

module tb;
  initial begin
    ABC abc = new;
    for (int i = 0; i < 10; i++) begin
    	abc.randomize();
      $display ("mode=0x%0h mod_en=0x%0h", abc.mode, abc.mod_en);
    end
  end
  
endmodule

mod_en is 1 whenever mode falls between 4'h5 and 4'hB. When that left-hand condition is false, mod_en can randomize to any value:

Output
ncsim> run
mode=0xf mod_en=0x1
mode=0x9 mod_en=0x1
mode=0x3 mod_en=0x1
mode=0xe mod_en=0x1
mode=0x1 mod_en=0x1
mode=0x0 mod_en=0x0
mode=0x1 mod_en=0x0
mode=0xe mod_en=0x0
mode=0x5 mod_en=0x1
mode=0x0 mod_en=0x0
ncsim: *W,RNQUIE: Simulation is complete.

The two rows where mode genuinely falls in [5:11] (0x9 and 0x5) both show mod_en=1, exactly as required. Everywhere else, mod_en shows up as both 0 and 1 freely, confirming it's truly unconstrained outside that range.

Quick Check

Q: Could this constraint alone ever produce mode=0x7 and mod_en=0 together?

Show Answer

No. 0x7 falls inside [5:11], so the implication requires mod_en == 1 whenever mode lands there — mod_en=0 would violate the constraint in that specific case.

if-else Constraint

The if/else constraint adds an option the plain implication operator doesn't have on its own: an explicit else branch. If the conditional expression is true, every constraint in the first set must be satisfied; otherwise, every constraint in the optional else part must be satisfied instead.

Nested if/else blocks are allowed, and multiple constraint statements within one branch need to be enclosed in curly braces { } — similar to how begin/end groups multiple statements in a procedural block like initial or always. Constraints are declarative code, though, so they use curly braces instead.

Example

The first if checks whether mode falls between 5 and 11. If true, mod_en is constrained to 1. If false, the else part runs — itself containing another if/else that checks whether mode equals 1:


class ABC;
  rand bit [3:0] mode;
  rand bit 		 mod_en;
  
  constraint c_mode {
    					// If 5 <= mode <= 11, then constrain mod_en to 1
    					// This part only has 1 statement and hence do not
    					// require curly braces {}
    					if (mode inside {[4'h5:4'hB]})
  							mod_en == 1;
  
    					// If the above condition is false, then do the following
   						else {
                          // If mode is constrained to be 1, then mod_en should be 1
                          if ( mode == 4'h1) {
      							mod_en == 1;
                            // If mode is any other value than 1 and not within
                            // 5:11, then mod_en should be constrained to 0
    						} else {
      							mod_en == 0;
    						}
  						}
                    }

endclass

module tb;
  initial begin
    ABC abc = new;
    for (int i = 0; i < 10; i++) begin
    	abc.randomize();
      $display ("mode=0x%0h mod_en=0x%0h", abc.mode, abc.mod_en);
    end
  end
  
endmodule

Output

Output
ncsim> run
mode=0xb mod_en=0x1
mode=0x1 mod_en=0x1
mode=0x6 mod_en=0x1
mode=0x7 mod_en=0x1
mode=0x2 mod_en=0x0
mode=0x2 mod_en=0x0
mode=0x2 mod_en=0x0
mode=0x9 mod_en=0x1
mode=0x7 mod_en=0x1
mode=0x8 mod_en=0x1
ncsim: *W,RNQUIE: Simulation is complete.

Every row matches the three-way branch exactly: mode in [5:11] (0xb, 0x6, 0x7, 0x9, 0x8) always shows mod_en=1; mode == 1 also shows mod_en=1; and mode=0x2 — matching neither of the first two conditions — consistently falls through to mod_en=0.

Where Is This Used?

Implication constraints are the standard way to express protocol legality rules like "if this is a WRITE transaction, data must be provided; if it's a READ, the data field is unused" — exactly the kind of conditional rule real bus protocols are full of, and a natural fit for a single implication or if-else constraint rather than separate transaction subclasses.

Common Beginner Mistakes

Mistake #1: Assuming the constrained variable gets some default when the antecedent is false

As both implication examples show, the variable isn't forced to any particular value — it's simply left unconstrained by that specific rule. Why? Other constraints elsewhere in the class might still apply, but this particular implication contributes nothing when its condition doesn't hold.

Mistake #2: Assuming -> alone can express an "otherwise" branch

-> only states what must hold when its left-hand side is true — it has no built-in way to require something different when it's false. Why? To actually constrain a variable differently in the false case (rather than just leave it free), if/else is the right tool, exactly as the nested example demonstrates.

Mistake #3: Forgetting curly braces around multiple statements in one branch

A single statement in an if or else branch doesn't need braces, but more than one statement does — matching the comment right in the example above. Why? Constraint blocks are declarative, and curly braces are how multiple expressions get grouped together within one conditional branch.

Mistake #4: Using = instead of == inside an if-else constraint body

Even though an if/else constraint looks a lot like procedural code, it's still a declarative expression underneath. Why? mod_en == 1; is a constraint the solver satisfies; mod_en = 1; would be an assignment, which has no meaning inside a constraint block at all.

Practice Exercise

Challenge: Constrain a Write/Read Transaction

Write a class Trans with rand bit is_write and rand bit [7:0] data. Add a constraint requiring data != 0 when is_write is 1, and data == 0 when is_write is 0.

Hint

This needs an explicit "otherwise" branch, not just a single implication — follow the same if/else pattern from this article's nested example.

Solution

class Trans;
  rand bit is_write;
  rand bit [7:0] data;

  constraint c_data {
    if (is_write)
      data != 0;
    else
      data == 0;
  }
endclass

If is_write randomizes to 0, data is forced to exactly 0. If is_write randomizes to 1, data can be any value from 1 to 255 — anything except 0. A plain implication alone couldn't express the is_write == 0 case; only the if/else form can require something specific in both branches.

Summary

In this tutorial, you learned:

  • The implication operator (->) applies a constraint only when its left-hand condition is true; otherwise the right-hand side is left entirely unconstrained
  • A variable left unconstrained by a false implication isn't forced to any default — it's genuinely free, subject only to whatever other constraints still apply
  • if/else constraints express the same conditional relationship, with the added ability to require something specific in the "otherwise" case, which -> alone can't do
  • Nested if/else blocks are allowed, and curly braces group multiple statements within one branch, the same way begin/end would in procedural code
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5