Repository: fvutils/pyvsc (language: python)

Issue:
Setting constraint_mode to False retains the previous range/bounds of the constraint
I was modifying the example in the ["Constraint Mode" section](https://fvutils.github.io/pyvsc/constraints.html#constraint-mode) and noticed that disabling the constraint via constraint_mode wasn't working. I tried both setting it to a constant (a == 0) and constraining it between two constants (a >= 0, a < 100). I assume this is b/c the calculated bounds is being used on a now unconstrained variable?

There are some "with item.randomize():" lines in the examples that have no following statement. Maybe I can go through some more examples and clean those up along the way and submit something.

Using git pyvsc: commit 743b6d3c3376dfa15feab91bfe454f5ee5a53eac tag: v0.8.2

Constraint Mode example but constraining a to a single constant value:
```python
#!/usr/bin/env python3

import vsc

@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
       self.a == 42

item = my_item()
debug = 0

# Always generate valid values
for i in range(10):
   item.randomize(debug=debug)
   print(True, item.a, item.b)

item.valid_ab_c.constraint_mode(False)

# Allow invalid values
for i in range(10):
   item.randomize(debug=debug)
   print(False, item.a, item.b)
```


Discussion / hints:
I found a spot in the ModelVisitor where an enabled check works to skip disabled constraints and makes the bounds calculation correct. I'm not sure what else that might do though. Another option would be to remove the constraint entirely, but it should stick around in case it's enabled again.

I tried disabling a dynamic constraint, but I think that might be a nop. Can you disable a dynamic constraint? Probably doesn't make sense to given that you need to specify them in the with block.

Fix patch:
diff --git a/src/vsc/visitors/variable_bound_visitor.py b/src/vsc/visitors/variable_bound_visitor.py
index ffae6c8..37ba943 100644
--- a/src/vsc/visitors/variable_bound_visitor.py
+++ b/src/vsc/visitors/variable_bound_visitor.py
@@ -7,6 +7,7 @@
 from typing import Dict, List
 
 from vsc.model.bin_expr_type import BinExprType
+from vsc.model.constraint_block_model import ConstraintBlockModel
 from vsc.model.constraint_foreach_model import ConstraintForeachModel
 from vsc.model.constraint_if_else_model import ConstraintIfElseModel
 from vsc.model.constraint_implies_model import ConstraintImpliesModel
@@ -102,7 +103,9 @@ def process(self,
             b.update()
 #            print(b.toString())
             
-        
+    def visit_constraint_block(self, c:ConstraintBlockModel):
+        if c.enabled:
+            super().visit_constraint_block(c)
 
     def visit_constraint_if_else(self, c:ConstraintIfElseModel):
         self.depth += 1


Test patch:
diff --git a/ve/unit/test_constraint_mode.py b/ve/unit/test_constraint_mode.py
index 046335f..3c0be8f 100644
--- a/ve/unit/test_constraint_mode.py
+++ b/ve/unit/test_constraint_mode.py
@@ -8,16 +8,16 @@
 from vsc_test_case import VscTestCase
 
 class TestConstraintMode(VscTestCase):
-    
+
     def test_smoke(self):
-        
+
         @vsc.randobj
-        class my_item_c(object):
+        class my_item_1_c(object):
             
             def __init__(self):
                 self.a = vsc.rand_bit_t(8)
                 self.b = vsc.rand_bit_t(8)
-                
+
             @vsc.constraint
             def ab_eq_c(self):
                 self.a == self.b
@@ -25,10 +25,10 @@ def ab_eq_c(self):
             @vsc.constraint
             def ab_ne_c(self):
                 self.a != self.b
-                
-        it = my_item_c()
 
-        for i in range(16):        
+        it = my_item_1_c()
+
+        for i in range(16):
             it.ab_eq_c.constraint_mode(True)
             it.ab_ne_c.constraint_mode(False)
             it.randomize()
@@ -38,5 +38,29 @@ def ab_ne_c(self):
             it.ab_ne_c.constraint_mode(True)
             it.randomize()
             self.assertNotEqual(it.a, it.b)
-                
-                
\ No newline at end of file
+
+    def test_bounds_smoke(self):
+
+        # Test that bounds are re-calculated correctly when disabling constraints.
+        # a_zero_c would set bounds to zero, but disabling that constraint zero
+        # should be *nigh* impossible for 1024 bits.
+        # See: https://github.com/fvutils/pyvsc/issues/173
+
+        @vsc.randobj
+        class my_item_0_c(object):
+
+            def __init__(self):
+                self.a = vsc.rand_bit_t(1024)
+
+            @vsc.constraint
+            def a_zero_c(self):
+                self.a == 0
+
+        it = my_item_0_c()
+
+        it.a_zero_c.constraint_mode(False)
+
+        for i in range(16):
+            it.randomize()
+            self.assertNotEqual(it.a, 0)
+