Esempio n. 1
0
    def process_fieldref(self, fm):
        if fm in self._randset_field_m.keys():
            # There's an existing randset that holds this field
            ex_randset = self._randset_field_m[fm]
            if self._active_randset is None:
                self._active_randset = ex_randset
            elif ex_randset is not self._active_randset:
                for f in self._active_randset.fields():
                    # Relink to the new consolidated randset
                    self._randset_field_m[f] = ex_randset
                    ex_randset.add_field(f)
                # TODO: this might be later
                for c in self._active_randset.constraints():
                    ex_randset.add_constraint(c)

                # Remove the previous randset
                self._randset_s.remove(self._active_randset)
                self._active_randset = ex_randset
        else:
            # No existing randset holds this field
            if self._active_randset is None:
                self._active_randset = RandSet()
                self._randset_s.add(self._active_randset)

            # Need to register this field/randset mapping
            self._active_randset.add_field(fm)
            self._randset_field_m[fm] = self._active_randset

        if fm in self._field_s:
            self._field_s.remove(fm)
Esempio n. 2
0
 def build(self, rs : RandSet):
     self.phase = 0
     for f in rs.fields():
         f.accept(self)
     for c in rs.constraints():
         c.accept(self)
     self.phase = 1
     for c in rs.constraints():
         c.accept(self)
Esempio n. 3
0
    def process_fieldref(self, fm):
        if RandInfoBuilder.EN_DEBUG > 0:
            print("--> RandInfoBuilder::process_fieldref %s" % fm.fullname)
        if fm in self._randset_field_m.keys():
            # There's an existing randset that holds this field
            ex_randset = self._randset_field_m[fm]
            if self._active_randset is None:
                self._active_randset = ex_randset
            elif ex_randset is not self._active_randset:
                if RandInfoBuilder.EN_DEBUG > 0:
                    print("RandInfoBuilder: combine two randsets")

                # This field isn't being ordered, so go ahead and
                for f in self._active_randset.fields():
                    # Relink to the new consolidated randset
                    self._randset_field_m[f] = ex_randset
                    ex_randset.add_field(f)

                for c in self._active_randset.constraints():
                    ex_randset.add_constraint(c)

                for c in self._active_randset.soft_constraints():
                    ex_randset.add_constraint(c)

                # Remove the previous randset
                idx = self._randset_m[self._active_randset]
                self._randset_m.pop(self._active_randset)
                self._randset_l[idx] = None
                self._active_randset = ex_randset
            else:
                # Field is handled by a randset that is the active one
                pass
        else:
            if RandInfoBuilder.EN_DEBUG > 0:
                print("RandInfoBuilder: create new randset")

            # No existing randset holds this field
            if self._active_randset is None:
                self._active_randset = RandSet()
                idx = len(self._randset_l)
                self._randset_m[self._active_randset] = idx
                self._randset_l.append(self._active_randset)

            # Need to register this field/randset mapping
            self._active_randset.add_field(fm)
            self._randset_field_m[fm] = self._active_randset

        if fm in self._field_m.keys():
            self._field_m.pop(fm)

        if RandInfoBuilder.EN_DEBUG > 0:
            print("<-- RandInfoBuilder::process_fieldref %s" % fm.fullname)
Esempio n. 4
0
    def visit_expr_array_subscript(self, s: ExprArraySubscriptModel):
        fm = s.getFieldModel()
        if self._pass == 1:
            # During pass 1, build out randsets based on constraint
            # relationships

            # If the field is already referenced by an existing randset
            # that is not this one, we need to merge the sets
            if fm in self._randset_field_m.keys():
                # There's an existing randset that holds this field
                # as a solve target
                ex_randset = self._randset_field_m[fm]
                if self._active_randset is None:
                    self._active_randset = ex_randset
                elif ex_randset is not self._active_randset:
                    for f in self._active_randset.fields():
                        # Relink to the new consolidated randset
                        self._randset_field_m[f] = ex_randset
                        ex_randset.add_field(f)
                    # TODO: this might be later
                    for c in self._active_randset.constraints():
                        ex_randset.add_constraint(c)

                    # Remove the previous randset
                    idx = self._randset_m[self._active_randset]
                    self._randset_m.pop(idx)
                    self._randset_l[idx] = None
                    self._active_randset = ex_randset
            else:
                # No existing randset holds this field as a
                # solve target
                if self._active_randset is None:
                    self._active_randset = RandSet()
                    idx = len(self._randset_l)
                    self._randset_m[self._active_randset] = idx
                    self._randset_l.append(self._active_randset)

                # Need to register this field/randset mapping
                self._active_randset.add_field(fm)
                self._randset_field_m[fm] = self._active_randset

            if fm in self._field_m.keys():
                self._field_m.pop(fm)
Esempio n. 5
0
    def process_fieldref(self, fm):
        if fm in self._randset_field_m.keys():
            # There's an existing randset that holds this field
            ex_randset = self._randset_field_m[fm]
            if self._active_randset is None:
                self._active_randset = ex_randset
            elif ex_randset is not self._active_randset:
                if fm in self._order_s:
                    # This field is part of the ordering constraint set.
                    if self._active_randset.order != -1 and self._active_randset.order < ex_randset.order:
                        # If the active randset is also ordered, and comes before
                        # the one containing this field as primary, then we must
                        # save the constraints
                        self._active_order_randset_s.add(ex_randset)
                        self._active_randset.add_nontarget(fm)
                else:
                    # This field isn't being ordered, so go ahead and
                    for f in self._active_randset.fields():
                        # Relink to the new consolidated randset
                        self._randset_field_m[f] = ex_randset
                        ex_randset.add_field(f)

                    for c in self._active_randset.constraints():
                        ex_randset.add_constraint(c)

                    # Remove the previous randset
                    self._randset_s.remove(self._active_randset)
                    self._active_randset = ex_randset
        else:
            # No existing randset holds this field
            if self._active_randset is None:
                self._active_randset = RandSet()
                self._randset_s.add(self._active_randset)

            # Need to register this field/randset mapping
            self._active_randset.add_field(fm)
            self._randset_field_m[fm] = self._active_randset

        if fm in self._field_s:
            self._field_s.remove(fm)
Esempio n. 6
0
    def build(field_model_l: [FieldModel],
              constraint_l: [ConstraintModel],
              rng=None) -> RandInfo:
        if rng is None:
            #            rng = Random()
            rng = random

        builder = RandInfoBuilder(rng)

        # First, collect all the fields
        builder._pass = 0
        for fm in field_model_l:
            fm.accept(builder)

        builder._randset_s.clear()
        builder._randset_field_m.clear()

        # Create rand sets around explicitly-ordered fields
        for i, o in enumerate(builder._order_l):
            s = RandSet(i)
            s.field_s.add(o)
            builder._randset_s.add(s)
            builder._randset_field_m[o] = s

        # Now, build the randset
        builder._pass = 1
        builder._used_rand = True
        # Visit the field objects passed in, as well
        # as their constraints
        for fm in field_model_l:
            fm.accept(builder)

        # Visit standalone constraints passed to the function
        for c in constraint_l:
            c.accept(builder)

#         for rs in builder._randset_s:
#             print("RS: " + str(rs))
#             for c in rs.constraint_s:
#                 print("RS Constraint: " + str(c))

        randset_l = list(builder._randset_s)
        randset_l.sort(key=lambda e: e.order)

        return RandInfo(randset_l, list(builder._field_s))
Esempio n. 7
0
class RandInfoBuilder(ModelVisitor, RandIF):
    def __init__(self, rng):
        # TODO: need access to the random state
        super().__init__()
        self._pass = 0
        self._field_s = set()
        self._active_constraint = None
        self._active_randset = None

        # Tracks the randsets added due to
        # dependencies against  ordered fields
        self._active_order_randset_s = set()

        self._randset_s: Set[RandSet] = set()
        self._randset_field_m: Dict[FieldModel,
                                    RandSet] = {}  # map<field,randset>
        self._constraint_s: List[ConstraintModel] = []
        self._used_rand = True
        self._in_generator = False
        self.active_cp = None
        self._rng = rng
        self._order_l = []
        self._order_s = set()

    @staticmethod
    def build(field_model_l: [FieldModel],
              constraint_l: [ConstraintModel],
              rng=None) -> RandInfo:
        if rng is None:
            #            rng = Random()
            rng = random

        builder = RandInfoBuilder(rng)

        # First, collect all the fields
        builder._pass = 0
        for fm in field_model_l:
            fm.accept(builder)

        builder._randset_s.clear()
        builder._randset_field_m.clear()

        # Create rand sets around explicitly-ordered fields
        for i, o in enumerate(builder._order_l):
            s = RandSet(i)
            s.field_s.add(o)
            builder._randset_s.add(s)
            builder._randset_field_m[o] = s

        # Now, build the randset
        builder._pass = 1
        builder._used_rand = True
        # Visit the field objects passed in, as well
        # as their constraints
        for fm in field_model_l:
            fm.accept(builder)

        # Visit standalone constraints passed to the function
        for c in constraint_l:
            c.accept(builder)

#         for rs in builder._randset_s:
#             print("RS: " + str(rs))
#             for c in rs.constraint_s:
#                 print("RS Constraint: " + str(c))

        randset_l = list(builder._randset_s)
        randset_l.sort(key=lambda e: e.order)

        return RandInfo(randset_l, list(builder._field_s))

    def randint(self, low: int, high: int) -> int:
        return self._rng.randint(low, high)

    def sample(self, s, k):
        return self._rng.sample(s, k)

    def visit_constraint_block(self, c):
        if not c.enabled:
            # Ignore constraint blocks that aren't enabled
            return

        # Null out the randset on entry to a constraint block
        if self._used_rand:
            self._active_randset = None
            self._constraint_s.append(c)
            super().visit_constraint_block(c)
            self._constraint_s.clear()

    def visit_constraint_solve_order(self, c: ConstraintSolveOrderModel):
        if self._pass == 0:
            for b in c.before_l:
                for a in c.after_l:
                    b_i = -1
                    a_i = -1
                    if b in self._order_s:
                        b_i = self._order_l.index(b)
                    if a in self._order_s:
                        a_i = self._order_l.index(a)

                    if b_i == -1 and a_i == -1:
                        # Just add the elements to the list
                        self._order_l.append(b)
                        self._order_l.append(a)
                        self._order_s.add(a)
                        self._order_s.add(b)
                    elif b_i != -1:
                        self._order_l.insert(b_i + 1, a)
                        self._order_s.add(a)
                    elif a_i != -1:
                        self._order_l.insert(a_i, b)
                        self._order_s.add(b)
                    else:
                        # Found both in the list already.
                        # They might be in the list for good reasons or bad
                        pass

    def visit_constraint_stmt_enter(self, c):
        if self._pass == 1 and len(self._constraint_s) == 1:
            self._active_randset = None
            self._active_order_randset_s.clear()
        self._constraint_s.append(c)
        super().visit_constraint_stmt_enter(c)

    def visit_constraint_stmt_leave(self, c):
        c_blk = self._constraint_s[0]
        self._constraint_s.pop()
        if self._pass == 1 and len(self._constraint_s) == 1:
            if self._active_randset is not None:
                self._active_randset.add_constraint(c)
                for s in self._active_order_randset_s:
                    s.add_constraint(c)
            else:
                #                print("TODO: handle no-reference constraint: " + str(c_blk.name))
                pass
        super().visit_constraint_stmt_leave(c)

    def visit_constraint_dynref(self, c):
        # Treat a dynamic constraint as an inline expansion
        # of the constraints in the referenced block
        for c_stmt in c.c.constraint_l:
            c_stmt.accept(self)

    def visit_constraint_expr(self, c):
        super().visit_constraint_expr(c)

    def visit_constraint_soft(self, c: ConstraintSoftModel):
        super().visit_constraint_soft(c)

    def visit_expr_array_subscript(self, s: ExprArraySubscriptModel):
        fm = s.getFieldModel()
        if self._pass == 1:
            # During pass 1, build out randsets based on constraint
            # relationships

            # If the field is already referenced by an existing randset
            # that is not this one, we need to merge the sets
            if fm in self._randset_field_m.keys():
                # There's an existing randset that holds this field
                # as a solve target
                ex_randset = self._randset_field_m[fm]
                if self._active_randset is None:
                    self._active_randset = ex_randset
                elif ex_randset is not self._active_randset:
                    for f in self._active_randset.fields():
                        # Relink to the new consolidated randset
                        self._randset_field_m[f] = ex_randset
                        ex_randset.add_field(f)
                    # TODO: this might be later
                    for c in self._active_randset.constraints():
                        ex_randset.add_constraint(c)

                    # Remove the previous randset
                    self._randset_s.remove(self._active_randset)
                    self._active_randset = ex_randset
            else:
                # No existing randset holds this field as a
                # solve target
                if self._active_randset is None:
                    self._active_randset = RandSet()
                    self._randset_s.add(self._active_randset)

                # Need to register this field/randset mapping
                self._active_randset.add_field(fm)
                self._randset_field_m[fm] = self._active_randset

            if fm in self._field_s:
                self._field_s.remove(fm)

    def visit_expr_fieldref(self, e):
        if self._pass == 1:
            # During pass 1, build out randsets based on constraint
            # relationships

            # If the field is already referenced by an existing randset
            # that is not this one, we need to merge the sets
            if isinstance(e.fm, FieldArrayModel):
                for f in e.fm.field_l:
                    self.process_fieldref(f)
            else:
                self.process_fieldref(e.fm)

        super().visit_expr_fieldref(e)

    def process_fieldref(self, fm):
        if fm in self._randset_field_m.keys():
            # There's an existing randset that holds this field
            ex_randset = self._randset_field_m[fm]
            if self._active_randset is None:
                self._active_randset = ex_randset
            elif ex_randset is not self._active_randset:
                if fm in self._order_s:
                    # This field is part of the ordering constraint set.
                    if self._active_randset.order != -1 and self._active_randset.order < ex_randset.order:
                        # If the active randset is also ordered, and comes before
                        # the one containing this field as primary, then we must
                        # save the constraints
                        self._active_order_randset_s.add(ex_randset)
                        self._active_randset.add_nontarget(fm)
                else:
                    # This field isn't being ordered, so go ahead and
                    for f in self._active_randset.fields():
                        # Relink to the new consolidated randset
                        self._randset_field_m[f] = ex_randset
                        ex_randset.add_field(f)

                    for c in self._active_randset.constraints():
                        ex_randset.add_constraint(c)

                    # Remove the previous randset
                    self._randset_s.remove(self._active_randset)
                    self._active_randset = ex_randset
        else:
            # No existing randset holds this field
            if self._active_randset is None:
                self._active_randset = RandSet()
                self._randset_s.add(self._active_randset)

            # Need to register this field/randset mapping
            self._active_randset.add_field(fm)
            self._randset_field_m[fm] = self._active_randset

        if fm in self._field_s:
            self._field_s.remove(fm)

    def visit_composite_field(self, f):
        old_used_rand = self._used_rand
        self._used_rand = f.is_used_rand
        super().visit_composite_field(f)
        self._used_rand = old_used_rand

    def visit_scalar_field(self, f):
        if self._pass == 0:
            self._field_s.add(f)

    def visit_enum_field(self, f):
        if self._pass == 0:
            self._field_s.add(f)

    def visit_covergroup(self, cg: CovergroupModel):

        if not self._in_generator or self._pass != 1:
            return

        # TODO: in the case of multiple covergroups, need to have
        # made a higher-level decision about which covergroup to target
        # TODO: determine which coverpoints we're going to enable
        # First try: just select one
        unhit_cp = []
        for cp in cg.coverpoint_l:
            if cp.get_coverage() != 100:
                unhit_cp.append(cp)
        for cp in cg.cross_l:
            if cp.get_coverage() != 100:
                unhit_cp.append(cp)

        if len(unhit_cp) > 0:
            # Only process the target coverpoint
            target_cp = self.randint(0, len(unhit_cp) - 1)
            bin_idx = unhit_cp[target_cp].select_unhit_bin(self)
            bin_expr = unhit_cp[target_cp].get_bin_expr(bin_idx)
            c = ConstraintSoftModel(bin_expr)
            cc = ConstraintBlockModel("coverage", [c])
            cc.accept(self)

    def visit_generator(self, g):
        self._in_generator = True
        super().visit_generator(g)
        self._in_generator = False
Esempio n. 8
0
 def dispose(self, rs : RandSet):
     for f in rs.fields():
         f.accept(self)
     for c in rs.constraints():
         c.accept(self)
Esempio n. 9
0
class RandInfoBuilder(ModelVisitor, RandIF):
    def __init__(self, rng):
        # TODO: need access to the random state
        super().__init__()
        self._pass = 0
        self._field_s = set()
        self._active_constraint = None
        self._active_randset = None
        self._randset_s: Set[RandSet] = set()
        self._randset_field_m: Dict[FieldModel,
                                    RandSet] = {}  # map<field,randset>
        self._constraint_s: List[ConstraintModel] = []
        self._used_rand = True
        self._in_generator = False
        self.active_cp = None
        self._rng = rng

    @staticmethod
    def build(field_model_l: [FieldModel],
              constraint_l: [ConstraintModel],
              rng=None) -> RandInfo:
        if rng is None:
            rng = Random()

        builder = RandInfoBuilder(rng)

        # First, collect all the fields
        builder._pass = 0
        for fm in field_model_l:
            fm.accept(builder)

        # Now, build the randset
        builder._pass = 1
        builder._used_rand = True
        # Visit the field objects passed in, as well
        # as their constraints
        for fm in field_model_l:
            fm.accept(builder)

        # Visit standalone constraints passed to the function
        for c in constraint_l:
            c.accept(builder)

#         for rs in builder._randset_s:
#             print("RS: " + str(rs))
#             for c in rs.constraint_s:
#                 print("RS Constraint: " + str(c))

        return RandInfo(list(builder._randset_s), list(builder._field_s))

    def randint(self, low: int, high: int) -> int:
        return self._rng.randint(low, high)

    def sample(self, s, k):
        return self._rng.sample(s, k)

    def visit_constraint_block(self, c):
        if not c.enabled:
            # Ignore constraint blocks that aren't enabled
            return

        # Null out the randset on entry to a constraint block
        if self._used_rand:
            self._active_randset = None
            self._constraint_s.append(c)
            super().visit_constraint_block(c)
            self._constraint_s.clear()

    def visit_constraint_stmt_enter(self, c):
        if self._pass == 1 and len(self._constraint_s) == 1:
            self._active_randset = None
        self._constraint_s.append(c)
        super().visit_constraint_stmt_enter(c)

    def visit_constraint_stmt_leave(self, c):
        c_blk = self._constraint_s[0]
        self._constraint_s.pop()
        if self._pass == 1 and len(self._constraint_s) == 1:
            if self._active_randset is not None:
                self._active_randset.add_constraint(c)
            else:
                #                print("TODO: handle no-reference constraint: " + str(c_blk.name))
                pass
        super().visit_constraint_stmt_leave(c)

    def visit_constraint_dynref(self, c):
        # Treat a dynamic constraint as an inline expansion
        # of the constraints in the referenced block
        for c_stmt in c.c.constraint_l:
            c_stmt.accept(self)

    def visit_constraint_expr(self, c):
        super().visit_constraint_expr(c)

    def visit_constraint_soft(self, c: ConstraintSoftModel):
        super().visit_constraint_soft(c)

    def visit_expr_array_subscript(self, s: ExprArraySubscriptModel):
        fm = s.getFieldModel()
        if self._pass == 1:
            # During pass 1, build out randsets based on constraint
            # relationships

            # If the field is already referenced by an existing randset
            # that is not this one, we need to merge the sets
            if fm in self._randset_field_m.keys():
                # There's an existing randset that holds this field
                ex_randset = self._randset_field_m[fm]
                if self._active_randset is None:
                    self._active_randset = ex_randset
                elif ex_randset is not self._active_randset:
                    for f in self._active_randset.fields():
                        # Relink to the new consolidated randset
                        self._randset_field_m[f] = ex_randset
                        ex_randset.add_field(f)
                    # TODO: this might be later
                    for c in self._active_randset.constraints():
                        ex_randset.add_constraint(c)

                    # Remove the previous randset
                    self._randset_s.remove(self._active_randset)
                    self._active_randset = ex_randset
            else:
                # No existing randset holds this field
                if self._active_randset is None:
                    self._active_randset = RandSet()
                    self._randset_s.add(self._active_randset)

                # Need to register this field/randset mapping
                self._active_randset.add_field(fm)
                self._randset_field_m[fm] = self._active_randset

            if fm in self._field_s:
                self._field_s.remove(fm)

    def visit_expr_fieldref(self, e):

        if self._pass == 1:
            # During pass 1, build out randsets based on constraint
            # relationships

            # If the field is already referenced by an existing randset
            # that is not this one, we need to merge the sets
            if isinstance(e.fm, FieldArrayModel):
                for f in e.fm.field_l:
                    self.process_fieldref(f)
            else:
                self.process_fieldref(e.fm)

        super().visit_expr_fieldref(e)

    def process_fieldref(self, fm):
        if fm in self._randset_field_m.keys():
            # There's an existing randset that holds this field
            ex_randset = self._randset_field_m[fm]
            if self._active_randset is None:
                self._active_randset = ex_randset
            elif ex_randset is not self._active_randset:
                for f in self._active_randset.fields():
                    # Relink to the new consolidated randset
                    self._randset_field_m[f] = ex_randset
                    ex_randset.add_field(f)
                # TODO: this might be later
                for c in self._active_randset.constraints():
                    ex_randset.add_constraint(c)

                # Remove the previous randset
                self._randset_s.remove(self._active_randset)
                self._active_randset = ex_randset
        else:
            # No existing randset holds this field
            if self._active_randset is None:
                self._active_randset = RandSet()
                self._randset_s.add(self._active_randset)

            # Need to register this field/randset mapping
            self._active_randset.add_field(fm)
            self._randset_field_m[fm] = self._active_randset

        if fm in self._field_s:
            self._field_s.remove(fm)

    def visit_composite_field(self, f):
        old_used_rand = self._used_rand
        self._used_rand = f.is_used_rand
        super().visit_composite_field(f)
        self._used_rand = old_used_rand

    def visit_scalar_field(self, f):
        if self._pass == 0:
            self._field_s.add(f)

    def visit_enum_field(self, f):
        if self._pass == 0:
            self._field_s.add(f)

    def visit_covergroup(self, cg: CovergroupModel):

        if not self._in_generator or self._pass != 1:
            return

        # TODO: in the case of multiple covergroups, need to have
        # made a higher-level decision about which covergroup to target
        # TODO: determine which coverpoints we're going to enable
        # First try: just select one
        unhit_cp = []
        for cp in cg.coverpoint_l:
            if cp.get_coverage() != 100:
                unhit_cp.append(cp)
        for cp in cg.cross_l:
            if cp.get_coverage() != 100:
                unhit_cp.append(cp)

        if len(unhit_cp) > 0:
            # Only process the target coverpoint
            target_cp = self.randint(0, len(unhit_cp) - 1)
            bin_idx = unhit_cp[target_cp].select_unhit_bin(self)
            bin_expr = unhit_cp[target_cp].get_bin_expr(bin_idx)
            c = ConstraintSoftModel(bin_expr)
            cc = ConstraintBlockModel("coverage", [c])
            cc.accept(self)

    def visit_generator(self, g):
        self._in_generator = True
        super().visit_generator(g)
        self._in_generator = False
Esempio n. 10
0
class RandInfoBuilder(ModelVisitor, RandIF):

    EN_DEBUG = 0

    def __init__(self, rng):
        # TODO: need access to the random state
        super().__init__()
        self._pass = 0
        self._field_m = {}
        self._field_l = []
        self._active_constraint = None
        self._active_randset = None

        # Tracks the randsets added due to
        # dependencies against  ordered fields
        self._active_order_randset_s = set()

        self._randset_m: Dict[RandSet, int] = {}
        self._randset_l = []
        self._randset_field_m: Dict[FieldModel,
                                    RandSet] = {}  # map<field,randset>
        self._constraint_s: List[ConstraintModel] = []
        self._soft_priority = 0
        self._used_rand = True
        self._in_generator = False
        self.active_cp = None
        self._rng = rng

        self._order_m = {}
        self._expr2fm = Expr2FieldVisitor()

    @staticmethod
    def build(field_model_l: [FieldModel],
              constraint_l: [ConstraintModel],
              rng=None) -> RandInfo:
        if rng is None:
            #            rng = Random()
            rng = random

        builder = RandInfoBuilder(rng)

        # First, collect all the fields
        builder._pass = 0
        for fm in field_model_l:
            fm.accept(builder)

        builder._randset_m.clear()
        builder._randset_l.clear()
        builder._randset_field_m.clear()

        # Now, build the randset
        builder._pass = 1
        builder._used_rand = True
        # Visit the field objects passed in, as well
        # as their constraints
        for fm in field_model_l:
            fm.accept(builder)

        # Visit standalone constraints passed to the function
        for c in constraint_l:
            c.accept(builder)

        randset_l = list(filter(lambda e: e is not None, builder._randset_l))

        # Handle ordering constraints.
        # - Collect fields that are members of this randset
        # - Sort in dependency order
        # - Create a randomization ordering list
        if len(builder._order_m.keys()) > 0:
            for rs in randset_l:
                # Create an ordering list of any
                rs_deps = {}
                for i, f in enumerate(rs.fields()):
                    if f in builder._order_m.keys():
                        rs_deps[f] = builder._order_m[f]

                    if len(rs_deps) > 0:
                        rs.rand_order_l = []
                        for fs in list(toposort(rs_deps)):
                            field_l = []
                            for fi in fs:
                                if fi in rs.fields():
                                    field_l.append(fi)
                            if len(field_l) > 0:
                                rs.rand_order_l.append(field_l)

        # It's important to maintain a fixed order for the
        # unconstrained fields, since this affects their
        # randomization.
        nonrand_fields = []
        for f, i in builder._field_m.items():
            nonrand_fields.append((i, f))
        # Sort by add order
        nonrand_fields.sort(key=lambda e: e[0])

        return RandInfo(randset_l, list(map(lambda e: e[1], nonrand_fields)))

    def randint(self, low: int, high: int) -> int:
        return self._rng.randint(low, high)

    def sample(self, s, k):
        return self._rng.sample(s, k)

    def visit_constraint_block(self, c):
        if not c.enabled:
            # Ignore constraint blocks that aren't enabled
            return

        # Null out the randset on entry to a constraint block
        if self._used_rand:
            self._active_randset = None
            self._constraint_s.append(c)
            super().visit_constraint_block(c)
            self._constraint_s.clear()

    def visit_constraint_solve_order(self, c: ConstraintSolveOrderModel):
        if self._pass == 0:
            for b in c.before_l:
                for a in c.after_l:
                    ExpandSolveOrderVisitor(self._order_m).expand(a, b)

    def visit_constraint_stmt_enter(self, c):
        if self._pass == 1 and len(self._constraint_s) == 1:
            self._active_randset = None
            self._active_order_randset_s.clear()
        self._constraint_s.append(c)
        super().visit_constraint_stmt_enter(c)

    def visit_constraint_stmt_leave(self, c):
        c_blk = self._constraint_s[0]
        self._constraint_s.pop()
        if self._pass == 1 and len(self._constraint_s) == 1:
            if self._active_randset is not None:
                self._active_randset.add_constraint(c)
                for s in self._active_order_randset_s:
                    s.add_constraint(c)
            else:
                #                print("TODO: handle no-reference constraint: " + str(c_blk.name))
                pass
        super().visit_constraint_stmt_leave(c)

    def visit_constraint_dynref(self, c):
        # Treat a dynamic constraint as an inline expansion
        # of the constraints in the referenced block
        for c_stmt in c.c.constraint_l:
            c_stmt.accept(self)

    def visit_constraint_expr(self, c):
        if RandInfoBuilder.EN_DEBUG:
            print("--> RandInfoBuilder::visit_constraint_expr")

        super().visit_constraint_expr(c)

        if RandInfoBuilder.EN_DEBUG:
            print("<-- RandInfoBuilder::visit_constraint_expr")

    def visit_constraint_dist_scope(self, s: ConstraintDistScopeModel):
        super().visit_constraint_dist_scope(s)

        # Save information on dist constraints to the
        # appropriate randset
        if self._active_randset is not None:
            f = self._expr2fm.field(s.dist_c.lhs)
            if f in self._active_randset.dist_field_m.keys():
                self._active_randset.dist_field_m[f].append(s)
            else:
                self._active_randset.dist_field_m[f] = [s]

    def visit_constraint_soft(self, c: ConstraintSoftModel):
        if RandInfoBuilder.EN_DEBUG:
            print("--> RandInfoBuilder::visit_constraint_soft")

        # Update the priority of this constraint
        c.priority += self._soft_priority
        self._soft_priority += 1

        super().visit_constraint_soft(c)

        if RandInfoBuilder.EN_DEBUG:
            print("<-- RandInfoBuilder::visit_constraint_soft")

    def visit_expr_array_subscript(self, s: ExprArraySubscriptModel):
        fm = s.getFieldModel()
        if self._pass == 1:
            # During pass 1, build out randsets based on constraint
            # relationships

            # If the field is already referenced by an existing randset
            # that is not this one, we need to merge the sets
            if fm in self._randset_field_m.keys():
                # There's an existing randset that holds this field
                # as a solve target
                ex_randset = self._randset_field_m[fm]
                if self._active_randset is None:
                    self._active_randset = ex_randset
                elif ex_randset is not self._active_randset:
                    for f in self._active_randset.fields():
                        # Relink to the new consolidated randset
                        self._randset_field_m[f] = ex_randset
                        ex_randset.add_field(f)
                    # TODO: this might be later
                    for c in self._active_randset.constraints():
                        ex_randset.add_constraint(c)

                    # Remove the previous randset
                    idx = self._randset_m[self._active_randset]
                    self._randset_m.pop(idx)
                    self._randset_l[idx] = None
                    self._active_randset = ex_randset
            else:
                # No existing randset holds this field as a
                # solve target
                if self._active_randset is None:
                    self._active_randset = RandSet()
                    idx = len(self._randset_l)
                    self._randset_m[self._active_randset] = idx
                    self._randset_l.append(self._active_randset)

                # Need to register this field/randset mapping
                self._active_randset.add_field(fm)
                self._randset_field_m[fm] = self._active_randset

            if fm in self._field_m.keys():
                self._field_m.pop(fm)

    def visit_expr_fieldref(self, e):
        # If the field is already referenced by an existing randset
        # that is not this one, we need to merge the sets
        fm = self._expr2fm.field(e)

        if self._pass == 0:
            fm.accept(self)
        elif self._pass == 1:
            # During pass 1, build out randsets based on constraint
            # relationships

            if isinstance(fm, FieldArrayModel):
                # Note: this is important for unique constraints on an
                # entire (possibly variable-size) scalar array
                for f in e.fm.field_l:
                    self.process_fieldref(f)
            else:
                self.process_fieldref(fm)

#        super().visit_expr_fieldref(e)

    def visit_expr_indexed_dynref(self, e):
        fm: FieldCompositeModel = Expr2FieldVisitor().field(e.root, True)
        c = fm.constraint_dynamic_model_l[e.idx]

        # Treat a dynamic constraint as an inline expansion
        # of the constraints in the referenced block
        for c_stmt in c.constraint_l:
            c_stmt.accept(self)

    def visit_expr_indexed_fieldref(self, e):
        self.process_fieldref(e.get_target())

    def process_fieldref(self, fm):
        if RandInfoBuilder.EN_DEBUG > 0:
            print("--> RandInfoBuilder::process_fieldref %s" % fm.fullname)
        if fm in self._randset_field_m.keys():
            # There's an existing randset that holds this field
            ex_randset = self._randset_field_m[fm]
            if self._active_randset is None:
                self._active_randset = ex_randset
            elif ex_randset is not self._active_randset:
                if RandInfoBuilder.EN_DEBUG > 0:
                    print("RandInfoBuilder: combine two randsets")

                # This field isn't being ordered, so go ahead and
                for f in self._active_randset.fields():
                    # Relink to the new consolidated randset
                    self._randset_field_m[f] = ex_randset
                    ex_randset.add_field(f)

                for c in self._active_randset.constraints():
                    ex_randset.add_constraint(c)

                for c in self._active_randset.soft_constraints():
                    ex_randset.add_constraint(c)

                # Remove the previous randset
                idx = self._randset_m[self._active_randset]
                self._randset_m.pop(self._active_randset)
                self._randset_l[idx] = None
                self._active_randset = ex_randset
            else:
                # Field is handled by a randset that is the active one
                pass
        else:
            if RandInfoBuilder.EN_DEBUG > 0:
                print("RandInfoBuilder: create new randset")

            # No existing randset holds this field
            if self._active_randset is None:
                self._active_randset = RandSet()
                idx = len(self._randset_l)
                self._randset_m[self._active_randset] = idx
                self._randset_l.append(self._active_randset)

            # Need to register this field/randset mapping
            self._active_randset.add_field(fm)
            self._randset_field_m[fm] = self._active_randset

        if fm in self._field_m.keys():
            self._field_m.pop(fm)

        if RandInfoBuilder.EN_DEBUG > 0:
            print("<-- RandInfoBuilder::process_fieldref %s" % fm.fullname)

    def visit_composite_field(self, f):
        old_used_rand = self._used_rand
        self._used_rand = f.is_used_rand
        super().visit_composite_field(f)
        self._used_rand = old_used_rand

    def visit_scalar_field(self, f):
        if self._pass == 0:
            if not f in self._field_m.keys():
                idx = len(self._field_l)
                self._field_m[f] = idx

    def visit_enum_field(self, f):
        if self._pass == 0:
            if not f in self._field_m.keys():
                idx = len(self._field_l)
                self._field_m[f] = idx

    def visit_covergroup(self, cg: CovergroupModel):

        if not self._in_generator or self._pass != 1:
            return

        # TODO: in the case of multiple covergroups, need to have
        # made a higher-level decision about which covergroup to target
        # TODO: determine which coverpoints we're going to enable
        # First try: just select one
        unhit_cp = []
        for cp in cg.coverpoint_l:
            if cp.get_coverage() != 100:
                unhit_cp.append(cp)
        for cp in cg.cross_l:
            if cp.get_coverage() != 100:
                unhit_cp.append(cp)

        if len(unhit_cp) > 0:
            # Only process the target coverpoint
            target_cp = self.randint(0, len(unhit_cp) - 1)
            bin_idx = unhit_cp[target_cp].select_unhit_bin(self)
            bin_expr = unhit_cp[target_cp].get_bin_expr(bin_idx)
            c = ConstraintSoftModel(bin_expr)
            cc = ConstraintBlockModel("coverage", [c])
            cc.accept(self)

    def visit_generator(self, g):
        self._in_generator = True
        super().visit_generator(g)
        self._in_generator = False