HomeSV VerificationDay 12

Randomization & Constraints

Generate thousands of test cases automatically. Master the rand keyword, constraint blocks, distributions, and the solver that makes verification scalable.

📍 Today's Topics

Why Constrained Random?

On Day 11, you learned how to generate test scenarios with fork/join. But there's a problem: manually writing every test case doesn't scale. A real chip may have 64-bit addresses, multiple protocol variations, and thousands of edge cases. You can't hand-code them all.

Constrained random generation solves this:

Result: 99% fewer lines of testbench code, and far better coverage.

The rand & randc Keywords

In SystemVerilog, declare random fields like this:

class Packet; rand bit [31:0] addr; // Random 32-bit address rand int data; // Random integer randc bit [3:0] cmd; // Random cyclic (0..15, all used before repeat) int fixed_id; // NOT random (static) endclass Packet pkt = new(); pkt.randomize(); // Solver fills in random values $display("Addr: %0h, Cmd: %0d", pkt.addr, pkt.cmd);

rand vs randc

KeywordBehaviorUse Case
randIndependent random each callAddresses, payload, timing (any value any time)
randcRandom cyclic: all values in type used before repeatingIDs, opcodes (want full coverage of enum)

Example: If cmd is randc bit [1:0], it will generate: 0, 3, 1, 2, 0, 1, 3, 2, ... (permutations). Guaranteed to hit all 4 values before repeating.

Constraint Blocks & Rules

A constraint block is a set of Boolean expressions that the randomizer must satisfy. Declare them with the constraint keyword:

class AXI_Request; rand bit [31:0] addr; rand bit [7:0] len; rand bit [2:0] size; // Constraint block: address must be aligned to size constraint align { (size == 3'b000) -> addr[0:0] == 0; // 1-byte: any addr (size == 3'b001) -> addr[1:0] == 0; // 2-byte: 2-aligned (size == 3'b010) -> addr[3:0] == 0; // 4-byte: 4-aligned (size == 3'b011) -> addr[7:0] == 0; // 8-byte: 8-aligned } // Another constraint: length must be 1-256 constraint len_range { len >= 1; len <= 256; } // Yet another: forbidden region constraint no_bootrom { addr < 32'h0000_0000 || addr >= 32'h0001_0000; } endclass

Key points:

Weighted & Guided Distributions

Sometimes you don't want uniform random. You want weighted random: hit certain values more often. Use dist or inside:

class Traffic; rand bit [7:0] opcode; rand bit [31:0] burst_len; // Weighted distribution: READ 50%, WRITE 30%, IDLE 20% constraint opcode_dist { opcode dist { 8'h01 := 50, 8'h02 := 30, 8'h00 := 20 }; } // Use inside for range: stress long bursts (80%) vs short (20%) constraint burst_stress { burst_len inside { [1024:65535] := 80, [1:1023] := 20 }; } endclass

Syntax: dist { value1 := weight1, value2 := weight2, ... }

Implication Constraints (->)

The -> operator is logical implication: if the left side is true, the right side must be true.

class Memory_Access; rand bit is_write; rand bit [31:0] addr; rand bit [31:0] data; rand bit [3:0] mask; // If write, data and mask are random. If read, ignore them. constraint write_deps { is_write -> (mask != 0); // Write: mask must not be all zeros !is_write -> (mask == 0); // Read: mask must be zero (ignored) } // Another: unaligned reads not allowed, aligned writes okay constraint alignment { (addr[1:0] != 0) -> is_write; // If misaligned, MUST be write } endclass

Rules:

Full Testbench Example: AXI Protocol

Let's build a constrained-random AXI write request generator:

class AXI_Write_Req; rand bit [11:0] awid; rand bit [31:0] awaddr; rand bit [7:0] awlen; rand bit [2:0] awsize; rand bit [1:0] awburst; // ID in range 0-4095 constraint id_c { awid inside { [0:4095] }; } // Address 4-byte aligned (size = 2, so 4-byte transfers) constraint addr_c { awsize == 3'b010; awaddr[1:0] == 0; } // Length 1-256 beats constraint len_c { awlen >= 0; awlen <= 255; } // Burst type: 0=FIXED, 1=INCR, 2=WRAP (no 3) constraint burst_c { awburst inside { [0:2] }; } // WRAP length: if WRAP, len must be power-of-2 aligned constraint wrap_c { (awburst == 2) -> (awlen inside { [0,1,3,7,15,31,63,127,255] }); } function void print(); $display("[AXI_W] ID=%0d, Addr=%08h, Len=%0d, Size=%0d, Burst=%0d", awid, awaddr, awlen, awsize, awburst); endfunction endclass module axi_gen_tb(); AXI_Write_Req req; initial begin req = new(); repeat(1000) begin if(req.randomize()) begin req.print(); end else begin $display("ERROR: Failed to randomize!"); end end $finish; end endmodule

Run this and 1,000 different valid AXI requests are generated, all satisfying alignment, range, and protocol rules. No manual test case writing!

Controlling Randomization

You can enable/disable constraints and reseed the random number generator:

class Packet; rand bit [7:0] payload; constraint size_c { payload inside { [0:127] }; } endclass Packet p = new(); // Normal randomization p.randomize(); // Constraints applied // Disable a specific constraint p.size_c.constraint_mode(0); p.randomize(); // payload can be 0-255 now // Re-enable p.size_c.constraint_mode(1); // Seed for reproducibility srandom(12345); p.randomize(); // Same sequence every run

FAQ & Takeaways

Q: What if my constraints are unsatisfiable?
A: randomize() returns 0 (false). Check your constraints are logically consistent. Use $display to verify a few values generate correctly.
Q: How does the constraint solver work?
A: SystemVerilog uses a SAT (satisfiability) solver behind the scenes. It finds variable assignments that make all constraints true. You don't write the solver—just declare the rules.
Q: Can I add constraints dynamically?
A: Not after class definition, but you can inherit and add new constraints in child classes (see Day 8: inheritance).

🎯 Takeaways:

Tomorrow (Day 13): Functional coverage — measure what you've tested, not just how many tests you ran.