diff --git a/amaranth/hdl/_ast.py b/amaranth/hdl/_ast.py index 78c16d4..c7eba34 100644 --- a/amaranth/hdl/_ast.py +++ b/amaranth/hdl/_ast.py @@ -93,8 +93,8 @@ class Shape: * a :class:`Shape`, where the result is itself; * an :class:`int`, where the result is :func:`unsigned(obj) `; - * a :class:`range`, where the result is wide enough to represent any element of the range, - and is signed if any element of the range is signed; + * a :class:`range`, where the result has minimal width required to represent all elements + of the range, and is signed if any element of the range is signed; * an :class:`enum.Enum` whose members are all :ref:`constant-castable ` or :class:`enum.IntEnum`, where the result is wide enough to represent any member of the enumeration, and is signed if any member of the enumeration is signed; @@ -121,6 +121,8 @@ class Shape: signed = obj[0] < 0 or obj[-1] < 0 width = max(bits_for(obj[0], signed), bits_for(obj[-1], signed)) + if obj[0] == obj[-1] == 0: + width = 0 return Shape(width, signed) elif isinstance(obj, type) and issubclass(obj, Enum): # For compatibility with third party enumerations, handle them as if they were diff --git a/docs/changes.rst b/docs/changes.rst index 2d68cdc..bc25118 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -36,9 +36,11 @@ Implemented RFCs .. _RFC 17: https://amaranth-lang.org/rfcs/0017-remove-log2-int.html .. _RFC 39: https://amaranth-lang.org/rfcs/0039-empty-case.html +.. _RFC 46: https://amaranth-lang.org/rfcs/0046-shape-range-1.html * `RFC 17`_: Remove ``log2_int`` * `RFC 39`_: Change semantics of no-argument ``m.Case()`` +* `RFC 46`_: Change ``Shape.cast(range(1))`` to ``unsigned(0)`` Language changes @@ -52,6 +54,7 @@ Language changes * Changed: ``Value.matches()`` with no patterns is ``Const(0)`` instead of ``Const(1)``. (`RFC 39`_) * Changed: ``Signal(range(stop), reset=stop)`` warning has been changed into a hard error and made to trigger on any out-of range value. * Changed: ``Signal(range(0))`` is now valid without a warning. +* Changed: ``Shape.cast(range(1))`` is now ``unsigned(0)``. (`RFC 46`_) * Deprecated: :func:`amaranth.utils.log2_int`. (`RFC 17`_) * Removed: (deprecated in 0.4) :meth:`Const.normalize`. (`RFC 5`_) * Removed: (deprecated in 0.4) :class:`Repl`. (`RFC 10`_) diff --git a/tests/test_hdl_ast.py b/tests/test_hdl_ast.py index cb3e6b9..5c79788 100644 --- a/tests/test_hdl_ast.py +++ b/tests/test_hdl_ast.py @@ -115,7 +115,7 @@ class ShapeTestCase(FHDLTestCase): self.assertEqual(s3.width, 4) self.assertEqual(s3.signed, True) s4 = Shape.cast(range(0, 1)) - self.assertEqual(s4.width, 1) + self.assertEqual(s4.width, 0) self.assertEqual(s4.signed, False) s5 = Shape.cast(range(-1, 0)) self.assertEqual(s5.width, 1) @@ -129,6 +129,9 @@ class ShapeTestCase(FHDLTestCase): s8 = Shape.cast(range(0, 10, 3)) self.assertEqual(s8.width, 4) self.assertEqual(s8.signed, False) + s9 = Shape.cast(range(0, 3, 3)) + self.assertEqual(s9.width, 0) + self.assertEqual(s9.signed, False) def test_cast_enum(self): s1 = Shape.cast(UnsignedEnum) @@ -1088,7 +1091,7 @@ class SignalTestCase(FHDLTestCase): s10 = Signal(range(0)) self.assertEqual(s10.shape(), unsigned(0)) s11 = Signal(range(1)) - self.assertEqual(s11.shape(), unsigned(1)) + self.assertEqual(s11.shape(), unsigned(0)) def test_shape_wrong(self): with self.assertRaisesRegex(TypeError,