Randomization in SystemVerilog

Constrained random verification is the backbone of modern verification. Instead of writing thousands of directed tests, you let the simulator generate valid random stimulus while you define the rules (constraints) it must follow.

Why Randomization?

Manual testing (directed tests) has limitations. You can only test scenarios you explicitly think of. Random testing finds bugs you never imagined!

Directed vs Random Verification

Aspect Directed Testing Random Testing
Effort Write one test at a time One test generates thousands of scenarios
Coverage Only what you thought of Explores unexpected corners
Debug Easy - you know what you tested Need good logging and reproducibility
Bug Finding Finds expected bugs Finds unexpected bugs!

rand vs randc

SystemVerilog provides two randomization keywords:

rand - Standard Random

Each value has equal probability on every call. Same value CAN appear multiple times in a row.

rand Example
class packet;
    rand bit [1:0] priority;  // Values: 0, 1, 2, 3
endclass

// Possible sequence over 8 randomizations:
// 2, 0, 0, 3, 1, 0, 2, 0 
// Notice: 0 appears 4 times! That's okay for rand.

randc - Cyclic Random

Cycles through all possible values before repeating any value. Like picking cards from a deck - you see all cards before reshuffling.

randc Example
class packet;
    randc bit [1:0] priority;  // Values: 0, 1, 2, 3
endclass

// Possible sequence over 8 randomizations:
// 2, 0, 3, 1 | 1, 3, 0, 2
//     Cycle 1    |    Cycle 2
// All 4 values appear before any repeats!

When to Use Which?

  • rand: When you want truly random distribution (addresses, data)
  • randc: When you need to cover all values quickly (opcodes, states, small enums)
Practical Example
class ahb_transaction;
    randc bit [2:0] hburst;    // randc: Hit all 8 burst types quickly
    randc bit [2:0] hsize;     // randc: Test all transfer sizes
    rand bit [31:0] haddr;     // rand: Address can be any value
    rand bit [31:0] hwdata;    // rand: Data can be any value
endclass

// After 8 transactions: All hburst values tested!
// After 8 transactions: All hsize values tested!
// haddr and hwdata: Random each time

Basic Randomization

Use the randomize() method to generate random values:

Calling randomize()
class my_transaction;
    rand bit [7:0]  addr;
    rand bit [31:0] data;
    rand bit        read_write;
    
    function void display();
        $display("ADDR=0x%02h, DATA=0x%08h, RW=%0b", 
                 addr, data, read_write);
    endfunction
endclass

module test;
    initial begin
        my_transaction txn;
        txn = new();
        
        // Randomize and check success
        if (txn.randomize()) begin
            $display("Randomization successful!");
            txn.display();
        end
        else begin
            $display("ERROR: Randomization failed!");
        end
        
        // Or use assert shorthand
        assert(txn.randomize()) 
            else $fatal("Randomization failed!");
        txn.display();
        
        // Randomize 10 times
        repeat(10) begin
            void'(txn.randomize());  // void' ignores return value
            txn.display();
        end
    end
endmodule

Basic Constraints

Constraints define rules that random values must follow. Without constraints, a 32-bit address could be any of 4 billion values. With constraints, you limit it to valid addresses only.

Constraint Syntax
class apb_transaction;
    rand bit [31:0] paddr;
    rand bit [31:0] pwdata;
    rand bit        pwrite;
    rand bit [3:0]  pstrb;
    
    // ═══════════════════════════════════════════════════════════
    // CONSTRAINTS
    // ═══════════════════════════════════════════════════════════
    
    // Address must be word-aligned (last 2 bits = 0)
    constraint addr_align_c {
        paddr[1:0] == 2'b00;
    }
    
    // Address within peripheral range
    constraint addr_range_c {
        paddr >= 32'h4000_0000;
        paddr <  32'h4001_0000;  // 64KB range
    }
    
    // Alternative: Use 'inside' for ranges
    constraint addr_range_alt_c {
        paddr inside {[32'h4000_0000:32'h4000_FFFF]};
    }
    
    // Strobe must have at least one byte enabled for writes
    constraint strobe_valid_c {
        pwrite -> pstrb != 4'b0000;  // If write, strb not zero
    }
    
    // Strobe must be zero for reads
    constraint strobe_read_c {
        !pwrite -> pstrb == 4'b0000;
    }
    
endclass

Common Constraint Operators

Operator Example Meaning
==, != addr[1:0] == 0 Equality test
inside x inside {1, 2, 3} Value must be one of these
[a:b] x inside {[0:100]} Value in range (inclusive)
-> a -> b Implication: if a then b
dist x dist {0:=1, 1:=9} Weighted distribution
$ x inside {[0:$]} Maximum value of range
unique unique {a, b, c} All values must be different

Implication Constraint (->)

The implication operator -> is powerful and often misunderstood. A -> B means "if A is true, then B must be true". If A is false, B can be anything!

Implication Examples
class transaction;
    rand bit [2:0] size;
    rand bit [2:0] burst;
    rand bit [31:0] addr;
    rand bit locked;
    
    // If size is word (010), address must be word-aligned
    constraint size_align_c {
        (size == 3'b010) -> (addr[1:0] == 2'b00);
    }
    
    // If burst is INCR4/INCR8/INCR16, locked must be 0
    constraint no_lock_burst_c {
        (burst inside {3'b011, 3'b101, 3'b111}) -> (locked == 0);
    }
    
    // Bidirectional implication (both ways)
    // Use <-> for "if and only if"
    constraint bidir_c {
        (size > 3'b010) <-> (addr[2:0] == 3'b000);
    }
    
endclass

// Truth table for A -> B:
//   A=0, B=0 : VALID   (A is false, B can be anything)
//   A=0, B=1 : VALID   (A is false, B can be anything)
//   A=1, B=0 : INVALID (A is true, B must be true)
//   A=1, B=1 : VALID   (A is true, B is true)

Weighted Distribution (dist)

Use dist to control the probability of different values. Two forms: := and :/

Distribution Examples
class weighted_transaction;
    rand bit [1:0] priority;
    rand bit [7:0] length;
    rand bit       error_inject;
    
    // := means equal weight for each VALUE
    constraint priority_dist_c {
        priority dist {
            0 := 1,   // Weight 1 for value 0
            1 := 2,   // Weight 2 for value 1 (2x more likely)
            2 := 4,   // Weight 4 for value 2 (4x more likely)
            3 := 3    // Weight 3 for value 3
        };
        // Total weight = 10
        // P(0) = 1/10 = 10%
        // P(1) = 2/10 = 20%
        // P(2) = 4/10 = 40%
        // P(3) = 3/10 = 30%
    }
    
    // :/ means weight is DIVIDED among range values
    constraint length_dist_c {
        length dist {
            [1:10]  :/ 20,    // 20 weight total ÷ 10 values = 2 each
            [11:50] :/ 60,    // 60 weight total ÷ 40 values = 1.5 each
            [51:100]:/ 20     // 20 weight total ÷ 50 values = 0.4 each
        };
        // Smaller lengths (1-10) more likely per value
    }
    
    // Error injection: 5% of the time
    constraint error_dist_c {
        error_inject dist {
            0 := 95,   // 95% no error
            1 := 5     // 5% error
        };
    }
    
endclass

:= vs :/ Difference

Comparison
// Using :=
x dist { [0:3] := 10 };  // Each value (0,1,2,3) has weight 10
// P(0)=P(1)=P(2)=P(3) = 10/40 = 25%

// Using :/
x dist { [0:3] :/ 10 };  // Total weight 10 divided by 4 values
// P(0)=P(1)=P(2)=P(3) = 2.5/10 = 25%
// Same result here, but different meaning!

Soft Constraints

Regular constraints are "hard" - they must always be satisfied. soft constraints are default values that can be overridden by inline constraints or child class constraints.

Soft Constraints
class base_packet;
    rand bit [7:0] size;
    rand bit [1:0] priority;
    
    // Hard constraint - always enforced
    constraint size_valid_c {
        size > 0;
        size <= 100;
    }
    
    // Soft constraint - default values, can be overridden
    constraint default_priority_c {
        soft priority == 2'b01;  // Default to priority 1
    }
    
    constraint default_size_c {
        soft size inside {[10:20]};  // Prefer medium sizes
    }
endclass

module test;
    initial begin
        base_packet pkt = new();
        
        // Normal randomization - uses soft constraints
        assert(pkt.randomize());
        $display("Default: size=%0d, priority=%0d", pkt.size, pkt.priority);
        // Usually: size in 10-20, priority = 1
        
        // Override soft constraints with inline
        assert(pkt.randomize() with {
            priority == 2'b11;  // Override soft constraint
            size == 50;         // Override soft constraint
        });
        $display("Override: size=%0d, priority=%0d", pkt.size, pkt.priority);
        // size = 50, priority = 3
        
        // Hard constraint cannot be overridden!
        // This would FAIL:
        // assert(pkt.randomize() with { size == 200; });
    end
endmodule

Complete Example

AXI Transaction with Constraints
class axi_transaction;
    // ═══════════════════════════════════════════════════════════
    // Random Fields
    // ═══════════════════════════════════════════════════════════
    rand bit [31:0] awaddr;
    rand bit [7:0]  awlen;      // Burst length - 1
    rand bit [2:0]  awsize;     // 2^awsize bytes per beat
    rand bit [1:0]  awburst;    // FIXED, INCR, WRAP
    rand bit [3:0]  awid;
    rand bit        awlock;
    
    randc bit [3:0] awcache;    // randc: cover all cache types
    randc bit [2:0] awprot;     // randc: cover all protection
    
    // ═══════════════════════════════════════════════════════════
    // Constraints
    // ═══════════════════════════════════════════════════════════
    
    // Size must match bus width (assume 64-bit bus)
    constraint valid_size_c {
        awsize inside {3'b000, 3'b001, 3'b010, 3'b011};  // 1-8 bytes
    }
    
    // Address alignment based on size
    constraint addr_align_c {
        (awsize == 3'b001) -> (awaddr[0] == 0);      // 2-byte aligned
        (awsize == 3'b010) -> (awaddr[1:0] == 0);    // 4-byte aligned
        (awsize == 3'b011) -> (awaddr[2:0] == 0);    // 8-byte aligned
    }
    
    // Burst length constraints
    constraint len_valid_c {
        awburst == 2'b00 -> awlen <= 15;  // FIXED: max 16 beats
        awburst == 2'b01 -> awlen <= 255; // INCR: max 256 beats
        awburst == 2'b10 -> awlen inside {1, 3, 7, 15}; // WRAP: 2,4,8,16
    }
    
    // WRAP bursts: address must be aligned to total transfer size
    constraint wrap_align_c {
        if (awburst == 2'b10) {
            // Wrap boundary = (awlen + 1) * 2^awsize
            // Address must be aligned to this
            (awaddr % ((awlen + 1) * (1 << awsize))) == 0;
        }
    }
    
    // 4KB boundary: AXI transfers cannot cross 4KB boundary
    constraint no_4kb_cross_c {
        (awaddr[11:0] + ((awlen + 1) * (1 << awsize))) <= 4096;
    }
    
    // Locked transfers must be single beat
    constraint lock_single_c {
        awlock -> (awlen == 0);
    }
    
    // Distribution: prefer smaller bursts for faster test
    constraint burst_len_dist_c {
        soft awlen dist {
            0       := 30,    // Single beat: 30%
            [1:3]   := 40,    // 2-4 beats: 40%
            [4:15]  := 20,    // 5-16 beats: 20%  
            [16:255]:= 10     // Larger: 10%
        };
    }
    
    // ═══════════════════════════════════════════════════════════
    // Methods
    // ═══════════════════════════════════════════════════════════
    
    function void display();
        $display("╔═══════════════════════════════════════════╗");
        $display("║  AXI Write Transaction                    ║");
        $display("╠═══════════════════════════════════════════╣");
        $display("║  AWADDR  : 0x%08h                    ║", awaddr);
        $display("║  AWLEN   : %0d (<%0d beats)              ║", awlen, awlen+1);
        $display("║  AWSIZE  : %0d bytes/beat                 ║", 1 << awsize);
        $display("║  AWBURST : %s                          ║", 
                 awburst == 0 ? "FIXED" : awburst == 1 ? "INCR " : "WRAP ");
        $display("║  Total   : %0d bytes                      ║", 
                 (awlen + 1) * (1 << awsize));
        $display("╚═══════════════════════════════════════════╝");
    endfunction
    
endclass

module test;
    initial begin
        axi_transaction txn = new();
        
        repeat(5) begin
            assert(txn.randomize());
            txn.display();
        end
        
        // Specific scenario: Large INCR burst
        assert(txn.randomize() with {
            awburst == 2'b01;  // INCR
            awlen >= 64;       // At least 64 beats
            awsize == 3'b011;  // 8 bytes
        });
        txn.display();
    end
endmodule

Common Interview Questions

  1. What is the difference between rand and randc?

    rand generates truly random values with possible repeats. randc cycles through all possible values before any repeat. Use randc for small enums where you want quick coverage.

  2. What happens if constraints make randomization impossible?

    randomize() returns 0 (failure). Always check the return value! Use assert(obj.randomize()) to catch failures.

  3. What is the difference between soft and hard constraints?

    Hard constraints must always be satisfied. Soft constraints are defaults that can be overridden by inline constraints or derived class constraints.

  4. Explain implication constraint (->)

    A -> B means "if A is true, B must be true". If A is false, B can be anything. Common mistake: thinking A -> B means "A causes B" when A is always false.