fhdl.ast.Signal: implement width derivation from min/max.
authorwhitequark <cz@m-labs.hk>
Wed, 12 Dec 2018 10:43:09 +0000 (10:43 +0000)
committerwhitequark <cz@m-labs.hk>
Wed, 12 Dec 2018 10:43:09 +0000 (10:43 +0000)
nmigen/fhdl/ast.py
nmigen/tools.py

index 1de1ce7f60412628ddf8ee1405eb44f3603a4208..74a0ca56158fb6f184b84502acfc16a39a429b3a 100644 (file)
@@ -1,3 +1,4 @@
+import builtins
 from collections import OrderedDict
 from collections.abc import Iterable, MutableMapping, MutableSet
 
@@ -496,6 +497,11 @@ class Signal(Value, DUID):
         If ``True``, do not generate reset logic for this ``Signal`` in synchronous statements.
         The ``reset`` value is only used as a combinatorial default or as the initial value.
         Defaults to ``False``.
+    min : int or None
+    max : int or None
+        If `bits_sign` is `None`, the signal bit width and signedness are
+        determined by the integer range given by `min` (inclusive,
+        defaults to 0) and `max` (exclusive, defaults to 2).
 
     Attributes
     ----------
@@ -505,7 +511,7 @@ class Signal(Value, DUID):
     reset : int
     """
 
-    def __init__(self, bits_sign=1, name=None, reset=0, reset_less=False):
+    def __init__(self, bits_sign=None, name=None, reset=0, reset_less=False, min=None, max=None):
         super().__init__()
 
         if name is None:
@@ -515,11 +521,28 @@ class Signal(Value, DUID):
                 name = "$signal"
         self.name = name
 
-        if isinstance(bits_sign, int):
-            bits_sign = bits_sign, False
-        self.nbits, self.signed = bits_sign
+        if bits_sign is None:
+            if min is None:
+                min = 0
+            if max is None:
+                max = 2
+            max -= 1  # make both bounds inclusive
+            if not min < max:
+                raise ValueError("Lower bound {!r} should be less than higher bound {!r}"
+                                 .format(min, max))
+            self.signed = min < 0 or max < 0
+            self.nbits  = builtins.max(bits_for(min, self.signed), bits_for(max, self.signed))
+
+        elif isinstance(bits_sign, int):
+            if not (min is None or max is None):
+                raise ValueError("Only one of bits/signedness or bounds may be specified")
+            self.nbits, self.signed = 1, False
+
+        else:
+            self.nbits, self.signed = bits_sign
+
         if not isinstance(self.nbits, int) or self.nbits < 0:
-            raise TypeError("Width must be a positive integer")
+            raise TypeError("Width must be a positive integer, not {!r}".format(self.nbits))
         self.reset = reset
         self.reset_less = reset_less
 
index 8f8e7c1bcf2932826095d8897de99d8f50e32fcf..4df7958ca790608dd813ec85c954035a779d9c1a 100644 (file)
@@ -1,7 +1,7 @@
 from collections import Iterable
 
 
-__all__ = ["flatten", "union"]
+__all__ = ["flatten", "union", "log2_int", "bits_for"]
 
 
 def flatten(i):
@@ -20,3 +20,23 @@ def union(i):
         else:
             r |= e
     return r
+
+
+def log2_int(n, need_pow2=True):
+    if n == 0:
+        return 0
+    r = (n - 1).bit_length()
+    if need_pow2 and (1 << r) != n:
+        raise ValueError("{} is not a power of 2".format(n))
+    return r
+
+
+def bits_for(n, require_sign_bit=False):
+    if n > 0:
+        r = log2_int(n + 1, False)
+    else:
+        require_sign_bit = True
+        r = log2_int(-n, False)
+    if require_sign_bit:
+        r += 1
+    return r