From 1df76098c2507405a658621e5292803f0b4a3d46 Mon Sep 17 00:00:00 2001 From: Matthew Ballance Date: Thu, 11 Apr 2024 17:55:31 -0700 Subject: [PATCH] 0.8.9 - (#205) - Ensure that recursive references between objects don't cause recursion Signed-off-by: Matthew Ballance --- doc/Changelog.md | 3 ++ etc/ivpm.info | 2 +- src/vsc/model/field_array_model.py | 18 +++---- src/vsc/model/field_composite_model.py | 27 +++++++--- src/vsc/model/field_model.py | 8 +-- src/vsc/model/field_scalar_model.py | 6 +-- src/vsc/model/model_visitor.py | 6 ++- src/vsc/model/randomizer.py | 11 ++-- .../visitors/clear_soft_priority_visitor.py | 8 +++ src/vsc/visitors/model_pretty_printer.py | 2 + .../visitors/ref_fields_postrand_visitor.py | 3 +- ve/unit/test_list_object.py | 54 +++++++++++++++++++ 12 files changed, 116 insertions(+), 32 deletions(-) diff --git a/doc/Changelog.md b/doc/Changelog.md index abff293..d286a81 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -1,4 +1,7 @@ +## 0.8.9 +- (#205) - Ensure that recursive references between objects don't cause recursion + ## 0.8.8 - Ensure covergroup type names are properly reflected in saved coverage data. - Test suite updates to adapt to newer Python versions diff --git a/etc/ivpm.info b/etc/ivpm.info index 9a280f9..b988e33 100644 --- a/etc/ivpm.info +++ b/etc/ivpm.info @@ -1,4 +1,4 @@ name=pyvsc -version=0.8.8 +version=0.8.9 diff --git a/src/vsc/model/field_array_model.py b/src/vsc/model/field_array_model.py index 3388f83..9266264 100644 --- a/src/vsc/model/field_array_model.py +++ b/src/vsc/model/field_array_model.py @@ -80,17 +80,17 @@ def name_elems(self): for i,f in enumerate(self.field_l): f.name = self.name + "[" + str(i) + "]" - def pre_randomize(self): + def pre_randomize(self, visited): # Set the size field for arrays that don't # have a random size if self.is_rand_sz: self.size.set_used_rand(True) else: self._set_size(len(self.field_l)) - FieldCompositeModel.pre_randomize(self) + FieldCompositeModel.pre_randomize(self, visited) - def post_randomize(self): - FieldCompositeModel.post_randomize(self) + def post_randomize(self, visited): + FieldCompositeModel.post_randomize(self, visited) self.sum_expr = None self.sum_expr_btor = None @@ -116,13 +116,9 @@ def build(self, builder): self._set_size(len(self.field_l)) super().build(builder) -# def set_used_rand(self, is_rand, level=0): -# if self.is_rand_sz: -# self.size.set_used_rand(is_rand) -# FieldCompositeModel.set_used_rand(self, is_rand, level=level) - def set_used_rand(self, is_rand, level=0): - super().set_used_rand(is_rand, level) - self.size.set_used_rand(is_rand, level+1) + def set_used_rand(self, is_rand, level=0, in_set=None): + super().set_used_rand(is_rand, level, in_set) + self.size.set_used_rand(is_rand, level+1, in_set) def get_sum_expr(self): if self.sum_expr is None: diff --git a/src/vsc/model/field_composite_model.py b/src/vsc/model/field_composite_model.py index 7dad5f5..3fbd8f5 100644 --- a/src/vsc/model/field_composite_model.py +++ b/src/vsc/model/field_composite_model.py @@ -57,12 +57,17 @@ def is_declared_rand(self, v): self.__is_declared_rand = bool(v) self.rand_mode = bool(v) - def set_used_rand(self, is_rand, level=0): + def set_used_rand(self, is_rand, level=0, in_set=None): self.is_used_rand = (is_rand and ((self.is_declared_rand and self.rand_mode) or level==0)) + + if in_set is None: + in_set = set() for f in self.field_l: - f.set_used_rand(self.is_used_rand, level+1) + if f not in in_set: + in_set.add(f) + f.set_used_rand(self.is_used_rand, level+1, in_set) def build(self, builder): # First, build the fields @@ -128,7 +133,7 @@ def get_fields(self, field_l): else: field_l.append(f) - def pre_randomize(self): + def pre_randomize(self, visited): """Called during the randomization process to propagate `pre_randomize` event""" # Perform a phase callback if available. Note, @@ -136,19 +141,25 @@ def pre_randomize(self): # fields that are actually being used as random if self.is_used_rand and self.rand_if is not None: self.rand_if.do_pre_randomize() - + + visited.append(self) for f in self.field_l: - f.pre_randomize() + if f not in visited: + f.pre_randomize(visited) + visited.remove(self) - def post_randomize(self): + def post_randomize(self, visited): """Called during the randomization process to propagate `post_randomize` event""" # Perform a phase callback if available if self.is_used_rand and self.rand_if is not None: self.rand_if.do_post_randomize() - + + visited.append(self) for f in self.field_l: - f.post_randomize() + if f not in visited: + f.post_randomize(visited) + visited.remove(self) def accept(self, v): v.visit_composite_field(self) diff --git a/src/vsc/model/field_model.py b/src/vsc/model/field_model.py index e82046c..94dca4d 100644 --- a/src/vsc/model/field_model.py +++ b/src/vsc/model/field_model.py @@ -39,10 +39,10 @@ def __init__(self, name): def build(self, builder): raise Exception("FieldModel::build unimplemented for type " + str(type(self))) - def pre_randomize(self): + def pre_randomize(self, visited): pass - def post_randomize(self): + def post_randomize(self, visited): pass def get_val(self) -> Value: @@ -53,10 +53,12 @@ def set_val(self, v : Value): @property def fullname(self): + visited = [] ret = self.name p = self.parent - while p is not None: + while p is not None and p not in visited: + visited.append(p) if p.name is not None: ret = p.name + "." + ret p = p.parent diff --git a/src/vsc/model/field_scalar_model.py b/src/vsc/model/field_scalar_model.py index 0e0c423..4ff9b29 100644 --- a/src/vsc/model/field_scalar_model.py +++ b/src/vsc/model/field_scalar_model.py @@ -45,7 +45,7 @@ def __init__(self, self.var = None self.val = ValueScalar(0) - def set_used_rand(self, is_rand, level=0): + def set_used_rand(self, is_rand, level=0, in_set=None): # Field is considered rand when # - It is a root field, on which 'randomize' is called # - It is declared as random, and 'rand_mode' is true @@ -85,7 +85,7 @@ def __str__(self): def get_constraints(self, constraint_l): pass - def pre_randomize(self): + def pre_randomize(self, visited): if self.rand_if is not None: self.rand_if.do_pre_randomize() @@ -95,7 +95,7 @@ def set_val(self, val : Value): def get_val(self): return self.val - def post_randomize(self): + def post_randomize(self, visited): if self.var is not None: # Convert to a Python base-10 integer (unsigned) val = int(self.var.assignment, 2) diff --git a/src/vsc/model/model_visitor.py b/src/vsc/model/model_visitor.py index 3e597bd..345be79 100644 --- a/src/vsc/model/model_visitor.py +++ b/src/vsc/model/model_visitor.py @@ -53,6 +53,7 @@ class ModelVisitor(object): def __init__(self): + self.field_visited = [] pass def visit_rand_obj(self, r): @@ -60,8 +61,11 @@ def visit_rand_obj(self, r): def visit_composite_field(self, f : FieldCompositeModel): # Visit fields + self.field_visited.append(f) for fi in f.field_l: - fi.accept(self) + if fi not in self.field_visited: + fi.accept(self) + self.field_visited.remove(f) # Visit constraints for c in f.constraint_model_l: diff --git a/src/vsc/model/randomizer.py b/src/vsc/model/randomizer.py index db15b38..cbddb5a 100644 --- a/src/vsc/model/randomizer.py +++ b/src/vsc/model/randomizer.py @@ -274,7 +274,8 @@ def randomize(self, ri : RandInfo, bound_m : Dict[FieldModel,VariableBoundModel] while x < rs_i: rs = ri.randsets()[x] for f in rs.all_fields(): - f.post_randomize() + visited = [] + f.post_randomize(visited) f.set_used_rand(False, 0) f.dispose() # Get rid of the solver var, since we're done with it f.accept(reset_v) @@ -537,8 +538,9 @@ def do_randomize( print(" " + ModelPrettyPrinter.print(fm)) # First, invoke pre_randomize on all elements + visited = [] for fm in field_model_l: - fm.pre_randomize() + fm.pre_randomize(visited) if constraint_l is None: constraint_l = [] @@ -601,9 +603,10 @@ def do_randomize( randomize_done(srcinfo, solve_info) for fm in field_model_l: ConstraintOverrideRollbackVisitor.rollback(fm) - + + visited = [] for fm in field_model_l: - fm.post_randomize() + fm.post_randomize(visited) # Process constraints to identify variable/constraint sets diff --git a/src/vsc/visitors/clear_soft_priority_visitor.py b/src/vsc/visitors/clear_soft_priority_visitor.py index 5f110e4..191e2b1 100644 --- a/src/vsc/visitors/clear_soft_priority_visitor.py +++ b/src/vsc/visitors/clear_soft_priority_visitor.py @@ -4,6 +4,7 @@ @author: mballance ''' from vsc.model.constraint_soft_model import ConstraintSoftModel +from vsc.model.field_composite_model import FieldCompositeModel from vsc.model.model_visitor import ModelVisitor @@ -11,10 +12,17 @@ class ClearSoftPriorityVisitor(ModelVisitor): def __init__(self): super().__init__() + self.visited = set() def clear(self, e): + self.visited.clear() e.accept(self) def visit_constraint_soft(self, c:ConstraintSoftModel): c.priority = 0 + + def visit_composite_field(self, f: FieldCompositeModel): + if f not in self.visited: + self.visited.add(f) + super().visit_composite_field(f) \ No newline at end of file diff --git a/src/vsc/visitors/model_pretty_printer.py b/src/vsc/visitors/model_pretty_printer.py index 2b134b8..6c303da 100644 --- a/src/vsc/visitors/model_pretty_printer.py +++ b/src/vsc/visitors/model_pretty_printer.py @@ -33,6 +33,7 @@ class ModelPrettyPrinter(ModelVisitor): def __init__(self): + super().__init__() self.out = StringIO() self.ind = "" self.print_values = False @@ -69,6 +70,7 @@ def visit_composite_field(self, f : FieldCompositeModel): self.writeln(name + " {") self.inc_indent() super().visit_composite_field(f) + self.dec_indent() self.writeln("}") diff --git a/src/vsc/visitors/ref_fields_postrand_visitor.py b/src/vsc/visitors/ref_fields_postrand_visitor.py index 6a7635b..34c2337 100644 --- a/src/vsc/visitors/ref_fields_postrand_visitor.py +++ b/src/vsc/visitors/ref_fields_postrand_visitor.py @@ -11,9 +11,10 @@ class RefFieldsPostRandVisitor(ModelVisitor): def __init__(self): super().__init__() + self.in_set = set() def visit_expr_fieldref(self, e): # Capture solving values and mark fields not-used-rand e.fm.post_randomize() - e.fm.set_used_rand(False) + e.fm.set_used_rand(False, in_set=self.in_set) \ No newline at end of file diff --git a/ve/unit/test_list_object.py b/ve/unit/test_list_object.py index df090cc..28d7fa3 100644 --- a/ve/unit/test_list_object.py +++ b/ve/unit/test_list_object.py @@ -154,3 +154,57 @@ def __init__(self): else: self.assertGreater(it.a, it.b) + def test_heterogenous_content(self): + @vsc.randobj + class base_constraints_class(object): + pass + + + @vsc.randobj + class base_rand_class(object): + def __init__(self, name): + self.name = name + self.constraints = vsc.rand_list_t(base_constraints_class(), 0) + + def add_constraints(self, c): + self.constraints.append(c) + + + @vsc.randobj + class user_constraints_class(base_constraints_class): + def __init__(self, ptr): + self.ptr = vsc.rand_attr(ptr) +# self.ptr = ptr +# self.ptr = vsc.rand_attr(user_rand_class("user_1")) + pass + + @vsc.constraint + def my_ptr_c(self): + self.ptr.a == 8 + self.ptr.b == 1999 + + + @vsc.randobj + class user_rand_class(base_rand_class): + def __init__(self, name): + super().__init__(name) + self.a = vsc.rand_bit_t(16) + self.b = vsc.rand_bit_t(16) + + + @vsc.constraint + def ab_c(self): + self.a in vsc.rangelist(vsc.rng(1,1000)) + self.b in vsc.rangelist(vsc.rng(1000,2000)) + pass + + def print_fields(self): + print(f"{self.name}: a={self.a}, b={self.b}") + + + usr1 = user_rand_class("user_1") + c = user_constraints_class(usr1) + usr1.add_constraints(c) + usr1.randomize(debug=0, solve_fail_debug=0) + print("--------------------\n") + usr1.print_fields() \ No newline at end of file