From 0c4fda92fecb0f6b1a3dce8960f8d13459c7eef1 Mon Sep 17 00:00:00 2001 From: Catherine Date: Fri, 3 Mar 2023 06:20:34 +0000 Subject: [PATCH] hdl.ast: accept any constant-castable expression in `Signal(reset=)`. See amaranth-lang/rfcs#4. This functionality was not explicitly specified in the RFC but it falls under "anywhere an integer or an enumeration is accepted". --- amaranth/hdl/ast.py | 35 ++++++++++++++++++++++------------- amaranth/lib/enum.py | 4 ++-- tests/test_hdl_ast.py | 23 ++++++++++++++--------- tests/test_lib_enum.py | 10 +++++----- 4 files changed, 43 insertions(+), 29 deletions(-) diff --git a/amaranth/hdl/ast.py b/amaranth/hdl/ast.py index 6c5c0c4..3357a4a 100644 --- a/amaranth/hdl/ast.py +++ b/amaranth/hdl/ast.py @@ -1001,19 +1001,28 @@ class Signal(Value, DUID): self.width = shape.width self.signed = shape.signed - if isinstance(reset, Enum): - reset = reset.value - if not isinstance(reset, int): - raise TypeError("Reset value has to be an int or an integral Enum") - - reset_width = bits_for(reset, self.signed) - if reset != 0 and reset_width > self.width: - warnings.warn("Reset value {!r} requires {} bits to represent, but the signal " - "only has {} bits" - .format(reset, reset_width, self.width), - SyntaxWarning, stacklevel=2 + src_loc_at) - - self.reset = reset + orig_reset = reset + try: + reset = Const.cast(reset) + except TypeError: + raise TypeError("Reset value must be a constant-castable expression, not {!r}" + .format(orig_reset)) + if orig_reset not in (0, -1): # Avoid false positives for all-zeroes and all-ones + if reset.shape().signed and not self.signed: + warnings.warn( + message="Reset value {!r} is signed, but the signal shape is {!r}" + .format(orig_reset, shape), + category=SyntaxWarning, + stacklevel=2) + elif (reset.shape().width > self.width or + reset.shape().width == self.width and + self.signed and not reset.shape().signed): + warnings.warn( + message="Reset value {!r} will be truncated to the signal shape {!r}" + .format(orig_reset, shape), + category=SyntaxWarning, + stacklevel=2) + self.reset = reset.value self.reset_less = bool(reset_less) self.attrs = OrderedDict(() if attrs is None else attrs) diff --git a/amaranth/lib/enum.py b/amaranth/lib/enum.py index 8e1d91e..9748b28 100644 --- a/amaranth/lib/enum.py +++ b/amaranth/lib/enum.py @@ -44,7 +44,7 @@ class EnumMeta(ShapeCastable, py_enum.EnumMeta): .format(member)) from e if member_shape.signed and not shape.signed: warnings.warn( - message="Value of enumeration member {!r} is signed, but enumeration " + message="Value of enumeration member {!r} is signed, but the enumeration " "shape is {!r}" # the repr will be `unsigned(X)` .format(member, shape), category=SyntaxWarning, @@ -54,7 +54,7 @@ class EnumMeta(ShapeCastable, py_enum.EnumMeta): shape.signed and not member_shape.signed): warnings.warn( message="Value of enumeration member {!r} will be truncated to " - "enumeration shape {!r}" + "the enumeration shape {!r}" .format(member, shape), category=SyntaxWarning, stacklevel=2) diff --git a/tests/test_hdl_ast.py b/tests/test_hdl_ast.py index 215bac2..d360ef5 100644 --- a/tests/test_hdl_ast.py +++ b/tests/test_hdl_ast.py @@ -986,20 +986,25 @@ class SignalTestCase(FHDLTestCase): s1 = Signal(2, reset=UnsignedEnum.BAR) self.assertEqual(s1.reset, 2) with self.assertRaisesRegex(TypeError, - r"^Reset value has to be an int or an integral Enum$" - ): + r"^Reset value must be a constant-castable expression, " + r"not $"): Signal(1, reset=StringEnum.FOO) - def test_reset_narrow(self): + def test_reset_signed_mismatch(self): with self.assertWarnsRegex(SyntaxWarning, - r"^Reset value 8 requires 4 bits to represent, but the signal only has 3 bits$"): - Signal(3, reset=8) + r"^Reset value -2 is signed, but the signal shape is unsigned\(2\)$"): + Signal(unsigned(2), reset=-2) + + def test_reset_wrong_too_wide(self): with self.assertWarnsRegex(SyntaxWarning, - r"^Reset value 4 requires 4 bits to represent, but the signal only has 3 bits$"): - Signal(signed(3), reset=4) + r"^Reset value 2 will be truncated to the signal shape unsigned\(1\)$"): + Signal(unsigned(1), reset=2) with self.assertWarnsRegex(SyntaxWarning, - r"^Reset value -5 requires 4 bits to represent, but the signal only has 3 bits$"): - Signal(signed(3), reset=-5) + r"^Reset value 1 will be truncated to the signal shape signed\(1\)$"): + Signal(signed(1), reset=1) + with self.assertWarnsRegex(SyntaxWarning, + r"^Reset value -2 will be truncated to the signal shape signed\(1\)$"): + Signal(signed(1), reset=-2) def test_attrs(self): s1 = Signal() diff --git a/tests/test_lib_enum.py b/tests/test_lib_enum.py index f7f668c..60bdc5f 100644 --- a/tests/test_lib_enum.py +++ b/tests/test_lib_enum.py @@ -54,25 +54,25 @@ class EnumTestCase(FHDLTestCase): def test_shape_explicit_wrong_signed_mismatch(self): with self.assertWarnsRegex(SyntaxWarning, - r"^Value of enumeration member is signed, but enumeration " + r"^Value of enumeration member is signed, but the enumeration " r"shape is unsigned\(1\)$"): class EnumA(Enum, shape=unsigned(1)): A = -1 def test_shape_explicit_wrong_too_wide(self): with self.assertWarnsRegex(SyntaxWarning, - r"^Value of enumeration member will be truncated to enumeration " + r"^Value of enumeration member will be truncated to the enumeration " r"shape unsigned\(1\)$"): class EnumA(Enum, shape=unsigned(1)): A = 2 with self.assertWarnsRegex(SyntaxWarning, - r"^Value of enumeration member will be truncated to enumeration " + r"^Value of enumeration member will be truncated to the enumeration " r"shape signed\(1\)$"): class EnumB(Enum, shape=signed(1)): A = 1 with self.assertWarnsRegex(SyntaxWarning, - r"^Value of enumeration member will be truncated to enumeration " - r"shape signed\(1\)$"): + r"^Value of enumeration member will be truncated to the " + r"enumeration shape signed\(1\)$"): class EnumC(Enum, shape=signed(1)): A = -2