Skip to content

hdl._ast: fix _value_repr computation. #1237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 25, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions amaranth/hdl/_ast.py
Original file line number Diff line number Diff line change
@@ -1972,9 +1972,11 @@ def __init__(self, shape=None, *, name=None, init=None, reset=None, reset_less=F

if decoder is not None:
# The value representation is specified explicitly. Since we do not expose `hdl._repr`,
# this is the only way to add a custom filter to the signal right now. The setter sets
# `self._value_repr` as well as the compatibility `self.decoder`.
pass
# this is the only way to add a custom filter to the signal right now.
if isinstance(decoder, type) and issubclass(decoder, Enum):
self._value_repr = (_repr.Repr(_repr.FormatEnum(decoder), self),)
else:
self._value_repr = (_repr.Repr(_repr.FormatCustom(decoder), self),)
else:
# If it's an enum, expose it via `self.decoder` for compatibility, whether it's a Python
# enum or an Amaranth enum. This also sets the value representation, even for custom
@@ -1995,20 +1997,16 @@ def __init__(self, shape=None, *, name=None, init=None, reset=None, reset_less=F
self._value_repr = (_repr.Repr(_repr.FormatInt(), self),)

# Compute the value representation that will be used by Amaranth.
if decoder is None:
self._value_repr = (_repr.Repr(_repr.FormatInt(), self),)
self._decoder = None
elif not (isinstance(decoder, type) and issubclass(decoder, Enum)):
self._value_repr = (_repr.Repr(_repr.FormatCustom(decoder), self),)
self._decoder = decoder
else: # Violence. In the name of backwards compatibility!
self._value_repr = (_repr.Repr(_repr.FormatEnum(decoder), self),)
if isinstance(decoder, type) and issubclass(decoder, Enum):
# Violence. In the name of backwards compatibility!
def enum_decoder(value):
try:
return "{0.name:}/{0.value:}".format(decoder(value))
except ValueError:
return str(value)
self._decoder = enum_decoder
else:
self._decoder = decoder

@property
def width(self):
12 changes: 12 additions & 0 deletions amaranth/hdl/_repr.py
Original file line number Diff line number Diff line change
@@ -14,6 +14,9 @@ class FormatInt(Format):
def format(self, value):
return f"{value:d}"

def __repr__(self):
return f"FormatInt()"


class FormatEnum(Format):
def __init__(self, enum):
@@ -25,6 +28,9 @@ def format(self, value):
except ValueError:
return f"?/{value:d}"

def __repr__(self):
return f"FormatEnum({self.enum.__name__})"


class FormatCustom(Format):
def __init__(self, formatter):
@@ -33,6 +39,9 @@ def __init__(self, formatter):
def format(self, value):
return self.formatter(value)

def __repr__(self):
return f"FormatCustom({self.formatter})"


class Repr:
def __init__(self, format, value, *, path=()):
@@ -44,3 +53,6 @@ def __init__(self, format, value, *, path=()):
self.format = format
self.value = value
self.path = path

def __repr__(self):
return f"Repr({self.format!r}, {self.value!r}, {self.path!r})"
6 changes: 6 additions & 0 deletions tests/test_hdl_ast.py
Original file line number Diff line number Diff line change
@@ -1321,20 +1321,26 @@ class Color(Enum):
s = Signal(decoder=Color)
self.assertEqual(s.decoder(1), "RED/1")
self.assertEqual(s.decoder(3), "3")
self.assertRepr(s._value_repr, "(Repr(FormatEnum(Color), (sig s), ()),)")

def test_enum(self):
s1 = Signal(UnsignedEnum)
self.assertEqual(s1.shape(), unsigned(2))
s2 = Signal(SignedEnum)
self.assertEqual(s2.shape(), signed(2))
self.assertEqual(s2.decoder(SignedEnum.FOO), "FOO/-1")
self.assertRepr(s2._value_repr, "(Repr(FormatEnum(SignedEnum), (sig s2), ()),)")

def test_const_wrong(self):
s1 = Signal()
with self.assertRaisesRegex(TypeError,
r"^Value \(sig s1\) cannot be converted to an Amaranth constant$"):
Const.cast(s1)

def test_value_repr(self):
s = Signal()
self.assertRepr(s._value_repr, "(Repr(FormatInt(), (sig s), ()),)")


class ClockSignalTestCase(FHDLTestCase):
def test_domain(self):
5 changes: 5 additions & 0 deletions tests/test_lib_data.py
Original file line number Diff line number Diff line change
@@ -458,6 +458,11 @@ def test_construct_signal(self):
self.assertIsInstance(cv, Signal)
self.assertEqual(cv.shape(), unsigned(3))
self.assertEqual(cv.name, "v")
self.assertRepr(cv._value_repr, """
(Repr(FormatInt(), (sig v), ()),
Repr(FormatInt(), (slice (sig v) 0:1), ('a',)),
Repr(FormatInt(), (slice (sig v) 1:3), ('b',)))
""")

def test_construct_signal_init(self):
v1 = Signal(data.StructLayout({"a": unsigned(1), "b": unsigned(2)}),
1 change: 1 addition & 0 deletions tests/test_lib_enum.py
Original file line number Diff line number Diff line change
@@ -137,6 +137,7 @@ class EnumA(IntEnum, shape=signed(4)):
B = -3
a = Signal(EnumA)
self.assertRepr(a, "(sig a)")
self.assertRepr(a._value_repr, "(Repr(FormatEnum(EnumA), (sig a), ()),)")

def test_enum_view(self):
class EnumA(Enum, shape=signed(4)):