typedef enum {low, mid, high} AddrType;
class MyBus extends Bus;
rand AddrType atype;
constraint addr_range
{
(atype
== low ) =>
-> addr inside { [0
: 15] };
(atype
== mid ) =>
-> addr inside { [16
: 127]};
(atype
== high) =>
-> addr inside {[128
: 255]};
}
endclass
—
Constraints interact bidirectionally. In this
example, the value chosen for addr depends on atype and how it is constrained, and the value chosen for atype depends on
addr and how it is constrained. All expression operators
are treated bidirectionally, including the
implication operator (=> ->).
Occasionally,
it is desirable to perform operations immediately before or after
randomization. That is accomplished via two built-in methods, pre_randomize() and post_randomize(), which are automatically called before and after
randomization. These methods can be overridden overloaded
with the desired functionality:
By
default, pre_randomize() and post_randomize() call
their overridden overloaded parent class
methods. When pre_randomize() or post_randomize() are
overridden overloaded, care must be taken
to invoke the parent class’ methods, unless the class is a base class (has no
parent class), otherwise the base class methods shall not be called.
constraint_declaration ::=
[ static ] constraint
constraint_identifier { { constraint_block } }
constraint_block ::=
solve [ priority ] identifier_list before identifier_list ;
| expression dist { dist_list
} ;
| constraint_expression
constraint_expression ::=
expression ;
| expression
=> -> constraint_set
| if
( expression ) constraint_set [ else constraint_set ]
| expression dist { dist_list
} ;
| foreach ( array_identifier
[ loop_variables
] ) constraint_set
constraint_set ::=
constraint_expression
| { {
constraint_expression } }
dist_list ::= dist_item
{ , dist_item }
dist_item ::=
value_range := expression
| value_range :/ expression
constraint_prototype ::= [ static
] constraint constraint_identifier
extern_constraint_declaration ::=
[ static ] constraint
class_identifier :: constraint_identifier
{ { constraint_block } }
identifier_list ::= identifier { , identifier
}
loop_variables ::= [ index_identifier ] { , [ index_identifier
] } // from Annex A.6.8
constraint_block is a list
of expression statements that restrict the range of a variable or define
relations between variables. A constraint_expression
is any SystemVerilog expression, or one of the constraint-specific
operators: => -> and dist (see Sections 12.4.4 and 12.4.5).
The declarative nature of
constraints imposes the following restrictions on constraint expressions:
— Calling tasks or functions is not allowed. Functions are allowed with certain
limitations (see Section 12.4.11).
— Operators with side
effects, such as ++ and -- are not allowed.
— randc variables cannot be specified in ordering constraints
(see solve...before
in Section 12.4.8).
— dist expressions cannot appear in other expressions (unlike inside); they can only be top-level expressions.
Limitations:
— A dist operation shall not be applied to randc variables.
— A dist expression requires that expression contain at least
one rand variable.
— A dist expression
can only be a top-level constraint (not a predicated constraint).
The implication
operator ( => -> ) can be used to declare an expression
that implies a constraint.
The syntax to
define an implication constraint is:
constraint_expression ::= //
from Annex A.1.9
...
| expression => -> constraint_set
Syntax 12-5—Constraint implication syntax (excerpt from Annex A)
The expression
can be any integral SystemVerilog expression.
The boolean equivalent of the implication operator a => -> b is (!a || b). This states that if the expression is true, then random
numbers generated are constrained by the constraint (or constraint block).
Otherwise the random numbers generated are unconstrained.
The constraint_set represents any valid
constraint or an unnamed constraint block. If the expression is true, all of
the constraints in the constraint block must also be satisfied.
For example:
mode == small => -> len < 10;
mode == large => -> len > 100;
In this
example, the value of mode implies that the value of len shall be constrained to less than 10 (mode == small),
greater than 100 (mode == large), or unconstrained (mode != small and mode != large).
In the
following example:
bit [3:0] a, b;
constraint c { (a == 0) => -> (b == 1);
}
Both a and b are 4 bits, so
there are 256 combinations of a and b. Constraint c says that a == 0 implies
that b == 1,
thereby eliminating 15 combinations: {0,0}, {0,2}, … {0,15}. Therefore, the
probability that a == 0 is thus 1/(256-15) or 1/241.
mode == small => -> len < 10 ;
mode == large => -> len > 100 ;
12.4.7 Iterative Constraints
Iterative constraints allow arrayed
variables to be constrained in a parameterized manner using loop variables and
indexing expressions.
The syntax to define an iterative constraint is:
constraint_expression
::= //
from Annex A.1.9
…
| foreach (array_identifier [ loop_variables ] ) constraint_set
loop_variables ::= [ index_identifier ]{ , [ index_identifier
] }
//
from Annex A.6.8
The foreach construct specifies iteration over the elements of
an array. Its argument is an identifier that designates any type of array
(fixed-size, dynamic, associative, or queue) followed by a list of loop
variables enclosed in square brackets. Each loop variable corresponds to one of
the dimensions of the array.
For example:
class C;
rand byte A[] ;
constraint C1 { foreach
( A [ i ] ) A[i] inside {2,4,8,16}; }
constraint C2 { foreach
( A [ j ] ) A[j] > 2 * j; }
endclass
C1 constrains each element of the array A to be in the set [2,4,8,16]. C2 constrains each element of the array A to be greater than twice its index.
The number of loop variables must not exceed
the number of dimensions of the array variable. The scope of each loop variable
is the foreach
constraint construct, including its constraint_set.
The type of each loop variable is implicitly declared to be consistent with the
type of array index. An empty loop variable indicates no iteration over that
dimension of the array. As with default arguments, a list of commas at the end can
be omitted, thus, foreach(
arr [ j ] ) is a shorthand for foreach( arr
[ j, , , , ] ). It shall be an error for any loop variable to have the same
identifier as the array.
The mapping of loop variables to array
indexes is determined by the dimension cardinality, as described in Section 22.4.
// 1 2 3
3
4 1 2
-> Dimension numbers
int A [2][3][4];
bit [3:0][2:1] B [5:1][4];
foreach( A [ i, j, k ] ) …
foreach( B [ q, r, , s ] ) …
The first foreach causes i to iterate
from 0 to 1, j from 0 to 2, and k from 0 to 3. The second foreach causes q to iterate from 5
to 1, r from 0 to 3, and s from 2 to 1.
Iterative constraints can include predicates. For example:
class C;
rand int
A[] ;
constraint c1 { arr.size
inside {[
constraint c2 { foreach
( A[ k ] ) (k < A.size – 1) => A[k + 1] > A[k]; }
endclass
The first constraint, c1, constrains the
size of the array A to be between 1 and 10. The second constraint, c2,
constrains each array value to be greater than the preceding one, i.e., an
array sorted in ascending order.
Within a foreach, predicate expressions involving only constants, state
variables, object handle comparisons, loop variables, or the size of the array
being iterated behave as guards against the creation of constraints, and not as
logical relations. For example, the implication in constraint c2 above involves
only a loop variable and the size of the array being iterated, thus, it allows
the creation of a constraint only when k < A.size()
– 1, which in this case prevents an out-of-bounds access in the constraint.
Guards are described in more detail in Section 12.4.12
Index expressions can include loop
variables, constants, and state variables. Invalid or out or bound array
indexes are not automatically eliminated; users must explicitly exclude these
indexes using predicates.
The size method of a
dynamic or associative array can be used to constrain the size of the array
(see constraint c1 above). If an array is constrained by both size constraints
and iterative constraints, the size constraints are solved first, and the
iterative constraints next. As a result of this implicit ordering between size
constraints and iterative constraints, the size method shall be treated as a state variable within the foreach block of
the corresponding array. For example, the expression A.size
is treated as a random variable in constraint c1, and as a state variable in
constraint c2. This implicit ordering can cause the solver to fail in some
situations.
class B;
rand bit s;
rand bit [31:0]
d;
constraint c { s => -> d == 0;
}
endclass
class B;
rand bit s;
rand bit [31:0]
d;
constraint c { s => -> d == 0;
}
constraint order
{ solve s before d; }
endclass
12.4.11 Functions in Constraints
Some properties are unwieldy or impossible to express in a single
expression. For example, the natural way to compute the number of 1’s in a
packed array uses a loop:
function int
count_ones ( bit [9:0] w );
for( count_ones
= 0; w != 0; w = w >> 1 )
count_ones += w & 1’b1;
endfunction
Such a function could be used to constrain
other random variables to the number of 1 bits:
constraint C1 { length == count_ones(
v ) };
Without the ability to call a function, this
constraint requires the loop to be unrolled and expressed as a sum of the
individual bits:
constraint C2
{
length
== ((w>>9)&1) + ((w>>8)&1) + ((w>>7)&1) +
((w>>6)&1) + ((w>>5)&1) +
((w>>4)&1)
+ ((w>>3)&1) + ((w>>2)&1) +
((w>>1)&1) + ((w>>0)&1);
}
Unlike the count_ones
function, more complex properties, which require temporary state or unbounded
loops, may be impossible to convert into a single expression. The ability to
call functions, thus, enhances the expressive power of the constraint language
and reduces the likelihood of errors. Note that the two constraints above are
not completely equivalent; C2 is bidirectional (length can constrain w and
vice-versa), whereas C1 is not.
To handle these common cases, SystemVerilog
allows constraint expressions to include function calls, but it imposes certain
semantic restrictions.
¾ Functions that appear in constraint
expressions cannot contain output or ref arguments (const ref are allowed).
¾ Functions that appear in constraint
expressions should be automatic (or
preserve no state information) and have no side effects.
¾ Functions that appear in constraints cannot
modify the constraints, for example, calling rand_mode
or constraint_mode methods.
¾ Functions shall be called before constraints
are solved, and their return values shall be treated as state variables.
¾
Random
variables used as function arguments shall establish an implicit variable
ordering or priority. Constraints that include only variables with higher
priority are solved before other, lower priority, constraints. Random variables
solved as part of a higher priority set of constraints become state variables
to the remaining set of constraints. For example:
class B;
rand int x, y;
constraint C {
x <= F(y); };
constraint D {
y inside { 2, 4, 8 } };
endclass
Forces y to be solved before x. Thus, constraint D is solved separately
before constraint C, which uses the values of y
and F(y) as state variables.
Within each prioritized set of constraints,
cyclical (randc)
variables are solved first.
¾ Circular dependencies created by the
implicit variable ordering shall result in an error.
¾
Function
calls in active constraints are executed an unspecified number of times (at
least once), in an unspecified order.
12.4.12 Constraint guards
Constraint guards are predicate expressions
that function as guards against the creation of constraints, and not as logical
relations to be satisfied by the solver. These predicate expressions are
evaluated before the constraints are solved, and are characterized by involving
only the following items:
¾ constants
¾ state variables
¾ object handle comparisons (comparisons
between two handles or a handle and the constant null)
In addition to the above, iterative
constraints (see Section 12.4.7) also consider loop variables and the size of
the array being iterated as state variables.
Treating these predicate expressions as
constraint guards prevents the solver from generating evaluation errors,
thereby failing on some seemingly correct constraints. This enables users to
write constraints that avoid errors due to nonexistent object handles or array
indices out of bounds. For example, the sort constraint of
the singly-linked list, SList, shown
below is intended to assign a random sequence of numbers that is sorted in
ascending order. However, the constraint expression will fail on the last
element when next.n
results in an evaluation error due to a non-existent handle.
class SList;
rand int n;
rand Slist next;
constraint sort
{ n < next.n;
}
endclass
The error condition above can be avoided by
writing a predicate expression to guard against that condition:
constraint sort { if( next != null ) n
< next.n; }
In the sort constraint
above, the if prevents the
creation of a constraint when next == null,
which in this case avoids accessing a non-existent object. Both implication (->)
and if…else can be used as guards.
Guard expressions can themselves include
sub-expressions that result in evaluation errors (e.g., null references), and
they are also guarded from generating errors. This logical sifting is
accomplished by evaluating predicate sub-expressions using the following
4-state representation:
¾ 0 TRUE Sub-expression evaluates to TRUE
¾ 1 FALSE Sub-expression evaluates to FALSE
¾ E ERROR Sub-expression
causes an evaluation error
¾ R RANDOM Expression
includes random variables and cannot be evaluated
Every sub-expression within a predicate
expression is evaluated to yield one of the above four values. The
sub-expressions are evaluated in an arbitrary order,
and the result of that evaluation plus the logical operation define the outcome
in the alternate 4-state representation. A conjunction (&&),
disjunction (||), or negation (!) of sub-expressions can include some
(perhaps all) guard sub-expressions. The following rules specify the resulting
value for the guard:
¾ Conjunction
(&&): If any one of the sub-expressions
evaluates to FALSE then the guard evaluates to FALSE. Otherwise, if any one
sub-expression evaluates to ERROR then the guard evaluates to ERROR, else the
guard evaluates to TRUE.
¾ If the guard evaluates to FALSE then the
constraint is eliminated.
¾ If the guard evaluates to TRUE then a
(possibly conditional) constraint is generated.
¾ If the guard evaluates to ERROR then an
error is generated and randomize fails.
¾ Disjunction
(||): If any one of the sub-expressions
evaluates to TRUE then the guard evaluates to TRUE. Otherwise, if any one
sub-expression evaluates to ERROR then the guard evaluates to ERROR, else the
guard evaluates to FALSE.
¾ If the guard evaluates to FALSE then a
(possibly conditional) constraint is generated.
¾ If the guard evaluates to TRUE then an
unconditional constraint is generated.
¾ If the guard evaluates to ERROR then an
error is generated and randomize fails.
¾
¾ Negation
(!): If the sub-expression evaluates ERROR
then the guard evaluates to ERROR. Otherwise, if the sub-expression evaluates
to TRUE or FALSE then the guard evaluates to FALSE or TRUE, respectively.
These rules are codified by the following truth tables:
&& |
0 |
1 |
E |
R |
|
|| |
0 |
1 |
E |
R |
|
! |
|
0 |
0 |
0 |
0 |
0 |
|
0 |
0 |
1 |
E |
R |
|
0 |
1 |
1 |
0 |
1 |
E |
R |
|
1 |
1 |
1 |
1 |
1 |
|
1 |
0 |
E |
0 |
E |
E |
E |
|
E |
E |
1 |
E |
E |
|
E |
E |
R |
0 |
R |
E |
R |
|
R |
R |
1 |
E |
R |
|
R |
R |
|
|
|
|
|
|||||||||
Conjunction |
|
Disjunction |
|
Negation |
These above rules are applied recursively
until all sub-expressions are evaluated. The final value of the evaluated
predicate expression determines the outcome as follows:
¾ If the result is TRUE then an unconditional
constraint is generated.
¾ If the result is FALSE then the constraint
is eliminated, and can generate no error.
¾ If the result is ERROR then an unconditional
error is generated and the constraint fails.
¾ If the final result of the evaluation is
RANDOM then a guarded constraint is generated.
When final value is RANDOM a traversal of
the predicate expression tree is needed to collect all conditional guards that
evaluate to RANDOM. When the final value is ERROR, a subsequent traversal of
the expression tree is not required, allowing implementations to issue only one
error.
Example 1:
class D;
int x;
endclass
class C;
rand int
x, y;
D a,b;
constraint c1 { (x < y || a.x > b.x || a.x == 5 ) -> x+y == 10; }
endclass
In the example above, the predicate sub-expressions are (x < y), (a.x > b.x), and (a.x == 5), which are all connected by disjunction. Some possible cases
are:
Case 1:
a is non-null, b is null, a.x is 5.
Since (a.x==5) is true, the fact that b.x generates an error does not result in an
error.
The unconditional constraint (x+y == 10) is generated.
Case 2: a is null
This always results in error,
irrespective of the other conditions.
Case 3:
a is non-null, b is non-null, a.x is 10, b.x is 20.
All the guard sub-expressions evaluate to
FALSE.
The conditional constraint(x<y) => (x+y == 10)is generated.
Example 2:
class D;
int x;
endclass
class C;
rand int
x, y;
D a,b;
constraint c1 { (x < y && a.x > b.x && a.x == 5 ) -> x+y == 10; }
endclass
In the example above, the predicate sub-expressions are (x < y), (a.x > b.x), and (a.x == 5), which are all connected by conjunction. Some possible cases
are:
Case 1:
a is non-null, b is null, a.x is 6.
Since (a.x==5) is false, the fact that b.x generates an error does not result in an
error.
The constraint is eliminated.
Case 2: a is null
This always results in error,
irrespective of the other conditions.
Case 3:
a is non-null, b is non-null, a.x is 5,
b.x is 2.
All the guard sub-expressions evaluate to
TRUE, producing constraint (x<y) -> (x+y == 10).
Example 3:
class D;
int x;
endclass
class C;
rand int
x, y;
D a,b;
constraint c1 { (x < y && (a.x > b.x || a.x ==5)) -> x+y == 10; }
endclass
In the example above, the predicate
sub-expressions are (x < y) and (a.x
> b.x || a.x == 5), which is connected by disjunction. Some
possible cases are:
Case 1:
a is non-null, b is null, a.x is 5.
The guard expression evaluates to (ERROR || a.x==5), which evaluates to (ERROR || TRUE).
The guard sub-expression evaluates to TRUE.
The conditional constraint(x<y) -> (x+y == 10)is generated.
Case 2:
a is non-null, b is null, a.x is 8.
The guard expression evaluates to (ERROR ||
FALSE), and generates an error.
Case 3: a is null
This
always results in error, irrespective of the other conditions.
Case 4:
a is non-null, b is non-null, a.x is 5,
b.x is 2.
All the guard sub-expressions evaluate to
TRUE.
The conditional constraint(x<y) -> (x+y == 10)is generated.
Users can override overload the pre_randomize() in any class to perform initialization and set
pre-conditions before the object is randomized.
Users can override overload the post_randomize() in any class to perform cleanup, print diagnostics,
and check post-conditions after the object is randomized.
If these methods are overridden
overloaded, they must call their associated parent class methods,
otherwise their pre- and post-randomization processing steps shall be skipped.
—
The randomize() method
shall not be overridden overloaded.
blocking_assignment ::=
...
| class_variable_identifier . randomize [ ( )
] with constraint_block ;
class_variable_identifier is the name
of an instantiated object.
12.10
In-line random variable control
The randomize() method can be used to temporarily control the
set of random and state variables within a class instance or object. When the
randomize method is called with no arguments, it behaves as described in the
previous sections, that is, it assigns new values to all random variables in an
object --- those declared as rand or
randc ---
such that all of the constraints are satisfied. When randomize is called with
arguments, those arguments designate the complete set of random variables
within that object; all other variables in the object are considered state
variables. For example, consider the following class and calls to randomize:
class CA;
rand byte x, y;
byte v, w;
constraint c1 { x < v && y > w );
endclass
CA a = new;
a.randomize(); //
random variables: x, y state variables:
v, w
a.randomize( x ); //
random variables: x state
variables: y, v, w
a.randomize( v, w ); // random
variables: v, w state variables: x, y
a.randomize( w, x ); // random
variables: w, x state variables: y, v
This mechanism
controls the set of active random variables for the duration of the call to
randomize, which is conceptually equivalent to making a set of calls to the rand_mode() method to disable or enable the corresponding
random variables. Calling randomize() with arguments
allows changing the random mode of any class property, even those not declared
as rand or randc. This mechanism, however,
does not affect the cyclical random mode: it cannot change a non-random
variable into a cyclical random variable (randc), and cannot change a
cyclical random variable into a non-cyclical random
variables (change from randc to rand).
The scope of the
arguments to the randomize method is the object class. Arguments are limited to
the names of properties of the calling object; expressions are not allowed. The
random mode of local class members
can only be changed when the call to randomize has access to those properties,
that is, within the scope of the class in which the local members are declared.
12.10.1 In-line constraint
checker
Normally, calling the
randomize method of a class that has no random variables causes the method to
behave as a checker, that is, it assigns no random values, and only returns a
status: one if all constraints are satisfied and zero otherwise. The in-line
random variable control mechanism can also be used to force the randomize() method to behave as a checker.
The randomize method
accepts the special argument null to
indicate no random variables for the duration of the call, that is, all class
members behave as state variables. This causes the randomize method to behave
as a checker instead of a generator. A checker evaluates all constraints and
simply returns one if all constraints are satisfied, and zero otherwise. For
example, if class CA defined above executes the following call:
success = a.randomize( null ); // no random variables
Then the solver
considers all variables as state variables and only checks whether the
constraint is satisfied, namely, that the relation (x < v && y > w) is true using the
current values of x, y,
v, and w.
12.11
Randomization of scope variables - ::randomize()
The built-in class
randomize method operates exclusively on class member variables. Using classes
to model the data to be randomized is a powerful mechanism that enables the
creation of generic, reusable objects containing random variables and
constraints that can be later extended, inherited, constrained, overridden,
enabled, disabled, merged with or separated from other objects. The ease with
which classes and their associated random variables and constraints can be
manipulated make classes an ideal vehicle for describing and manipulating random
data and constraints. However, some less-demanding problems that do not require
the full flexibility of classes, can use a simpler
mechanism to randomize data that does not belong to a class. The scope
randomize method, ::randomize(), enables users to randomize
data in the current scope, without the need to define a class or instantiate a
class object.
The syntax of the
scope randomize method is:
statement ::= [ block_identifier : ]
{ attribute_instance } statement_item
// from Annex A.6.4
statement_item ::=
...
| scope_randomize
scope_randomize ::= [::]
randomize ( [ variable_identifier_list ] ) [ with { constraint_block
}
]
variable_identifier_list ::= variable_identifier
{, variable_identifier
}
The scope randomize
method behaves exactly the same as a class randomize method, except that it
operates on the variables of the current scope instead of class member
variables. Arguments to this method specify the variables that are to be
assigned random values, i.e., the random variables.
For example:
module stim;
bit[15:0] addr;
bit[31:0]
data;
function bit gen_stim();
bit success, rd_wr;
success = ::randomize( addr, data,
rd_wr );
return rd_wr ;
endfunction
...
endmodule
The function gen_stim calls ::randomize() with three variables
as arguments: addr, data, and rd_wr. Thus,
::randomize() assigns new random variables to those variables that are
visible in the scope of the gen_stim function.
Note that addr and data
have module scope, whereas rd_wr has scope
local to the function. The above example can also be written using a class:
class stimc;
rand bit[15:0] addr;
rand bit[31:0] data;
rand bit rd_wr;
endclass
function bit gen_stim( stimc p );
bit success;
success = p.randomize();
addr = p.addr;
data
= p.data;
return
p.rd_wr;
endfunction
However, for this simple application, the scope randomize method leads to a straightforward
implementation.
The scope randomize
method returns 1 if it successfully sets all the random variables to valid
values, otherwise it returns 0. If the scope randomize method is called with no
arguments then it behaves as a checker, and simply returns status.
12.11.1 Adding
constraints to scope variables - ::randomize() with
The ::randomize()
with form of the scope randomize method
allows users to specify random constraints to be applied to the local scope
variables. When specifying constraints, the arguments to the scope randomize
method become random variables, all other variables
are considered state variables.
task stimulus( int length );
int a, b, c, success;
success
= ::randomize( a, b, c ) with { a
< b ; a + b < length };
...
success
= ::randomize( a, b ) with { b – a
> length };
...
endtask
The task stimulus
above calls ::randomize twice resulting in two sets of
random values for its local variables a, b, and c. In the first call variables
a and b are constrained such that variable a is less than b, and their sum is
less than the task argument length, which is designated as a state variable. In
the second call, variables a and b are constrained
such that their difference is greater than state variable length.
12.10
Random number system functions and methods
12.10.3
$srandom()
The system function $srandom() method allows manually seeding
the Random Number Generator (RNG) of objects or threads. The RNG of a process can be seeded
using the srandom() method of the process (see Section 9.9).
The syntax for the $ prototype of the srandom() system task method is:
task $ function void srandom( int seed, [class_identifier obj]
);
The $srandom() system
task initializes the local random number generator using the value of the given
seed.
The optional
object argument is used to seed an object instead of the current process
(thread). The top level
randomizer of each program is initialized with $srandom(1) prior to any randomization calls.
The srandom()
method initializes an object's random number generator using the value of the
given seed.
12.10.4 get_randstate()
The get_randstate()
method retrieves the current state an object's Random Number Generator (RNG). The
state of the RNG associated with a process is retrieved using the get_randstate()method
of the process (see Section 9.9).
The prototype of the get_randstate() method is:
function string get_randstate();
The get_randstate() method returns a
copy of the internal state of the RNG associated with the given object.
The RNG state is a
string of unspecified length and format. The length and contents of the string
are implementation dependent.
12.10.5 set_randstate()
The set_randstate()method
sets the state of an object's Random Number Generator (RNG). The state of the
RNG associated with a process is set using the set_randstate()method of the process (see
Section 9.9).
The prototype of the set_randstate()
method is:
function void set_randstate( string state );
The set_randstate() method copies the
given state into the internal state of an object's RNG.
The RNG state is a
string of unspecified length and format. Calling set_randstate() with a string
value that was not obtained from get_randstate() — or
from a different implementation of get_randstate() — is undefined.
initial begin
Foo foo = new();
Bar bar = new();
integer z;
void’(foo.randomize());
// z = $random;
void’(bar.randomize());
end
12.14
Random sequence generation - randsequence
Parser generators,
such as yacc, use a Backus-Naur Form (BNF) or similar
notation to describe the grammar of the language to be parsed. The grammar is
thus used to generate a program that is able to check if a stream of tokens
represents a syntactically correct utterance in that language. SystemVerilog’s sequence generator reverses this process:
it uses the grammar to randomly create a correct utterance (i.e., a stream of
tokens) of the language described by the grammar. The random sequence generator
is useful for randomly generating structured sequences of stimulus such as
instructions or network traffic patterns.
The sequence generator
uses a set of rules and productions within a randsequence block. The syntax of the randsequence block is:
randcase_statement ::= // from
Annex A.6.4
randcase randcase_item { randcase_item } endcase
randcase_item
::= expression : statement_or_null
randsequence ::= randsequence ( [
production_ identifier ] ) //
from Annex A.6.12
production {
production }
endsequence
production ::= [ function_data_type
] production_name [ ( tf_port_list
) ] : rs_rule { | rs_rule } ;
rs_rule ::= rs_production_list [ := expression [ rs_code_block
] ]
rs_production_list ::=
rs_prod
{ rs_prod
}
| rand join [ (expression) ] production_item production_item { production_item
}
rs_ code_block ::= { { data_declaration } {
statement_or_null } }
rs_prod ::=
production_item
| rs_code_block
| rs_if_else
| rs_repeat
| rs_case
production_item ::= production_identifier [
( list_of_arguments
) ]
rs_if_else ::= if (
expression ) production_item
[ else production_item
]
rs_repeat ::= repeat ( expression ) production_item
rs_case ::= case ( expression ) rs_case_item
{ rs_case_item } endcase
rs_case_item ::=
expression
{ , expression } : production_item
| default [:] production_item
Syntax
12-9 —Randsequence syntax (excerpt from Annex A)
A randsequence grammar is
composed of one or more productions. Each production contains a name and a list
of production items. Production items are further classified into terminals and
nonterminals.
Nonterminals are defined in terms of terminals and other nonterminals. A
terminal is an indivisible item that needs no further definition than its
associated code block. Ultimately, every nonterminal is decomposed into its
terminals. A production list contains a succession of production items,
indicating that the items must be streamed in sequence. A single production can
contain multiple production lists separated by the | symbol. Production lists separated by a | imply a set of choices,
which the generator will make at random.
A simple example illustrates the basic
concepts:
randsequence( main )
main : first second done ;
first : add | dec
;
second : pop | push ;
done : { $display(“done”); } ;
add : { $display(“add”); } ;
dec :
{ $display(“dec”);
} ;
pop : { $display(“pop”); } ;
push : { $display(“push”); } ;
endsequence
The production main is defined in terms of three nonterminals: first,
second, and done. When main is chosen, it generates the
sequence, first, second, and done. When first is generated, it is decomposed into its productions, which
specifies a random choice between add
and dec.
Similarly, the second production
specifies a choice between pop and push. All other productions are
terminals; they are completely specified by their code block, which in the
example displays the production name. Thus, the grammar leads to the following
possible outcomes:
add
pop done
add
push done
dec pop done
dec push done
When
the randsequence statement is
executed, it generates a gramar-driven stream of random productions. As each production is generated, the side effects
of executing its associated code blocks produce the desired stimulus. In
addition to the basic grammar, the sequence generator provides for random
weights, interleaving and other control mechanisms. Although the randsequence statement does not intrinsically
create a loop, a recursive production will cause looping.
The randsequence
statement creates an automatic scope. All production identifiers are local to
the scope. In addition, each code block within the randsequence block creates an
anonymous automatic scope. Hierarchical
references to the variables declared within the code blocks are not allowed. To
declare a static variable, the static prefix must be used. The randsequence keyword can be followed by an optional
production name (inside the parenthesis) that designates the name of the
top-level production. If unspecified, the first production becomes the
top-level production.
12.14.1 Random production
weights
The probability that a
production list is generated can be changed by assigning weights to production
lists. The probability that a particular production list is generated is
proportional to its specified weight.
production ::= [ function_data_type ] production_name [ ( tf_port_list ) ] : rs_rule { | rs_rule } ;
rs_rule ::= rs_production_list
[ :=
expression [ rs_code_block ] ]
The := operator assigns the
weight specified by the expression to its production list. Weight expression
must evaluate to integral non-negative values. A weight is only meaningful when assigned to alternative productions, that
is, production list separated by a |. Weight expressions are evaluated when
their enclosing production is selected, thus allowing weights to change
dynamically. For example, the first
production of the previous example can be re-written as:
first : add := 3
| dec
:= 2
;
This
defines the production first in terms
of two weighted production lists add and
dec. The production add will be generated with 60%
probability and the production dec will
be generated with 40% probability.
If no weight is
specified, a production shall use a weight of 1. If only some weights are
specified, the unspecified weights shall use a weight of 1.
12.14.2 If..else production statements
A production can be
made conditionally by means of an if..else production statement. The syntax of the if..else production statement is:
rs_if_else ::= if ( expression ) production_item [ else production_item ]
The expression can be
any expression that evaluates to a boolean
value. If the expression evaluates to true, the production following the
expression is generated, otherwise the production following the optional else statement is generated. For
example:
randsequence()
...
PP_PO : if ( depth < 2 ) PUSH else POP ;
PUSH : { ++depth; do_push();
};
POP : { --depth; do_pop(); };
endsequence
This
example defines the production PP_OP. If the variable depth is less than 2 then
production PUSH is generated, otherwise production POP is generated. The
variable depth is updated by the code blocks of both the PUSH and POP
productions.
12.14.3 Case production
statements
A production can be
selected from a set of alternatives using a case production statement. The syntax of the case production statement is:
rs_case ::= case
( expression ) rs_case_item { rs_case_item } endcase
rs_case_item ::=
expression
{ , expression } : production_item
| default [ :
] production_item
The case production
statement is analogous to the procedural case statement except as noted below.
The case expression is evaluated, and its value is compared against the value
of each case-item expression, which are evaluated and compared in the order in
which they are given. The production associated with the first case-item
expression that matches the case expression is generated. If no matching
case-item expression is found then the production associated with the optional default item is generated, or nothing
if there no default item. Multiple default statements in
one case production statement shall be illegal. Case-item expressions separated by
commas allow multiple expressions to share the production. For example:
randsequence()
SELECT : case ( device & 7 )
0
: NETWORK
1, 2
: DISK
default : MEMORY
endcase ;
...
endsequence
This example defines
the production SELECT with a case statement. The case expression (device & 7) is evaluated and compared against the
two case-item expressions. If the expression matches 0, the production NETWORK
is generated, and if it matches 1 or 2 the production DISK is generated.
Otherwise the production MEMORY is generated.
12.14.4 Repeat production
statements
The repeat production statement is used to
iterate over a production a specified number of times. The syntax of the repeat production statement is:
rs_repeat ::= repeat
( expression ) production_item
The repeat expression
must evaluate to a non-negative integral value. That value specifies the number
of times that the corresponding production is generated. For example:
randsequence()
...
PUSH_OPER : repeat( $urandom_range(
2, 6 ) ) PUSH ;
PUSH : ...
endsequence
In this example the
PUSH_OPER production specifies that the PUSH production be repeated a random
number of times (between 2 and 6) depending on by the value returned by $urandom_range().
The repeat production statement itself
cannot be terminated prematurely. A break
statement will terminate the entire randsequence block
(see Section 12.14.6).
12.14.5 Interleaving
productions – rand join
The rand join production control is used to
randomly interleave two or more production sequences while maintaining the
relative order of each sequence. The syntax of the rand join production control is:
rs_production_list ::=
rs_prod
{ rs_prod
}
| rand join [ (expression) ] production_item production_item { production_item
}
For example:
randsequence( TOP )
TOP : rand join S1 S2 ;
S1 : A B ;
S2 : C D ;
endsequence
The generator will randomly produce the following sequences:
A B C D
A C B D
A C D B
C D A B
C A B D
C A D B
The optional expression following the rand join keywords must be a real
number in the range 0.0 to 1.0. The value of this expression represents the
degree to which the length of the sequences to be interleaved affects the
probability of selecting a sequence. A sequence’s length is the number of
productions not yet interleaved at a given time. If the expression is 0.0, the
shortest sequences are given higher priority. If the expression is 1.0, the
longest sequences are given priority. For instance, using the previous example:
TOP : rand join (0.0) S1 S2 ;
Gives higher priority to the sequences: A
B C D C D A B
TOP : rand join (1.0) S1 S2 ;
Gives higher priority to the sequences: A
C B D A C D B C A B D
C A D B
If unspecified, the generator used the default
value of 0.5, which does not prioritize any sequence length.
At each step, the generator interleaves nonterminal symbols to depth of one.
12.14.6 Aborting productions
- break and return
Two procedural
statements can be used to terminate a production prematurely: break and return. These two statements can appear in any code block; they
differ in what they consider the scope from which to exit.
The break statement terminates the sequence
generation. When a break statement
is executed from within a production code block, it forces a jump out of the randsequence
block. For example:
randsequence()
WRITE : SETUP DATA ;
SETUP : { if( fifo_length >= max_length
) break; } COMMAND ;
DATA : ...
endsequence
next_statement :
...
When the example above
executes the break statement within
the SETUP production, the COMMAND production is not generated, and execution
continues on the line labeled next_statement. Use of the break statement within a loop statement behaves as defined in
Section 8.6. Thus, the break statement terminates the smallest enclosing looping
statement, otherwise the randsequence block.
The return statement aborts the generation
of the current production. When a return
statement is executed from within a production code block, the current
production is aborted. Sequence generation continues with the next production
following the aborted production. For example:
randsequence()
TOP : P1 P2 ;
P1 : A B C ;
P2 : A { if( flag == 1 ) return; } B C ;
A : { $display( “A”
); } ;
B : { if( flag == 2 )
return; $display( “B” ); } ;
C : { $display( “C”
); } ;
endsequence
Depending on the value of variable flag, the
example above displays the following:
flag == 0 ==> A B C A B C
flag == 1 ==> A B C A
flag == 2 ==> A C A C
When flag == 1,
production P2 is aborted in the middle, after generating A. When flag == 2,
production B is aborted twice (once as part of P1 and once as part of P2), but
each time, generation continues with the next production, C.
12.14.7 Value passing
between productions
Data can be passed
down to a production about to be generated, and generated productions can
return data to the nonterminals that triggered their
generation. Passing data to a production is similar to a task call, and uses
the same syntax. Returning data from a production requires that a type be
declared for the production, which uses syntax similar to a function
declaration.
Productions that
accept data include a formal argument list. The syntax for declaring the
arguments to a production is similar to a task prototype; the syntax for
passing data to the production is the same as a task call.
production ::= [ function_data_type ] production_name [ ( tf_port_list ) ] : rs_rule { | rs_rule } ;
production_item ::= production_identifier [ ( list_of_arguments ) ]
For example, the first
example above could be written as:
randsequence( main )
main : first second gen ;
first : add | dec
;
second : pop | push ;
add : gen(“add”) ;
dec : gen(“dec”) ;
pop : gen(“pop”)
;
push : gen(“push”)
;
gen( string s = “done” ) : { $display( s }; } ;
endsequence
In this example, the
production gen accepts a string
argument whose default is “done”. Five other productions generate this
production, each with a different argument (the one in main uses the default).
A production creates a
scope, which encompasses all its rules and code blocks. Thus, arguments passed
down to a production are available throughout the production.
Productions that
return data require a type declaration. The optional return type precedes the
production. Productions that do not specify a return type shall assume a void return type.
A value is returned
from a production by using the return with an expression.
When the return statement is used
with a production that returns a value, it must specify an expression of the
correct type, just like non-void functions. The return statement assigns the given expression to the corresponding
production. The return value can be read in the code blocks of the production
that triggered the generation of the production returning a value. Within these
code blocks, return values are accessed using the production name plus an
optional indexing expression. Within each production, a variable of the same
name is implicitly declared for each production that returns a value. If the
same production appears multiple times then a one-dimensional array that starts
at 1 is implicitly declared. For example:
randsequence( bin_op )
void bin_op
: value operator value // void
type is optional
{ $display(
“%s %b %b”, operator, value[1], value[2] ); }
;
bit [7:0] value : { return
$urandom } ;
string operator
: add := 5 { return “+” ; }
| dec := 2 { return
“-” ; }
| mult := 1 { return “*” ; }
;
endsequence
In the example above,
the operator and value productions return a string and an 8-bit value, respectively.
The production bin_op
includes these two value-returning productions. Therefore, the code block
associated with production bin_op has access to the following implicit variable
declarations:
bit [7:0] value [1:2];
string operator;
Accessing these
implicit variables yields the values returned from the corresponding
productions. When executed, the example above displays a simple three-item
random sequence: an operator followed by two 8-bit values. The operators +, -, and *are chosen with a distribution of 5/8, 2/8, and 1/8,
respectively.
Only the return values
of productions already generated (i.e., to the left of the code block accessing
them) can be retrieved. Attempting to read the return value
of a production that has not been generated results in an undefined value.
For example:
X :
A {int y = B;} B ; // invalid use of B
X :
A {int y = A[2];} B A ; // invalid use of
A[2]
X :
A {int y = A;} B {int j = A + B;} ; //
valid
The sequences produced
by randsequence can be driven directly into a system,
as a side effect of production generation, or the entire sequence can be
generated for future processing. For
example, the following function generates and returns a queue of random numbers
in the range given by its arguments. The first and last queue item correspond to the lower and upper bounds, respectively.
Also, the size of the queue is randomly selected based on the production
weights.
function int[$] GenQueue(int low, int high);
int[$] q;
TOP : BOUND(low) LIST BOUND(high) ;
LIST : LIST
ITEM := 8 { q = { q, ITEM }; }
|
ITEM :=
2 { q = { q, ITEM }; }
;
int ITEM : { return
$urandom_range( low, high ); } ;
BOUND(int b) : { q = { q, b }; } ;
endsequence
GenQueue = q;
endfunction
When the randsequence in function GenQueue
executes, it generates the TOP production, which causes three productions to be
generated: BOUND with argument low, LIST, and BOUND with argument high. The BOUND
production simply appends its argument to the queue. The LIST production
consists of a weighted LIST ITEM production and an ITEM production. The LIST
ITEM production is generated with 80% probability, which causes the LIST
production to be generated recursively, thereby postponing the generation of
the ITEM production. The selection between LIST ITEM and ITEM is repeated until
the ITEM production is selected, which terminates the LIST production. Each
time the ITEM production is generated, it produces a random number in the
indicated range, which is later appended to the queue.
The following example
uses a randsequence
block to produce random traffic for a DSL packet network.
class DSL; ... endclass //
class that creates valid DSL packets
randsequence (STREAM)
STREAM : GAP DATA := 80
| DATA :=
20 ;
DATA : PACKET(0) := 94 { transmit( PACKET ); }
| PACKET(1) := 6 { transmit( PACKET ); } ;
DSL PACKET(bit bad) : { DSL d = new;
if( bad ) d.crc
^= 23; // mangle crc
return d;
} ;
GAP: { ##
$urandom_range( 1, 20 ); };
endsequence
In this example, the
traffic consists of a stream of (good and bad) data packets and gaps. The first
production, STREAM, specifies that 80% of the time the traffic consists of a
GAP followed by some DATA, and 20% of the time it consists of just DATA (no
GAP). The second production, DATA, specifies that 94% of all data packets are good packets, and the remaining 6% are bad packets. The PACKET production
implements the DSL packet creation; if the production argument is 1 then a bad
packet is produced by mangling the crc of a valid DSL
packet. Finally, the GAP production implements the transmission gaps by waiting
a random number of cycles between 1 and 20.