Inline Constraints in SystemVerilog

Inline constraints let you add or override constraints at the point where you call randomize(). This gives you flexibility to use the same class for different test scenarios without modifying the class definition.

The "with" Clause

Inline constraints are specified using the with clause after randomize(). They act as additional constraints on top of the class constraints.

Basic Inline Constraint
class Packet;
    rand bit [31:0] addr;
    rand bit [7:0]  size;
    
    constraint valid_size {
        size inside {[1:64]};
    }
endclass

Packet p = new();

// Default randomization
p.randomize();                        // Any size from 1-64

// With inline constraint - add more restrictions
p.randomize() with {size == 32;};     // Force size = 32
p.randomize() with {addr < 1000;};    // Add addr restriction
p.randomize() with {
    size inside {4, 8, 16};
    addr[1:0] == 0;                   // Multiple constraints
};

How It Works

Inline constraints are added to class constraints. Both must be satisfied. If they conflict with a hard constraint, randomization fails!

Overriding Soft Constraints

Inline constraints can override soft constraints, but not regular (hard) constraints.

Overriding Soft Constraints
class Transaction;
    rand bit [7:0] length;
    
    // Soft constraint - can be overridden
    constraint default_length {
        soft length == 4;
    }
    
    // Hard constraint - cannot be overridden
    constraint valid_length {
        length inside {[1:128]};
    }
endclass

Transaction t = new();

t.randomize();                    // length = 4 (soft default)
t.randomize() with {length == 64;};  // length = 64 (override soft)
t.randomize() with {length == 200;}; // FAILS! Violates hard constraint

Randomization Failure

If inline constraints conflict with hard constraints, randomize() returns 0 (failure). Always check the return value in critical code!

Using Local Variables

You can use local variables inside inline constraints. Use the local:: scope resolution to reference them.

Using Local Variables
class Packet;
    rand bit [31:0] addr;
    rand bit [7:0]  size;
endclass

task generate_packets();
    Packet p = new();
    bit [31:0] base_addr = 32'h1000;
    int max_size = 32;
    
    // Use local variables in constraint
    p.randomize() with {
        addr >= local::base_addr;
        addr < local::base_addr + 32'h100;
        size <= local::max_size;
    };
    
    // Loop with different parameters
    for (int i = 0; i < 10; i++) begin
        p.randomize() with {
            addr == local::base_addr + (local::i * 4);
        };
    end
endtask

When is local:: Needed?

Use local:: when the variable name could be confused with a class member. It explicitly tells the solver to use the local variable.

Practical Use Cases

1. Targeted Testing

Corner Case Testing
// Test boundary conditions
p.randomize() with {addr == 0;};           // Min address
p.randomize() with {addr == 32'hFFFFFFFF;}; // Max address
p.randomize() with {size == 1;};           // Min size
p.randomize() with {size == 128;};         // Max size

// Test specific scenarios
p.randomize() with {
    addr[11:0] == 12'hFFF;  // Cross 4KB boundary
    size > 16;
};

2. Sequence Control

Controlling Sequences
class AHBSequence extends uvm_sequence;
    rand bit [31:0] start_addr;
    
    task body();
        AHBTransaction tr;
        
        // First transaction - write
        `uvm_create(tr)
        tr.randomize() with {
            trans_type == WRITE;
            addr == local::start_addr;
        };
        `uvm_send(tr)
        
        // Second transaction - read same address
        `uvm_create(tr)
        tr.randomize() with {
            trans_type == READ;
            addr == local::start_addr;
        };
        `uvm_send(tr)
    endtask
endclass

3. Iterative Randomization

Generating Related Values
// Generate incrementing addresses
bit [31:0] addr_list[$];
Packet p = new();

for (int i = 0; i < 10; i++) begin
    p.randomize() with {
        addr >= local::i * 1024;
        addr < (local::i + 1) * 1024;
    };
    addr_list.push_back(p.addr);
end

std::randomize()

std::randomize() lets you randomize regular variables (not just class members) with inline constraints.

std::randomize Example
// Randomize standalone variables
bit [31:0] addr;
bit [7:0]  data;

// Simple randomization
std::randomize(addr);

// With constraints
std::randomize(addr, data) with {
    addr inside {[32'h1000:32'h2000]};
    data != 0;
};

// Check for success
if (!std::randomize(addr) with {addr < 100;})
    $error("Randomization failed!");

When to Use std::randomize?

Use it when you need quick randomization of simple variables without creating a class. Great for test setup or generating random delays.

Quick Summary

  • with {} - Add inline constraints to randomize()
  • Inline constraints are added to class constraints
  • Can override soft constraints, not hard constraints
  • Use local:: to reference local variables
  • std::randomize() for standalone variable randomization
  • Always check return value when constraints might conflict

What's Next?

Continue your SystemVerilog journey: