add Value.considered_bool() function
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Thu, 30 Sep 2021 12:50:18 +0000 (13:50 +0100)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Thu, 30 Sep 2021 12:50:18 +0000 (13:50 +0100)
testing len(Value) == 1 is one of the rare places that makes an assumption
between Type 1 (Ast) low-level nmigen language constructs and
between Type 2 (dsl.Module) high-level nmigen language constructs.

a "Value.considered_bool()" function de-couples that assumption and allows
for a proper separation between Type 1 and Type 2 nmigen language
constructs, providing a clean design where Liskov Substitution Principle
may successfully be applied at the Type I (ast) level

nmigen/compat/fhdl/structure.py
nmigen/hdl/ast.py
nmigen/hdl/dsl.py

index 68ddc497db424b28d40c1f9530af679575438202..5a29b0061fd52ffa7c507af7e2dd5537fcb0192e 100644 (file)
@@ -123,14 +123,14 @@ class If(ast._InternalSwitch):
     @deprecated("instead of `If(cond, ...)`, use `with m.If(cond): ...`")
     def __init__(self, cond, *stmts):
         cond = Value.cast(cond)
-        if len(cond) != 1:
+        if not cond.considered_bool():
             cond = cond.bool()
         super().__init__(cond, {("1",): ast.Statement.cast(stmts)})
 
     @deprecated("instead of `.Elif(cond, ...)`, use `with m.Elif(cond): ...`")
     def Elif(self, cond, *stmts):
         cond = Value.cast(cond)
-        if len(cond) != 1:
+        if not cond.considered_bool():
             cond = cond.bool()
         self.cases = OrderedDict((("-" + k,), v) for (k,), v in self.cases.items())
         self.cases[("1" + "-" * len(self.test),)] = ast.Statement.cast(stmts)
index 5045844e605474b13daa606aaba3072d38e1d673..5f3f5bc98cf57619340a6070053fbbaa39fabeb9 100644 (file)
@@ -175,6 +175,9 @@ class Value(metaclass=ABCMeta):
     def __Assign__(self, rhs, *, src_loc_at=0):
         return _InternalAssign(self, rhs, src_loc_at=src_loc_at)
 
+    def considered_bool(self):
+        return len(self) == 1
+
     def __bool__(self):
         raise TypeError("Attempted to convert nMigen value to Python boolean")
 
@@ -767,7 +770,8 @@ def _InternalMux(sel, val1, val0):
         Output ``Value``. If ``sel`` is asserted, the Mux returns ``val1``, else ``val0``.
     """
     sel = Value.cast(sel)
-    if len(sel) != 1:
+    # test whether the value is considered boolean, and if not, convert it
+    if not sel.considered_bool():
         sel = sel.bool()
     return Operator("m", [sel, val1, val0])
 
index c4473692b9c1d3c37a9fd27939eea64999dd1c26..337e176338b73d20298d9aed6c251fb77c98c495 100644 (file)
@@ -433,9 +433,13 @@ class Module(_ModuleBuilderRoot, Elaboratable):
             tests, cases = [], OrderedDict()
             for if_test, if_case in zip(if_tests + [None], if_bodies):
                 if if_test is not None:
-                    if_test = Value.cast(if_test)
-                    if len(if_test) != 1:
-                        if_test = if_test.bool()
+                    # check if the if_test is not considered boolean, and only
+                    # append a converted version if it is not. this defers the
+                    # Value.cast (which would lose type if done mandatorially).
+                    # ast._InternalSwitch does a (second) Value.cast anyway
+                    _if_test = Value.cast(if_test)
+                    if not _if_test.considered_bool():
+                        if_test = _if_test.bool()
                     tests.append(if_test)
 
                 if if_test is not None: