Skip to content

Commit 4bfe2cd

Browse files
whitequarkwanda-phi
andcommitted
sim: add support for dumping structure fields in VCD.
See #790. This commit adds an entirely private API for describing formatting of values that is used in the standard library, in departure from our standing policy of not using private APIs in the standard library. This is a temporary measure intended to get the version 0.4 released faster, as it has been years in the making. It is expected that this API will be made public in the version 0.5 after going through the usual RFC process. This commit only adds VCD lines for fields defined in `lib.data.Layout` when using `sim.pysim`. The emitted RTLIL and Verilog remain the same. It is expected that when `sim.cxxsim` lands, RTLIL/Verilog output will include aliases for layout fields as well. The value representation API also handles formatting of enumerations, with no changes visible to the designer. The implementation of `Signal(decoder=)` is changed as well to use the new API, with full backwards compatibility and no public API changes. Co-authored-by: Wanda <[email protected]>
1 parent 04f9069 commit 4bfe2cd

File tree

6 files changed

+198
-67
lines changed

6 files changed

+198
-67
lines changed

amaranth/back/rtlil.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import re
66

77
from .._utils import bits_for, flatten
8-
from ..hdl import ast, ir, mem, xfrm
8+
from ..hdl import ast, ir, mem, xfrm, _repr
99
from ..lib import wiring
1010

1111

@@ -337,12 +337,14 @@ def resolve(self, signal, prefix=None):
337337
wire_name = signal.name
338338

339339
is_sync_driven = signal in self.driven and self.driven[signal]
340-
340+
341341
attrs = dict(signal.attrs)
342-
if signal._enum_class is not None:
343-
attrs["enum_base_type"] = signal._enum_class.__name__
344-
for value in signal._enum_class:
345-
attrs["enum_value_{:0{}b}".format(value.value, signal.width)] = value.name
342+
for repr in signal._value_repr:
343+
if repr.path == () and isinstance(repr.format, _repr.FormatEnum):
344+
enum = repr.format.enum
345+
attrs["enum_base_type"] = enum.__name__
346+
for value in enum:
347+
attrs["enum_value_{:0{}b}".format(value.value, signal.width)] = value.name
346348

347349
# For every signal in the sync domain, assign \sig's initial value (using the \init reg
348350
# attribute) to the reset value.
@@ -872,7 +874,7 @@ def _convert_fragment(builder, fragment, name_map, hierarchy):
872874

873875
if sub_type == "$mem_v2" and "MEMID" not in sub_params:
874876
sub_params["MEMID"] = builder._make_name(sub_name, local=False)
875-
877+
876878
sub_ports = OrderedDict()
877879
for port, value in sub_port_map.items():
878880
if not isinstance(subfragment, ir.Instance):
@@ -917,7 +919,7 @@ def _convert_fragment(builder, fragment, name_map, hierarchy):
917919
stmt_compiler._has_rhs = False
918920
stmt_compiler._wrap_assign = False
919921
stmt_compiler(group_stmts)
920-
922+
921923
# For every driven signal in the sync domain, create a flop of appropriate type. Which type
922924
# is appropriate depends on the domain: for domains with sync reset, it is a $dff, for
923925
# domains with async reset it is an $adff. The latter is directly provided with the reset
@@ -930,7 +932,7 @@ def _convert_fragment(builder, fragment, name_map, hierarchy):
930932
wire_curr, wire_next = compiler_state.resolve(signal)
931933

932934
if not cd.async_reset:
933-
# For sync reset flops, the reset value comes from logic inserted by
935+
# For sync reset flops, the reset value comes from logic inserted by
934936
# `hdl.xfrm.DomainLowerer`.
935937
module.cell("$dff", ports={
936938
"\\CLK": wire_clk,

amaranth/hdl/_repr.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from abc import ABCMeta, abstractmethod
2+
3+
4+
__all__ = ["Format", "FormatInt", "FormatEnum", "FormatCustom", "Repr"]
5+
6+
7+
class Format(metaclass=ABCMeta):
8+
@abstractmethod
9+
def format(self, value):
10+
raise NotImplementedError
11+
12+
13+
class FormatInt(Format):
14+
def format(self, value):
15+
return f"{value:d}"
16+
17+
18+
class FormatEnum(Format):
19+
def __init__(self, enum):
20+
self.enum = enum
21+
22+
def format(self, value):
23+
try:
24+
return f"{self.enum(value).name}/{value:d}"
25+
except ValueError:
26+
return f"?/{value:d}"
27+
28+
29+
class FormatCustom(Format):
30+
def __init__(self, formatter):
31+
self.formatter = formatter
32+
33+
def format(self, value):
34+
return self.formatter(value)
35+
36+
37+
class Repr:
38+
def __init__(self, format, value, *, path=()):
39+
from .ast import Value # avoid a circular dependency
40+
assert isinstance(format, Format)
41+
assert isinstance(value, Value)
42+
assert isinstance(path, tuple) and all(isinstance(part, (str, int)) for part in path)
43+
44+
self.format = format
45+
self.value = value
46+
self.path = path

amaranth/hdl/ast.py

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from enum import Enum
88
from itertools import chain
99

10+
from ._repr import *
1011
from .. import tracer
1112
from .._utils import *
1213
from .._utils import _ignore_deprecated
@@ -53,6 +54,9 @@ def __init_subclass__(cls, **kwargs):
5354
raise TypeError(f"Class '{cls.__name__}' deriving from `ShapeCastable` must override "
5455
f"the `const` method")
5556

57+
def _value_repr(self, value):
58+
return (Repr(FormatInt(), value),)
59+
5660

5761
class Shape:
5862
"""Bit width and signedness of a value.
@@ -1127,19 +1131,51 @@ def __init__(self, shape=None, *, name=None, reset=None, reset_less=False,
11271131

11281132
self.attrs = OrderedDict(() if attrs is None else attrs)
11291133

1130-
if decoder is None and isinstance(orig_shape, type) and issubclass(orig_shape, Enum):
1131-
decoder = orig_shape
1132-
if isinstance(decoder, type) and issubclass(decoder, Enum):
1134+
if decoder is not None:
1135+
# The value representation is specified explicitly. Since we do not expose `hdl._repr`,
1136+
# this is the only way to add a custom filter to the signal right now. The setter sets
1137+
# `self._value_repr` as well as the compatibility `self.decoder`.
1138+
self.decoder = decoder
1139+
else:
1140+
# If it's an enum, expose it via `self.decoder` for compatibility, whether it's a Python
1141+
# enum or an Amaranth enum. This also sets the value representation, even for custom
1142+
# shape-castables that implement their own `_value_repr`.
1143+
if isinstance(orig_shape, type) and issubclass(orig_shape, Enum):
1144+
self.decoder = orig_shape
1145+
else:
1146+
self.decoder = None
1147+
# The value representation is specified implicitly in the shape of the signal.
1148+
if isinstance(orig_shape, ShapeCastable):
1149+
# A custom shape-castable always has a `_value_repr`, at least the default one.
1150+
self._value_repr = tuple(orig_shape._value_repr(self))
1151+
elif isinstance(orig_shape, type) and issubclass(orig_shape, Enum):
1152+
# A non-Amaranth enum needs a value repr constructed for it.
1153+
self._value_repr = (Repr(FormatEnum(orig_shape), self),)
1154+
else:
1155+
# Any other case is formatted as a plain integer.
1156+
self._value_repr = (Repr(FormatInt(), self),)
1157+
1158+
@property
1159+
def decoder(self):
1160+
return self._decoder
1161+
1162+
@decoder.setter
1163+
def decoder(self, decoder):
1164+
# Compute the value representation that will be used by Amaranth.
1165+
if decoder is None:
1166+
self._value_repr = (Repr(FormatInt(), self),)
1167+
self._decoder = None
1168+
elif not (isinstance(decoder, type) and issubclass(decoder, Enum)):
1169+
self._value_repr = (Repr(FormatCustom(decoder), self),)
1170+
self._decoder = decoder
1171+
else: # Violence. In the name of backwards compatibility!
1172+
self._value_repr = (Repr(FormatEnum(decoder), self),)
11331173
def enum_decoder(value):
11341174
try:
11351175
return "{0.name:}/{0.value:}".format(decoder(value))
11361176
except ValueError:
11371177
return str(value)
1138-
self.decoder = enum_decoder
1139-
self._enum_class = decoder
1140-
else:
1141-
self.decoder = decoder
1142-
self._enum_class = None
1178+
self._decoder = enum_decoder
11431179

11441180
# Not a @classmethod because amaranth.compat requires it.
11451181
@staticmethod
@@ -1914,3 +1950,6 @@ class SignalDict(_MappedKeyDict):
19141950
class SignalSet(_MappedKeySet):
19151951
_map_key = SignalKey
19161952
_unmap_key = lambda self, key: key.signal
1953+
1954+
1955+
from ._repr import *

amaranth/lib/data.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from abc import ABCMeta, abstractmethod
2+
from enum import Enum
23
from collections.abc import Mapping, Sequence
34
import warnings
45

56
from amaranth.hdl import *
7+
from amaranth.hdl._repr import *
68
from amaranth.hdl.ast import ShapeCastable, ValueCastable
79

810

@@ -231,6 +233,21 @@ def const(self, init):
231233
int_value |= (key_value.value << field.offset) & mask
232234
return View(self, Const(int_value, self.as_shape()))
233235

236+
def _value_repr(self, value):
237+
yield Repr(FormatInt(), value)
238+
for key, field in self:
239+
shape = Shape.cast(field.shape)
240+
field_value = value[field.offset:field.offset+shape.width]
241+
if shape.signed:
242+
field_value = field_value.as_signed()
243+
if isinstance(field.shape, ShapeCastable):
244+
for repr in field.shape._value_repr(field_value):
245+
yield Repr(repr.format, repr.value, path=(key,) + repr.path)
246+
elif isinstance(field.shape, type) and issubclass(field.shape, Enum):
247+
yield Repr(FormatEnum(field.shape), field_value, path=(key,))
248+
else:
249+
yield Repr(FormatInt(), field_value, path=(key,))
250+
234251

235252
class StructLayout(Layout):
236253
"""Description of a structure layout.
@@ -774,6 +791,9 @@ def const(cls, init):
774791
fields.update(init or {})
775792
return cls.as_shape().const(fields)
776793

794+
def _value_repr(cls, value):
795+
return cls.__layout._value_repr(value)
796+
777797

778798
class Struct(View, metaclass=_AggregateMeta):
779799
"""Structures defined with annotations.

amaranth/lib/enum.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import warnings
33

44
from ..hdl.ast import Value, Shape, ShapeCastable, Const
5+
from ..hdl._repr import *
56

67

78
__all__ = py_enum.__all__
@@ -150,6 +151,9 @@ def const(cls, init):
150151
member = cls(init)
151152
return Const(member.value, cls.as_shape())
152153

154+
def _value_repr(cls, value):
155+
yield Repr(FormatEnum(cls), value)
156+
153157

154158
class Enum(py_enum.Enum):
155159
"""Subclass of the standard :class:`enum.Enum` that has :class:`EnumMeta` as

0 commit comments

Comments
 (0)