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()

with 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:

  • if_then – simple if block

  • else_if – else if clause

  • else_then – terminating else clause

@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, te solve_order statement causes b to have values evenly distributed between the value sets [4] and [0..3,5..255].

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():