Repository: phanrahan/magma

Issue:
Unify math.py:* and bitutils.py:*
- [x] fix the bitutils implementation to match the math one more closely
- [x] deprecate (warning) math one
- [ ] kill math one once we upgrade to magma3.0

Discussion:

Patch:
diff --git a/docs/cheat_sheet.md b/docs/cheat_sheet.md
index a925329c..cfd6053a 100644
--- a/docs/cheat_sheet.md
+++ b/docs/cheat_sheet.md
@@ -65,12 +65,6 @@ inout respectively.
 * `m.sint(value, n=None)`: convert `value` to an `SInt` (same rules as
   `m.bits`, except will not convert `m.UInt` to `m.SInt`)
 
-# Math Helpers
-Contained in the module `magma.math`
-* `m.math.log2_ceil(x: int) -> int`: `log2(x)` rounded up
-* `m.math.log2_floor(x: int) -> int`: `log2(x)` rounded down
-* `m.math.is_pow2(x: int) -> bool`: `True` if `x` is a power of 2
-
 # Registers
 Retain state until updated:
 ```python
diff --git a/magma/backend/blif.py b/magma/backend/blif.py
index c40f1f35..24ad0e66 100644
--- a/magma/backend/blif.py
+++ b/magma/backend/blif.py
@@ -1,7 +1,7 @@
 import types
 from collections import OrderedDict
 from collections.abc import Sequence
-from ..bitutils import seq2int, fun2seq
+from ..bitutils import seq2int
 from ..bit import Bit, Digital
 from ..array import Array
 from ..compiler import make_compiler
diff --git a/magma/bitutils.py b/magma/bitutils.py
index a1ef0a31..e1da67f8 100644
--- a/magma/bitutils.py
+++ b/magma/bitutils.py
@@ -1,136 +1,36 @@
-# https://wiki.python.org/moin/BitManipulation
-# https://code.google.com/p/python-bitstring/
-from types import FunctionType
-from collections.abc import Sequence
-import inspect
-from .compatibility import StringTypes
+import functools
+import math
+from typing import List, Optional, Sequence, Tuple
 
-__all__  = ['seq2int', 'int2seq', 'ints2seq', 'fun2seq', 'lutinit']
-__all__ += ['int2uint', 'clz', 'pow2', 'log2', 'clog2', 'rol', 'ror']
-__all__ += ['clog2safe']
+import hwtypes
 
-#
-# seq to int
-#
-def seq2int(l):
-    n = len(l)
 
-    i = 0
-    for j in range(n):
-        if l[j]:
-            i |= 1 << j
-    return i
+__all__  = ['seq2int', 'int2seq']
+__all__ += ['clog2', 'clog2safe']
 
-#
-# int to seq
-#
-def int2seq(i, n=0):
-    if isinstance(i, StringTypes):
-        i = ord(i)
 
-    # find minimum number of bits needed for i
-    if n == 0:
-        j = i
-        while j:
-            n += 1
-            j >>= 1
+# TODO(rsetaluri): We should deprecate this function and invoke the hwtypes
+# versions directly at the call sites.
+def seq2int(l: Sequence) -> int:
+    return int(hwtypes.BitVector[len(l)](l))
 
-    return [1 if i & (1 << j) else 0 for j in range(n)]
 
-#
-# ints to seq
-#
-def ints2seq(l, n):
-    return [int2seq(i,n) for i in l]
+# TODO(rsetaluri): We should deprecate this function and invoke the hwtypes
+# versions directly at the call sites.
+def int2seq(i: int, n: Optional[int] = None) -> List[int]:
+    bit_length = i.bit_length()
+    if n is None:
+        n = bit_length
+    elif n < bit_length:
+        raise ValueError("Insufficient number of bits")
+    return hwtypes.BitVector[n](i).bits()
 
-#
-# fun to seq
-#
-def fun2seq(f, n=None):
-    if not n:
-        logn = len(inspect.getargspec(f).args)
-        n = 1 << logn
-    else:
-        logn = log2(n)
-    
-    l = []
-    for i in range(n):
-         arg = int2seq(i, logn)
-         l.append(1 if f(*arg) else 0)
-    return l
 
-def lutinit(init, n=None):
-    if isinstance(init, FunctionType):
-        init = fun2seq(init, n)
+def clog2(x: int) -> int:
+    return math.ceil(math.log(x, 2))
 
-    if isinstance(init, Sequence):
-        nlut = len(init)
-        if n != nlut:
-            assert n % nlut == 0
-            init = (n//nlut) * init
-        init = seq2int(init)
 
-    assert n is not None
-
-    return init, n
-
-
-def int2uint(x, nbits):
-    return x & ((1 << nbits)-1)
-
-# base 32
-def clz(x):
-    if x == 0:
-        return 32
-
-    n = 0
-    if x <= 0x0000FFFF:
-        n = n + 16
-        x = x << 16
-    if x <= 0x00FFFFFF:
-        n = n + 8
-        x = x << 8
-    if x <= 0x0FFFFFFF:
-        n = n + 4
-        x = x << 4
-    if x <= 0x3FFFFFFF:
-        n = n + 2
-        x = x << 2
-    if x <= 0x7FFFFFFF:
-        n = n + 1
-    return n
-
-
-def log2(x):
-    return 31 - clz(x)
-
-def clog2(x):
-    if x == 0: return 0
-    y = log2(x)
-    if x > (1 << y):
-        y = y + 1
-    return y
-
-
-def clog2safe(x):
+def clog2safe(x: int) -> int:
     if x == 1:
         return 1
     return clog2(x)
-
-
-def pow2(n):
-    return 1 << n
-
-def rol(l, m):
-    n = len(l)
-    return l[m:n] + l[0:m]
-
-def ror(l, m):
-    n = len(l)
-    return l[n-m:n] + l[0:n-m]
-
-
-if __name__ == "__main__":
-    def f(a,b):
-        return a or b
-    print(fun2seq(f))
diff --git a/magma/math.py b/magma/math.py
index 0c1702bb..371558fa 100644
--- a/magma/math.py
+++ b/magma/math.py
@@ -1,13 +1,8 @@
 import math
 
+from magma.common import deprecated
 
+
+@deprecated(msg="Use bitutils.clog2 instead")
 def log2_ceil(x: int) -> int:
     return math.ceil(math.log(x, 2))
-
-
-def log2_floor(x: int) -> int:
-    return math.floor(math.log(x, 2))
-
-
-def is_pow2(x: int) -> bool:
-    return log2_ceil(x) == log2_floor(x)


Test patch:
diff --git a/tests/test_bitutils.py b/tests/test_bitutils.py
new file mode 100644
index 00000000..d6843e01
--- /dev/null
+++ b/tests/test_bitutils.py
@@ -0,0 +1,43 @@
+import magma as m
+
+
+_SEQ2INT_CASES = (
+    ([1, 0, 1, 1], 13),
+    ([True, False, 1, 0], 5),
+)
+_INT2SEQ_CASES = (
+    (14, None, [0, 1, 1, 1]),
+    (12, 8, [0, 0, 1, 1, 0, 0, 0, 0]),
+)
+_CLOG2_CASES = (
+    (1, 0),
+    (7, 3),
+    (14, 4),
+    (16, 4),
+)
+_CLOG2SAFE_CASES = (
+    (1, 1),
+    (7, 3),
+    (14, 4),
+    (16, 4),
+)
+
+
+def test_seq2int():
+    for s, i in _SEQ2INT_CASES:
+        assert m.bitutils.seq2int(s) == i
+
+
+def test_int2seq():
+    for i, n, s in _INT2SEQ_CASES:
+        assert m.bitutils.int2seq(i, n=n) == s
+
+
+def test_clog2():
+    for i, o in _CLOG2_CASES:
+        assert m.bitutils.clog2(i) == o
+
+
+def test_clog2safe():
+    for i, o in _CLOG2SAFE_CASES:
+        assert m.bitutils.clog2safe(i) == o