T5 1 Paper Constraints
T5 1 Paper Constraints
T5 1 Paper Constraints
I.
INTRODUCTION
Over the years, Constrained Random Verification (CRV) became the market focus. CRV, in its most ideal
form, is seen as an effective way in improving the verification process; it is easier to build a single Constrained
Random (CR) test that is equivalent to many directed tests (despite the fact that building a CRV environment
would be more complex than its Directed counterpart would be). However, CRV cannot be used in a standalone
manner; it needs to go hand-in-hand with a measurement strategy to assess the Design Under Verification (DUV)
verification progress. Here, the term Coverage Driven Verification (CDV)1 arose, in which a coverage model that
represents different features of the DUV to be verified is built, and coverage is collected during CR tests run.
Although CR stimuli provide a tremendous value towards faster functional coverage closure over directed tests,
yet modeling and debugging constraints, and random stimuli, is not trivial and suffer many challenges.
In programing, a gotcha is a documented language feature, which, if missed, causes unexpected or
unintuitive behavior. This paper illustrates the top most common SystemVerilog and UVM constrained random
gotchas, which when carefully studied and addressed would help: 1) eliminate/reduce unnecessary debug times
when encountering unexpected randomization failures, 2) eliminate/reduce unexpected randomization results, 3)
eliminate/reduce random instability, and 4) ensure efficiency when coding random constraints.
II.
This section provides a very basic knowledge about the SystemVerilog constrained random2. A given
randomization problem consists of a set of random variables and a set of randomization constraints. A constraint
solver is the engine attempting to solve the randomization problem at hand following pre-known steps.
A. SystemVerilog randomization methods
The SystemVerilog language provides multiple methods to generate and manipulate random data:
$urandom(): System function can be called in a procedural context to generate a pseudo-random number.
$urandom_range(): System function returns an unsigned random integer value within a specified range.
randomize(): Built-in class method used to randomize class fields with rand/randc qualifiers according to
predefined constraints. It can accept inline constraints using the with clause in addition to the
constraints defined in a class context. It can be called to recursively randomize all random variables of a
class, or to randomize specific variable(s) as well (either defined with the rand qualifier or not), keeping
all pre-defined constraints satisfiable.
std::randomize(): Can be called outside the class scope to randomize non-class members. Can accept
inline constraints using the with clause.
The IEEE 1364 Verilog language provided other randomization system functions like $random and $dist_*,
these functions should not be used in SystemVerilog as they are not part of the random stability model.
B. SystemVerilog randomization constraints
Constraints are expressions that need to be held true by the constraint solver when solving a
randomization problem. Constraint expressions may include random variables, non-random state
variables, operators, distributions, literals, and constants.
Constraints can be defined as explicit properties of a class, or specified in-line with randomize() calls.
Constraints special operators: inside (set membership), -> (implication), dist (distribution/weighting),
foreach (iteration), if..else (conditional), and solve..before (probability and distribution).
III.
Typically, randomization failures occur when constraints contradictions occur. However, often spotting out
the constraints contradictions root cause is not a straightforward task. This section highlights the root causes of
common unexpected randomization failures gotchas during simulation runtime, and the means to resolve them.
A. My randomization attempt failed and I was not notified
Problem Description
The randomize() method by default returns 1 if it has successfully set all the random variables and objects to
valid values; otherwise, it returns 0. Also, a randomization attempt is atomic all-or-none procedure. That means
for any constraint that cannot be satisfied, randomize() does not update any random variable and returns a status
of 0. If the return status of randomize() was not captured/checked, then you force the simulator to void cast the
result of randomize(). This can result in a silent failure, which could waste your time trying to figure it out.
class instr_burst;
rand bit [15:0] addr, start_addr, end_addr;
rand bit [3:0] len;
constraint addr_range {addr >= start_addr; addr <= end_addr len;}
endclass
instr_burst i1 = new;
i1.randomize() with {start_addr != 0; end_addr == 16'h0008; len == 4'h8;};
The second method is to wrap your randomization with an immediate assert statement. This is easy to add and
makes your code more compact. However, sometimes it could also result in unexpected scenarios; e.g. if you
forgot and used $assertoff() that was applied to these kind of assertions, or if you switched off immediate
assertions firing via your simulator settings[2].
assert(i1.randomize());
This third method is to go for a simulator-dependent way to capture randomization failures. Generally, this is
not the recommended method as it is dependent on simulator configurations that could be easily missed, and
hence cause unexpected behaviors.
B. I am only randomizing a single variable in a class, yet I am encountering a randomization failure
Problem Description
Randomization failures occur after constructing your object and randomizing a single variable of it.
class trans;
rand bit [7:0] a, b, c ;
constraint constr { b < a; }
endclass
initial begin
trans t1 = new;
assert (t1.randomize (b)); //Randomization failure!
end
other constraints in the cluster. In the previous example, one of the constraints denotes that b is smaller than
a. Initially, a has a value 0, and since b cannot hold a negative value (unsigned bit data type), the
randomization fails. Think of it as if the Solver is generating current values for all random variables not passed to
randomize(). So, before initially randomizing a single variable, randomize the entire cluster first. This way, the
remaining random variables will take values that satisfy all constraints rather than holding initial values. Also
note that you can still get randomization failures if your constraints depend on non-random variables that change
dynamically in a way not to satisfy any of the constraints enclosed in the randomization cluster.
assert (t1.randomize);
assert (t1.randomize (b));
class instr;
rand bit [7:0] a, b, c ;
constraint prob{solve a before b;
solve b before c;
solve c before a;}
endclass
unmatched size randc variables. This theory also holds true for any other explicit solving order inferred from
constraints, e.g. using methods in constraints.
constraint c { a[3:0] == b; }
IV.
In real life, figuring the root cause of unexpected random results is not trivial; typically unexpected random
results are observed from a testcase failure or a functional mismatch, denoting a long time wasted during debug.
This section highlights some of the scenarios that result in unexpected randomization results.
A. Random values generated change from run to run; I could not reproduce a test failure or validate a fix
Problem Description
Minimal code modifications change the random values generated from run to run, although running with same
code revision, simulator revision, seed, simulation commands, and environmental settings.
Illustration & Remedy
Random stability is a major issue when it comes to day-by-day development; we normally seek (and expect)
identical generated random values upon minimal code changes. Random stability is crucial in order to: 1) Get
consistent results that are important for analysis, development, and verification closure. 2) Replicate bugs,
eliminate their escape, and test bug fixes. The element responsible for generating random values in
SystemVerilog is called Random Number Generator, abbreviated RNG. Each thread, package, module instance,
program instance, interface instance, or class instance has a built-in RNG. Thread, module, program, interface
and package RNGs are used to select random values for $urandom() (as well as $urandom_range(),
std::randomize(), randsequence, randcase, and shuffle()), and to initialize the RNGs of child threads or child
class instances. A class instance RNG is used exclusively to select the values returned by the classs predefined
randomize() method[3].
Whenever an RNG is used either for selecting a random value, or for initializing another RNG, it will change
state so that the next random number it generates is different. Therefore, the random value a specific
randomization call generates depends on the number of times the RNG has been used, and on its initialization.
Initializing the RNG, in turn, depends on the number of times its parent RNG has been used and on the parent
RNG initialization. The top most RNG is always a module, program, interface or package RNG, and all of these
RNGs are initialized to the same value, which is chosen by the simulator according to the simulation seed. For a
5
given randomize() call, the process is essentially the same up to the point where the object is allocated. Once the
object is allocated, it gets its own RNG which, unlike package, module, program, interface or thread RNGs,
changes state only when randomize() is called. Therefore, from instantiation point onwards, the only instructions
that affect the results of a given randomize() call are earlier randomize() calls. In the example below, the random
scenarios generated by randomizing the rw_s sequence will totally change when instantiating a new sequence
object rand_s before the rw_s sequence object instantiation.
virtual task body;
random_seq rand_s = new; //Affects random stability of Line A
simple_seq rw_s
= new;
fork begin
assert (rw_s.randomize()); //Line A: Randomize "rw_s" test sequence
rw_s.start(); //Drive the sequence
end
...
join
endtask
Instead of depending on the absolute execution path for a thread, or on the ordering of an object construction,
the RNG of a given thread or an object can be manually set to a specific known state. This makes the execution
path up to a point dont care. This is known as manual seeding, which is a powerful tool to guarantee random
stability upon minimal code changes. Manual seeding can be performed using:
srandom(): Takes an integer argument acting as the seed. Once called on a process id or a class object, it
manually sets the process (or object) RNG to a specific known state, making any subsequent random
results depend only on the relative execution path from the manual seeding point onwards.
Random stability is addressed carefully in the Universal Verification Methodology (UVM)[4]: a) UVM
components are re-seeded during their construction based on their type and full path names. b) Sequences are reseeded automatically before their actual start.
B. Unexpected negative values are generated upon randomize
Problem Description - 1
Randomization attempts do what you ask them to do. If you gave them signed types, their solution space will
accommodate for negative values as well. This rule applies to any variable declared as signed, as well as variables
of type int or byte. Not taking this into consideration can result in performance penalty, unexpected results, and
sometimes randomization failures.
Illustration & Remedy - 1
Always check the sign nature of your random variables and make sure you are not mistakenly defining
variables as signed, and vice versa. I.e. 1) Do not use the signed modifier when not needed, 2) For 7-bit variables
of unsigned nature, use bit [7:0] instead of byte data type, 3) For 32-bit variables of unsigned nature, use bit
[31:0] instead of int data type.
Problem Description - 2
Issues may also arise the other way around, that is, when using unsigned data types to hold negative values.
Take a look at the following example:
You would expect that after the randomize() call the dynamic array will be generated with an arbitrary size
and its elements will be randomized; however, this is not the case! The randomize call here will exit with no error
or warning, and the dynamic array will not be resized, retaining its previous size, 0.
Illustration & Remedy
The SystemVerilog LRM states that the size of a dynamic array or queue declared as rand or randc can also
be constrained. In that case, the array shall be resized according to the size constraint. If a dynamic arrays size is
not constrained, then the array shall not be resized. Initially the size of the dynamic array is zero.
assert(c1.randomize() with {dyn_arr.size() < 10;});
Another important aspect is that the Solver will NOT instantiate new class objects when resizing a dynamic
array of class handles, this sometimes result in unexpected runtime fatal errors especially to people with other
HVLs background. It has to be carefully kept in mind that randomize() does not instantiate class objects.
D. Output random distribution is not expected when using the dist operator
Problem Description
7
When using dist constraints on the following form, the Solver always generates some values (12 and 31)
and never generates others.
class c;
randc bit [1:0]
a ;
randc bit [4:0]
b ;
constraint c_trans {
(a != 2'b01) -> (b < 5'h10); //#1
(a == 2'b01) -> (b >= 5'h10); //#2
}
endclass
Illustration
Even when variables are explicitly defined with the randc modifier, their intended cyclic random behavior can
be compromised upon constraints dependencies. The order in which randc variables are solved is tool dependent
as not defined by the SystemVerilog LRM, so if the tool chooses to solve one of the variables first, it can
compromise the cyclic nature of the second variable. So the Solver here has two options: either to throw a
randomization failure, or to compromise the intended cyclic random behavior of one of the randc variables for the
randomization attempt to be successful.
F. My inline constraints are not applied
Problem Description
The following example shows an attempt to randomize a transaction that constrains the transaction address to
be equal to the calling sequence address. However, t.addr and seq.addr are not equal after the randomization
attempt!
class trans;
rand bit [31:0] addr;
endclass
class seq;
rand bit [31:0] addr;
trans t;
assert(t.randomize() with {t.addr == addr;});
endclass
Other scenarios with the same symptom (a constraint being ignored), could occur when:
H. Random values generated change from run to run when running with different simulators.
Problem Description
The simulation runtime random variables generated are different when running on different simulators,
although running the same source code revision with the same seed, environment, and equivalent commands.
Illustration & Remedy
Different simulators use different constraint solvers that cannot be compared to each other; a simulator A
invoked with initial seed S, would probably generate totally different random stimulus than simulator B invoked
with the same initial seed S. This might even be true for different versions of the same simulator. So if you tend to
use different simulators in your daily verification tasks make sure to: 1) Build reference models and self-checking
testbenches so that different constrained random values generated during simulation may not be troublesome. 2)
Build Coverage models to assess tests effectiveness. 3) Stick to the same version of the same simulator and the
same seed during debug times or when reproducing failures. 4) Leverage manual seeding for random stability.
I. I am getting unexpected random results when using default constraints
Problem Description
Although a default constraint is defined, random values generated are not compliant with the constraint. Take
a look at the below example, sometimes values generated for y are smaller than z.
J. My foreign language random generation is not affected by the initial simulation seed change
Problem Description
Imagine a design that contains some C code that performs some random generation while the C
implementation is connected to the SystemVerilog implementation using the Direct Programming Interface (DPIC). However, the initial simulation seed is not affecting random numbers generated by the C code.
Illustration & Remedy
Normally, the initial SystemVerilog simulation seed, set by the simulator or by the user via simulation
plusargs, affects the SystemVerilog code only; it does not affect the foreign language code. This can be resolved
by passing the simulation initial seed to the foreign language (e.g. C/C++) code as follows:
1.
2.
3.
SystemVerilog side
//
$urandom;
initial
set_foreign_seed (global_seed);
REFERENCES
[1]
IEEE Standard for SystemVerilog, Unified Hardware Design, Specification, and Verification Language, IEEE Std 1800-2012, 2012.
[2]
Verilog and SystemVerilog Gotchas: 101 Common Coding Errors and How to Avoid Them, Stuart Sutherland and Don Mills,
Springer.
[3]
UVM Random Stability: Dont leave it to chance, Avidan Efody, DVCon 2012.
[4]
[5]
10