PyVSC Constraints
Constraint Blocks
Constraint blocks are class methods decorated with the constraint
decorator. Dynamic constraint blocks are decorated with the
dynamic_constraint
decorator.
Constraint blocks are ‘virtual’, in that constraints can be overridden by inheritance.
@vsc.randobj
class my_base_s(object):
def __init__(self):
self.a = vsc.rand_bit_t(8)
self.b = vsc.rand_bit_t(8)
self.c = vsc.rand_bit_t(8)
self.d = vsc.rand_bit_t(8)
@vsc.constraint
def ab_c(self):
self.a < self.b
@vsc.randobj
class my_ext_s(my_base_s):
def __init__(self):
super().__init__()
self.a = vsc.rand_bit_t(8)
self.b = vsc.rand_bit_t(8)
self.c = vsc.rand_bit_t(8)
self.d = vsc.rand_bit_t(8)
@vsc.constraint
def ab_c(self):
self.a > self.b
Instances of my_base_s
will ensure that a
is less than b
. Instances
of my_ext_s
will ensure that a
is greater than b
.
Expressions
Dynamic-constraint Reference
Constraint blocks decorated with constraint
always apply.
Dynamic-constraint blocks, decorated with dynamic_constraint
only
apply when referenced. A dynamic constraint is referenced using syntax
similar to a method call.
Dynamic constraints provide an abstraction mechanism for applying a condition without knowing the details of what that condition is.
@vsc.randobj
class my_cls(object):
def __init__(self):
self.a = vsc.rand_uint8_t()
self.b = vsc.rand_uint8_t()
@vsc.constraint
def a_c(self):
self.a <= 100
@vsc.dynamic_constraint
def a_small(self):
self.a in vsc.rangelist(vsc.rng(1,10))
@vsc.dynamic_constraint
def a_large(self):
self.a in vsc.rangelist(vsc.rng(90,100))
my_i = my_cls()
my_i.randomize()
with my_i.randomize_with() as it:
it.a_small()
with my_i.randomize_with() as it:
it.a_large()
with my_i.randomize_with() as it:
it.a_small() | it.a_large()
The example above defines two dynamic constraints. One ensures that the
range of a
is inside 1..10, while the other ensures that the range of
a
is inside 90..100.
The first randomization call results in a value of a across the full
value of a
(0..100).
The second randomization call results in the value of a
being 1..10.
The third randomization call results in the value of a
being 90..100.
The final randomization call results in the value of a
being either
1..10 or 90..100.
in
PyVSC provides two ways of expressing set-membership constraints. Python’s
in
operator may be used directly to express simple cases. More complex
cases, including negation of set-membership, may be captured using the
inside
and not_inside
methods on PyVSC scalar data types.
The in
constraint ensures that the value of the specified variable
stays inside the specified ranges. Both individual values and
ranges may be specified. In the example below, the value of a
will be
1, 2, or 4..8. The value of b
will be between c
and d
(inclusive).
The right-hand side of an ‘in’ constraint must be a rangelist
expression.
Elements in a rangelist
may be:
- individual expressions
- ranges of expressions, using rng
or a tuple of two expressions
- a list of expressions or ranges
@vsc.randobj
class my_s(object):
def __init__(self):
self.a = vsc.rand_bit_t(8)
self.b = vsc.rand_bit_t(8)
self.c = vsc.rand_bit_t(8)
self.d = vsc.rand_bit_t(8)
@vsc.constraint
def ab_c(self):
self.a in vsc.rangelist(1, 2, vsc.rng(4,8))
self.c != 0
self.d != 0
self.c < self.d
self.b in vsc.rangelist(vsc.rng(self.c,self.d))
PyVSC scalar data types provide inside
and not_inside
methods that to express
set membership.
@vsc.randobj
class my_s(object):
def __init__(self):
self.a = vsc.rand_bit_t(8)
self.b = vsc.rand_bit_t(8)
self.c = vsc.rand_bit_t(8)
self.d = vsc.rand_bit_t(8)
@vsc.constraint
def ab_c(self):
self.a in vsc.rangelist(1, 2, vsc.rng(4,8))
self.c != 0
self.d != 0
self.c < self.d
self.b.inside(vsc.rangelist(1, 2, 4, 8))
self.c.not_inside(vsc.rangelist(1, 2, 4, 8))
In the example above, the b
variable will be inside the range (1,2,4,8).
The c
variable will be outside (ie not equal to) (1,2,4,8)
Mutable Rangelists
It is sometimes useful to change the value/range list used in an
Membership test operations
constraint between randomizations. The rangelist
class can be
constructed as a class member, referenced in constraints, and modified
between calls to randomize
.
The rangelist
class provides three methods to modify the values in
a rangelist after it has been created:
append() – Add a new value or range tuple
clear() – Remove all previously-added ranges
extend() – Add a list of values and/or range tuples to the rangelist
@vsc.randobj
class Selector():
def __init__(self):
self.availableList = vsc.rangelist((0,900))
self.selectedList = vsc.rand_list_t(vsc.uint32_t(), 15)
@vsc.constraint
def available_c(self):
with vsc.foreach(self.selectedList) as sel:
sel.inside(self.availableList)
def getSelected(self):
'''Returns a sorted list of selected integers.'''
selected = []
for resource in self.selectedList:
selected.append(int(resource))
selected.sort()
return selected
selector = Selector()
selector.randomize()
selector.availableList.clear()
selector.availableList.extend([(1000, 2000)])
selector.randomize()
In the example above, the rangelist is initially created to contain
a value range of 0..900. All values in the selectedList
produced
by the first randomization will fall in this range.
The rangelist is subsequently cleared, and a new range 1000..2000 added. The second randomization will produce values in the 1000..2000 range.
part select
@vsc.randobj
class my_s(object):
def __init__(self):
self.a = vsc.rand_bit_t(32)
self.b = vsc.rand_bit_t(32)
self.c = vsc.rand_bit_t(32)
self.d = vsc.rand_bit_t(32)
@vsc.constraint
def ab_c(self):
self.a[7:3] != 0
self.a[4] != 0
self.b != 0
self.c != 0
self.d != 0
Statements
dist
Distribution constraints associate weights with values or value ranges of the specified variable.
@vsc.randobj
class my_c(object):
def __init__(self):
self.a = vsc.rand_uint8_t()
@vsc.constraint
def dist_a(self):
vsc.dist(self.a, [
vsc.weight(1, 10),
vsc.weight(2, 20),
vsc.weight(4, 40),
vsc.weight(8, 80)])
Any otherwise-legal values for the variable that does not have a non-zero weight associated will be excluded from the legal value set. The example above associates non-zero weights with 1, 2, 4, 8. So, a value such as ‘3’ will not be produced.
@vsc.randobj
class my_c(object):
def __init__(self):
self.a = vsc.rand_uint8_t()
@vsc.constraint
def dist_a(self):
vsc.dist(self.a, [
vsc.weight((10,15), 80),
vsc.weight((20,30), 40),
vsc.weight((40,70), 20),
vsc.weight((80,100), 10)])
Ranges for weights are specified as a tuple, as shown above.
foreach
foreach constraints are modeled with the foreach
class. By default,
the foreach iterator is a reference to the current element of the array.
@vsc.randobj
class my_s(object):
def __init__(self);
self.my_l = vsc.rand_list_t(vsc.uint8_t(), 4)
@vsc.constraint
def my_l_c(self):
with vsc.foreach(self.my_l) as it:
it < 10
The foreach
class supports control over whether the item, index,
or both is provided for use in constraints.
Here is an example of requesting the index instead of the iterator.
@vsc.randobj
class my_s(object):
def __init__(self);
self.my_l = vsc.rand_list_t(vsc.uint8_t(), 4)
@vsc.constraint
def my_l_c(self):
with vsc.foreach(self.my_l, idx=True) as i:
self.my_l[i] < 10
Here is an example of explicitly requesting the iterator.
@vsc.randobj
class my_s(object):
def __init__(self);
self.my_l = vsc.rand_list_t(vsc.uint8_t(), 4)
@vsc.constraint
def my_l_c(self):
with vsc.foreach(self.my_l, it=True) as it:
it < 10
Now, finally, here is an example of having both an iterator and index.
@vsc.randobj
class my_s(object):
def __init__(self);
self.my_l = vsc.rand_list_t(vsc.uint8_t(), 4)
@vsc.constraint
def my_l_c(self):
with vsc.foreach(self.my_l, it=True, idx=True) as (i,it):
it == (i+1)
if/else
if/else constraints are modeled using three statements:
@vsc.randobj
class my_s(object):
def __init__(self):
self.a = vsc.rand_bit_t(8)
self.b = vsc.rand_bit_t(8)
self.c = vsc.rand_bit_t(8)
self.d = vsc.rand_bit_t(8)
@vsc.constraint
def ab_c(self):
self.a == 5
with vsc.if_then(self.a == 1):
self.b == 1
with vsc.else_if(self.a == 2):
self.b == 2
with vsc.else_if(self.a == 3):
self.b == 4
with vsc.else_if(self.a == 4):
self.b == 8
with vsc.else_if(self.a == 5):
self.b == 16
implies
@vsc.randobj
class my_s(object):
def __init__(self):
super().__init__()
self.a = vsc.rand_bit_t(8)
self.b = vsc.rand_bit_t(8)
self.c = vsc.rand_bit_t(8)
self.d = vsc.rand_bit_t(8)
@vsc.constraint
def ab_c(self):
self.a == 5
with vsc.implies(self.a == 1):
self.b == 1
with vsc.implies(self.a == 2):
self.b == 2
with vsc.implies(self.a == 3):
self.b == 4
with vsc.implies(self.a == 4):
self.b == 8
with vsc.implies(self.a == 5):
self.b == 16
soft
Soft constraints are enforced, except in cases where they violate a hard constraint. Soft constraints are often used to set default values and relationships, which are then overridden by another constraint.
@vsc.randobj
class my_item(object):
def __init__(self):
self.a = vsc.rand_bit_t(8)
self.b = vsc.rand_bit_t(8)
@vsc.constraint
def ab_c(self):
self.a < self.b
vsc.soft(self.a == 5)
item = my_item()
item.randomize() # a==5
with item.randomize_with() as it:
it.a == 6
The soft
constraint applies to a single expression, as shown above.
Soft constraints are disabled if they conflict with another hard
constraint declared in the class or introduced as an inline constraint.
solve_order
Solve-order constraints are used to provide the user control over
value distributions by ordering solve operations. The PyVSC solve_order
statement corresponds to the SystemVerilog solve a before b
statement.
@vsc.randobj
class my_c(object):
def __init__(self):
self.a = vsc.rand_bit_t()
self.b = vsc.rand_uint8_t()
@vsc.constraint
def ab_c(self):
vsc.solve_order(self.a, self.b)
with vsc.if_then(self.a == 0):
self.b == 4
with vsc.else_then:
self.b != 4
In the example above, the solve_order
statement causes b
to
have values evenly distributed between the value sets [4] and
[0..3,5..255].
Use lists of variables to create multiple solve-order constraints.
The example below solves a
and b
before c
and d
.
@vsc.constraint
def abcd_c(self):
vsc.solve_order([self.a, self.b], [self.c, self.d])
unique
The unique
constraint ensures that all variables in the specified list have
a unique value.
@vsc.rand_obj
class my_s(object):
def __init__(self):
self.a = vsc.rand_bit_t(32)
self.b = vsc.rand_bit_t(32)
self.c = vsc.rand_bit_t(32)
self.d = vsc.rand_bit_t(32)
@vsc.constraint
def ab_c(self):
self.a != 0
self.b != 0
self.c != 0
self.d != 0
vsc.unique(self.a, self.b, self.c, self.d)
Customizing Constraint Behavior
In general, the bulk of constraints should be declared inside a class and should always be enabled. However, there are often cases where these base constraints need to be customized slightly when the class is used in a test. PyVSC provides several mechanisms for customizing constraints.
Randomize-With
Classes decorated with the randobj
decorator are randomized by calling
the randomize
method, as shown in the example below.
@vsc.randobj
class my_base_s(object):
def __init__(self):
self.a = vsc.rand_bit_t(8)
self.b = vsc.rand_bit_t(8)
@vsc.constraint
def ab_c(self):
self.a < self.b
item = my_base_s()
item.randomize()
PyVSC also provides a randomize_with
method that allows additional
constraints to be added in-line. The example below shows using this
to constraint a
to explicit values.
@vsc.randobj
class my_base_s(object):
def __init__(self):
self.a = vsc.rand_bit_t(8)
self.b = vsc.rand_bit_t(8)
@vsc.constraint
def ab_c(self):
self.a < self.b
item = my_base_s()
for i in range(10):
with item.randomize_with() as it:
it.a == i
Constraint Mode
All constraints decorated with the constraint
decorator can be enabled
and disabled using the constraint_mode
method. This allows constraints
to be temporarily turned off. For example, a constraint that enforces
valid ranges for certain variables might be disabled to allow testing
design response to illegal values.
@vsc.randobj
class my_item(object):
def __init__(self):
self.a = vsc.rand_bit_t(8)
self.b = vsc.rand_bit_t(8)
@vsc.constraint
def valid_ab_c(self):
self.a < self.b
item = my_item()
# Always generate valid values
for i in range(10):
with item.randomize():
item.valid_ab_c.constraint_mode(False)
# Allow invalid values
for i in range(10):
with item.randomize():
Rand Mode
The random mode of rand-qualified fields can be changed using the rand_mode
method. This allows randomization of rand-qualified fields to be programmatically
disabled.
Due to the operator overloading that PyVSC uses to enable direct access to the value of class attributes, a special mode must be entered in order to access or modify rand_mode.
@vsc.randobj
class my_item(object):
def __init__(self):
self.a = vsc.rand_bit_t(8)
self.b = vsc.rand_bit_t(8)
@vsc.constraint
def valid_ab_c(self):
self.a < self.b
item = my_item()
# Randomize both 'a' and 'b'
for i in range(10):
with item.randomize():
# Disable randomization of 'a'
with vsc.raw_mode():
item.a.rand_mode = False
# Randomize only 'b'
for i in range(10):
with item.randomize():