parent
21b5451036
commit
f135226a79
|
@ -78,11 +78,15 @@ class Shape:
|
|||
If ``False``, the value is unsigned. If ``True``, the value is signed two's complement.
|
||||
"""
|
||||
def __init__(self, width=1, signed=False):
|
||||
if not isinstance(width, int) or width < 0:
|
||||
raise TypeError("Width must be a non-negative integer, not {!r}"
|
||||
.format(width))
|
||||
if not isinstance(width, int):
|
||||
raise TypeError(f"Width must be an integer, not {width!r}")
|
||||
if not signed and width < 0:
|
||||
raise TypeError(f"Width of an unsigned value must be zero or a positive integer, "
|
||||
f"not {width}")
|
||||
if signed and width <= 0:
|
||||
raise TypeError(f"Width of a signed value must be a positive integer, not {width}")
|
||||
self.width = width
|
||||
self.signed = signed
|
||||
self.signed = bool(signed)
|
||||
|
||||
# The algorithm for inferring shape for standard Python enumerations is factored out so that
|
||||
# `Shape.cast()` and Amaranth's `EnumMeta.as_shape()` can both use it.
|
||||
|
@ -116,7 +120,7 @@ class Shape:
|
|||
return Shape(obj)
|
||||
elif isinstance(obj, range):
|
||||
if len(obj) == 0:
|
||||
return Shape(0, obj.start < 0)
|
||||
return Shape(0)
|
||||
signed = obj[0] < 0 or obj[-1] < 0
|
||||
width = max(bits_for(obj[0], signed),
|
||||
bits_for(obj[-1], signed))
|
||||
|
|
|
@ -153,8 +153,8 @@ Shapes from ranges
|
|||
|
||||
Casting a shape from a :class:`range` ``r`` produces a shape that:
|
||||
|
||||
* has a width large enough to represent both ``min(r)`` and ``max(r)``, and
|
||||
* is signed if either ``min(r)`` or ``max(r)`` are negative, unsigned otherwise.
|
||||
* has a width large enough to represent both ``min(r)`` and ``max(r)``, but not larger, and
|
||||
* is signed if ``r`` contains any negative values, unsigned otherwise.
|
||||
|
||||
Specifying a shape with a range is convenient for counters, indexes, and all other values whose width is derived from a set of numbers they must be able to fit:
|
||||
|
||||
|
@ -184,6 +184,16 @@ Specifying a shape with a range is convenient for counters, indexes, and all oth
|
|||
|
||||
Amaranth detects uses of :class:`Const` and :class:`Signal` that invoke such an off-by-one error, and emits a diagnostic message.
|
||||
|
||||
.. note::
|
||||
|
||||
An empty range always casts to an ``unsigned(0)``, even if both of its bounds are negative.
|
||||
This happens because, being empty, it does not contain any negative values.
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> Shape.cast(range(-1, -1))
|
||||
unsigned(0)
|
||||
|
||||
|
||||
.. _lang-shapeenum:
|
||||
|
||||
|
|
|
@ -42,11 +42,20 @@ class ShapeTestCase(FHDLTestCase):
|
|||
s3 = Shape(3, True)
|
||||
self.assertEqual(s3.width, 3)
|
||||
self.assertEqual(s3.signed, True)
|
||||
s4 = Shape(0)
|
||||
self.assertEqual(s4.width, 0)
|
||||
self.assertEqual(s4.signed, False)
|
||||
|
||||
def test_make_wrong(self):
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^Width must be a non-negative integer, not -1$"):
|
||||
Shape(-1)
|
||||
r"^Width must be an integer, not 'a'$"):
|
||||
Shape("a")
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^Width of an unsigned value must be zero or a positive integer, not -1$"):
|
||||
Shape(-1, signed=False)
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^Width of a signed value must be a positive integer, not 0$"):
|
||||
Shape(0, signed=True)
|
||||
|
||||
def test_compare_non_shape(self):
|
||||
self.assertNotEqual(Shape(1, True), "hi")
|
||||
|
@ -87,7 +96,7 @@ class ShapeTestCase(FHDLTestCase):
|
|||
|
||||
def test_cast_int_wrong(self):
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^Width must be a non-negative integer, not -1$"):
|
||||
r"^Width of an unsigned value must be zero or a positive integer, not -1$"):
|
||||
Shape.cast(-1)
|
||||
|
||||
def test_cast_tuple_wrong(self):
|
||||
|
@ -116,7 +125,7 @@ class ShapeTestCase(FHDLTestCase):
|
|||
self.assertEqual(s6.signed, False)
|
||||
s7 = Shape.cast(range(-1, -1))
|
||||
self.assertEqual(s7.width, 0)
|
||||
self.assertEqual(s7.signed, True)
|
||||
self.assertEqual(s7.signed, False)
|
||||
s8 = Shape.cast(range(0, 10, 3))
|
||||
self.assertEqual(s8.width, 4)
|
||||
self.assertEqual(s8.signed, False)
|
||||
|
@ -386,7 +395,7 @@ class ConstTestCase(FHDLTestCase):
|
|||
|
||||
def test_shape_wrong(self):
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^Width must be a non-negative integer, not -1$"):
|
||||
r"^Width of an unsigned value must be zero or a positive integer, not -1$"):
|
||||
Const(1, -1)
|
||||
|
||||
def test_wrong_fencepost(self):
|
||||
|
@ -1022,7 +1031,7 @@ class SignalTestCase(FHDLTestCase):
|
|||
|
||||
def test_shape_wrong(self):
|
||||
with self.assertRaisesRegex(TypeError,
|
||||
r"^Width must be a non-negative integer, not -10$"):
|
||||
r"^Width of an unsigned value must be zero or a positive integer, not -10$"):
|
||||
Signal(-10)
|
||||
|
||||
def test_name(self):
|
||||
|
|
Loading…
Reference in a new issue