Constraints in SystemVerilog

Constraints are rules that guide the random solver to generate legal and meaningful values. Without constraints, random values would be meaningless - you'd get addresses outside your memory range or invalid packet sizes. Let's learn how to write effective constraints.

Why Do We Need Constraints?

Unconstrained randomization generates any possible value. For a 32-bit address, that's 4 billion possibilities! Most of those might be invalid for your DUT. Constraints let you focus on legal and interesting values.

Think of constraints like a game's rules. In cricket, you can't just throw the ball anywhere - there are rules about where it should bounce. Constraints are the "rules" for what random values are acceptable.

Basic Constraint Syntax

Constraint Block Syntax
class Packet;
    rand bit [31:0] addr;
    rand bit [7:0]  length;
    rand bit        read_write;
    // Constraint block
    constraint addr_range {
        addr >= 32'h1000;
        addr < 32'h2000;
    }
    constraint valid_length {
        length inside {[1:64]};  // 1 to 64 bytes
    }
endclass

Types of Constraints

Simple Relational Constraints

Relational Constraints
constraint simple_constraints {
    addr > 0;                    // Greater than
    addr < 32'hFFFF;             // Less than
    length <= 128;               // Less than or equal
    length >= 4;                 // Greater than or equal
    length == 64;                // Exact value
    length != 0;                 // Not equal
}

Inside Constraints

Use inside to specify a set or range of valid values:

Inside Constraints
constraint inside_examples {
    // Specific values
    length inside {4, 8, 16, 32, 64};
    // Range
    addr inside {[32'h1000:32'h1FFF]};
    // Mix of values and ranges
    size inside {1, 2, [4:8], 16, 32};
    // Exclude values (NOT inside)
    !(addr inside {[32'hDEAD:32'hDEAF]});
}

Implication Constraints

Use -> for "if-then" conditions:

Implication Constraints
constraint if_then_rules {
    // If read, then length must be 4
    (read_write == 0) -> (length == 4);
    // If write, then data shouldn't be zero
    (read_write == 1) -> (data != 0);
    // Burst mode requires aligned address
    (burst_mode == 1) -> (addr[1:0] == 0);
}

Implication vs if-else

Implication a -> b means "if a is true, then b must be true". If a is false, b can be anything. It's not the same as if-else!

Distribution Constraints

Want some values to appear more often than others? Use dist to control the probability distribution.

Distribution Constraints
constraint probability_examples {
    // :=  means weight applies to each value
    // :/  means weight is divided among range
    // 80% reads, 20% writes
    read_write dist {0 := 80, 1 := 20};
    // Small packets are more common
    length dist {
        [1:16]   := 60,    // 60% chance for 1-16
        [17:32]  := 30,    // 30% chance for 17-32
        [33:64]  := 10     // 10% chance for 33-64
    };
    // Using :/ - weight divided among range
    addr dist {
        [0:99]   :/ 50,    // Each value gets 50/100 = 0.5
        [100:109] :/ 50    // Each value gets 50/10 = 5
    };                     // So 100-109 are 10x more likely!
}

:= vs :/ Confusion

:= assigns weight to each value in range. :/ divides weight among values. This is a common interview question and source of bugs!

Soft Constraints

soft constraints are "preferred but not required". They can be overridden by inline constraints. Regular constraints cannot be overridden - they must always be satisfied.

Soft Constraints
class Transaction;
    rand bit [7:0] data;
    rand bit [3:0] size;
    // Soft constraint - can be overridden
    constraint default_size {
        soft size == 4;  // Prefer size 4, but can be changed
    }
    // Hard constraint - must always be true
    constraint valid_size {
        size inside {1, 2, 4, 8, 16};
    }
endclass
// Usage:
Transaction t = new();
t.randomize();                    // size = 4 (default)
t.randomize() with {size == 8};   // size = 8 (overrides soft)
t.randomize() with {size == 3};   // ERROR! Violates hard constraint

When to Use Soft?

Use soft for default values that tests might want to override. Common in base transaction classes.

Solve-Before

solve...before controls the order in which variables are solved. This affects the probability distribution when variables depend on each other.

Solve-Before Example
class Packet;
    rand bit mode;           // 0 or 1
    rand bit [7:0] length;
    constraint length_rule {
        mode == 0 -> length inside {[1:10]};    // 10 values
        mode == 1 -> length inside {[1:100]};   // 100 values
    }
    // Without solve-before:
    // All 110 solutions equally likely
    // mode=0 has 10/110 = 9% chance
    // mode=1 has 100/110 = 91% chance
    // With solve-before:
    constraint solve_mode {
        solve mode before length;
    }
    // Now mode is solved first (50-50)
    // Then length is solved based on mode
    // mode=0 has 50% chance, mode=1 has 50% chance
endclass

Interview Favorite!

solve-before is a very common interview topic. Remember: it affects probability distribution, not the legal solution space.

Constraint Control

You can enable/disable constraints at runtime:

Enabling/Disabling Constraints
class Transaction;
    rand bit [31:0] addr;
    constraint aligned_addr {
        addr[1:0] == 0;  // Word aligned
    }
    constraint high_addr {
        addr >= 32'h8000_0000;
    }
endclass
Transaction t = new();
// Disable a constraint
t.aligned_addr.constraint_mode(0);  // Turn off alignment
t.randomize();                       // addr can be unaligned
// Re-enable
t.aligned_addr.constraint_mode(1);
// Check if constraint is active
if (t.aligned_addr.constraint_mode())
    $display("Constraint is ON");

Advanced Constraint Features

Array Constraints (foreach)

You can constrain every element of an array using the foreach loop. This is essential for payload generation.

Foreach Constraint
class Packet;
    rand byte payload[];
    constraint payload_c {
        payload.size() == 4; // Fix size for example
        // Constrain each byte
        foreach (payload[i]) {
            payload[i] inside {[0:127]}; // ASCII range
            // Relationship between elements
            if (i > 0) payload[i] > payload[i-1]; // Ascending order
        }
    }
endclass

Functions in Constraints

You can call functions inside constraints to handle complex logic that is hard to express with standard operators.

Function in Constraint
function int count_ones(bit [31:0] data);
    count_ones = $countones(data);
endfunction
class Bus;
    rand bit [31:0] data;
    constraint sparse_c {
        // Only generate values with exactly 2 to 4 bits set
        count_ones(data) inside {[2:4]};
    }
endclass

Function Rules

Functions called in constraints must be pure (no side effects, no static variables) and act as random variables are treated as inputs to the function.

Case Study: Ethernet Traffic Generator

Let's build a realistic constraints block for an Ethernet frame generator. This demonstrates how to combine multiple constraint types to solve a real verification problem.

Ethernet Frame Generator
class EthFrame;
    rand bit [47:0] dest_addr;
    rand bit [47:0] src_addr;
    rand bit [15:0] type_len;
    rand byte       payload[]; // Dynamic array
    rand bit [31:0] fcs;       // Frame Check Sequence
    rand bit        has_error;
    // 1. Basic Frame Size Constraints
    constraint frame_size_c {
        payload.size() inside {[46:1500]}; // Standard payload range
    }
    // 2. Alignment Constraints (Optimization)
    constraint aligned_payload_c {
        payload.size() % 4 == 0; // Word aligned payload
    }
    // 3. Error Injection Control
    constraint error_dist_c {
        // 5% chance of generating a corrupted frame
        has_error dist { 0 := 95, 1 := 5 };
    }
    // 4. Conditional FCS Corruption
    constraint fcs_error_c {
        solve has_error before fcs; // Solve error mode first
        (has_error == 1) -> fcs == 32'hDEAD_BEEF;
        (has_error == 0) -> fcs == 0; // In real TB, this would be calc function
    }
    // 5. Multicast vs Unicast distribution
    constraint addr_type_c {
        // LSB of first byte indicates Multicast
        dest_addr[40] dist { 0 := 80, 1 := 20 }; // 80% Unicast
    }
endclass

Deep Dive: Interview Traps

1. Bi-Directional Solving

Constraints are solved simultaneously (bi-directionally), not sequentially like procedural code.

Bi-Directional Example
rand bit a;
rand bit b;
constraint c {
    a == 0 -> b == 1; // logical implication
    b == 0;           // hard constraint
}
// Q: What is the value of 'a'?
// Logic:
// 1. b must be 0.
// 2. For (a==0 -> b==1) to be satisfied when b is 0:
//    - If a is 0, b should be 1 (Conflict! 1!=0)
//    - If a is 1, constraint is satisfied (False -> Anything is True)
// Result: 'a' MUST be 1. The solver forces 'a' based on 'b'.

2. The "dist" One-Off Error

A specific range in `dist` includes the end value. `[0:2]` means 0, 1, and 2.

Dist Weight Calculation
rand int x;
constraint c {
    x dist { [0:9] :/ 100 };
}
// Q: What is the probability of x being 5?
// A: The weight 100 is divided by the number of items (0 to 9 = 10 items).
//    100 / 10 = 10.
//    Probability is 10/100 = 10% (assuming total weight matches).

Quick Summary

  • Basic: Use relational operators (<,>, ==, !=)
  • inside: Specify sets or ranges of values
  • ->: Implication for conditional constraints
  • dist: Control probability distribution (:= vs :/)
  • soft: Default values that can be overridden
  • solve-before: Control solving order for probability
  • constraint_mode(): Enable/disable at runtime

What's Next?

Learn more advanced randomization techniques: