Repository: phanrahan/magma

Issue:
[Types] Add fill (with 0 or 1) function for all types

Comments:

Patch:
diff --git a/magma/__init__.py b/magma/__init__.py
index 22ea0b7d..275bbb5a 100644
--- a/magma/__init__.py
+++ b/magma/__init__.py
@@ -119,6 +119,7 @@ from magma.stubify import (
 from magma.compile import MagmaCompileException
 from magma.linking import link_module, link_default_module, clear_link_info
 import magma.math
+from magma.value_utils import fill
 
 ################################################################################
 # BEGIN ALIASES
diff --git a/magma/conversions.py b/magma/conversions.py
index 21128210..0f6d479b 100644
--- a/magma/conversions.py
+++ b/magma/conversions.py
@@ -60,7 +60,7 @@ def can_convert_to_bit_type(value):
     return issubclass(magma_type(value), (Digital, Array, Tuple))
 
 
-def convertbit(value, totype):
+def convertbit(value, totype, name=None):
     # NOTE: We don't do `isinstance` here because we want an upcast to cause a
     # conversion
     value = magma_value(value)
@@ -90,41 +90,44 @@ def convertbit(value, totype):
         # invariant
         return totype(1) if value else totype(0)
 
+    if name is None:
+        name = value.name
+
     if value.is_input():
-        b = In(totype)(name=value.name)
+        b = In(totype)(name=name)
     elif value.is_output():
-        b = Out(totype)(name=value.name)
+        b = Out(totype)(name=name)
     else:
-        b = totype()
+        b = totype(name=name)
     b._wire = value._wire
     return b
 
 
-def bit(value):
-    return convertbit(value, Bit)
+def bit(value, name=None):
+    return convertbit(value, Bit, name=name)
 
 
-def clock(value):
-    return convertbit(value, Clock)
+def clock(value, name=None):
+    return convertbit(value, Clock, name=name)
 
 
-def reset(value):
-    return convertbit(value, Reset)
+def reset(value, name=None):
+    return convertbit(value, Reset, name=name)
 
 
-def asyncreset(value):
-    return convertbit(value, AsyncReset)
+def asyncreset(value, name=None):
+    return convertbit(value, AsyncReset, name=name)
 
 
-def asyncresetn(value):
-    return convertbit(value, AsyncResetN)
+def asyncresetn(value, name=None):
+    return convertbit(value, AsyncResetN, name=name)
 
 
-def enable(value):
-    return convertbit(value, Enable)
+def enable(value, name=None):
+    return convertbit(value, Enable, name=name)
 
 
-def convertbits(value, n, totype, checkbit):
+def convertbits(value, n, totype, checkbit, name=None):
     # NOTE: We don't do `isinstance` here because we want an upcast to cause a
     # conversion
     if type(value) is totype:
@@ -200,30 +203,34 @@ def convertbits(value, n, totype, checkbit):
             Direction.InOut: InOut
         }[T.direction](Bit)
 
-    value = totype[len(Ts), T](ts)
+    value = totype[len(Ts), T](ts, name=name)
     if n is not None and len(value) < n:
+        # TODO(leonardt): The extended value isn't named, but perhaps we'd like to move
+        # to an explicit convert + extend rather than doing them in a single
+        # operation? If so, then we could provide the same name interface for
+        # the extension operators.
         value = value.ext_to(n)
     return value
 
 
-def array(value, n=None):
-    return convertbits(value, n, Array, False)
+def array(value, n=None, name=None):
+    return convertbits(value, n, Array, False, name=name)
 
 
-def bits(value, n=None):
-    return convertbits(value, n, Bits, True)
+def bits(value, n=None, name=None):
+    return convertbits(value, n, Bits, True, name=name)
 
 
-def uint(value, n=None):
-    return convertbits(value, n, UInt, True)
+def uint(value, n=None, name=None):
+    return convertbits(value, n, UInt, True, name=name)
 
 
-def sint(value, n=None):
-    return convertbits(value, n, SInt, True)
+def sint(value, n=None, name=None):
+    return convertbits(value, n, SInt, True, name=name)
 
 
-def bfloat(value, n=None):
-    return convertbits(value, n, BFloat, True)
+def bfloat(value, n=None, name=None):
+    return convertbits(value, n, BFloat, True, name=name)
 
 
 def concat(*args):
diff --git a/magma/value_utils.py b/magma/value_utils.py
index 9c745c17..4941fa34 100644
--- a/magma/value_utils.py
+++ b/magma/value_utils.py
@@ -103,3 +103,20 @@ def _make_selector_impl(value, child):
 
 def make_selector(value):
     return _make_selector_impl(value, None)
+
+
+class _FillVisitor(ValueVisitor):
+    def __init__(self, fill_value: bool):
+        self._fill_value = fill_value
+
+    def visit_Digital(self, value):
+        value @= self._fill_value
+
+    def visit_Bits(self, value):
+        size = len(value)
+        fill_value = 0 if not self._fill_value else (1 << size) - 1
+        value @= fill_value
+
+
+def fill(value, fill_value: bool):
+    _FillVisitor(fill_value).visit(value)


Test patch:
diff --git a/tests/test_conversion.py b/tests/test_conversion.py
index 145525b1..323729e8 100644
--- a/tests/test_conversion.py
+++ b/tests/test_conversion.py
@@ -84,3 +84,19 @@ def test_bits_upcast(T):
 def test_bit_upcast():
     x = m.Enable()
     assert type(m.bit(x)) is m.Bit
+
+
+@pytest.mark.parametrize(
+    'T,conversion,expected_T',
+    [
+        (m.Bit, m.clock, m.Clock),
+        (m.Bits[4], m.uint, m.UInt),
+        (m.UInt[4], m.bits, m.Bits),
+        (m.Reset, m.bit, m.Bit)
+    ]
+)
+def test_name(T, conversion, expected_T):
+    x = T(name="x")
+    y = conversion(x, name="y")
+    assert str(y) == 'y'
+    assert isinstance(y, expected_T)
diff --git a/tests/test_value_utils.py b/tests/test_value_utils.py
index bbaeae78..7cad99c2 100644
--- a/tests/test_value_utils.py
+++ b/tests/test_value_utils.py
@@ -1,6 +1,13 @@
+import pytest
+
 import magma as m
-from magma.value_utils import (ValueVisitor, ArraySelector, TupleSelector,
-                               make_selector)
+from magma.value_utils import (
+    ValueVisitor,
+    ArraySelector,
+    TupleSelector,
+    make_selector,
+    fill,
+)
 
 
 class _Prod(m.Product):
@@ -67,3 +74,26 @@ def test_selector():
 
     assert str(selector) == "[0].x[0]"
     assert selector.select(_Foo.I) is _Foo.I[0].x[0]
+
+
+@pytest.mark.parametrize("fill_value", (True, False))
+def test_fill(fill_value):
+    S = m.AnonProduct[{"x": m.Bits[8], "y": m.Bit}]
+    T = m.AnonProduct[{"s": S, "u": m.Array[4, m.Bits[6]]}]
+
+    t = T()
+    fill(t, fill_value)
+
+    value = t.value()
+    assert value is not None
+
+    assert value.s.const()
+    assert value.s.x.const()
+    assert value.s.y.const()
+    assert int(value.s.x) == (0 if not fill_value else 255)
+    assert int(value.s.y) == (0 if not fill_value else 1)
+
+    assert value.u.const()
+    for u_i in value.u:
+        assert u_i.const()
+        assert int(u_i) == (0 if not fill_value else 63)