Skip to content

Commit 8bf4f77

Browse files
wanda-phiwhitequark
authored andcommitted
sim: use Format.* for VCD output, remove hdl._repr.
This also changes `decoder` a bit: when an enum is used as a decoder, it is converted to a `Format.Enum` instead. The original enum is still stored on the `decoder` attribute, so that it can be propagated on `Signal.like`.
1 parent 122be78 commit 8bf4f77

File tree

12 files changed

+88
-179
lines changed

12 files changed

+88
-179
lines changed

amaranth/back/rtlil.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from ..utils import bits_for
66
from .._utils import to_binary
77
from ..lib import wiring
8-
from ..hdl import _repr, _ast, _ir, _nir
8+
from ..hdl import _ast, _ir, _nir
99

1010

1111
__all__ = ["convert", "convert_fragment"]

amaranth/hdl/_ast.py

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -406,10 +406,6 @@ def format(self, obj, spec):
406406
"""
407407
return Format(f"{{:{spec}}}", Value.cast(obj))
408408

409-
# TODO: write an RFC for turning this into a proper interface method
410-
def _value_repr(self, value):
411-
return (_repr.Repr(_repr.FormatInt(), value),)
412-
413409

414410
class _ShapeLikeMeta(type):
415411
def __subclasscheck__(cls, subclass):
@@ -2097,46 +2093,15 @@ def __init__(self, shape=None, *, name=None, init=None, reset=None, reset_less=F
20972093

20982094
if isinstance(orig_shape, ShapeCastable):
20992095
self._format = orig_shape.format(orig_shape(self), "")
2096+
elif isinstance(orig_shape, type) and issubclass(orig_shape, Enum):
2097+
self._format = Format.Enum(self, orig_shape, name=orig_shape.__name__)
21002098
else:
21012099
self._format = Format("{}", self)
21022100

2103-
if decoder is not None:
2104-
# The value representation is specified explicitly. Since we do not expose `hdl._repr`,
2105-
# this is the only way to add a custom filter to the signal right now.
2106-
if isinstance(decoder, type) and issubclass(decoder, Enum):
2107-
self._value_repr = (_repr.Repr(_repr.FormatEnum(decoder), self),)
2108-
else:
2109-
self._value_repr = (_repr.Repr(_repr.FormatCustom(decoder), self),)
2110-
else:
2111-
# If it's an enum, expose it via `self.decoder` for compatibility, whether it's a Python
2112-
# enum or an Amaranth enum. This also sets the value representation, even for custom
2113-
# shape-castables that implement their own `_value_repr`.
2114-
if isinstance(orig_shape, type) and issubclass(orig_shape, Enum):
2115-
decoder = orig_shape
2116-
else:
2117-
decoder = None
2118-
# The value representation is specified implicitly in the shape of the signal.
2119-
if isinstance(orig_shape, ShapeCastable):
2120-
# A custom shape-castable always has a `_value_repr`, at least the default one.
2121-
self._value_repr = tuple(orig_shape._value_repr(self))
2122-
elif isinstance(orig_shape, type) and issubclass(orig_shape, Enum):
2123-
# A non-Amaranth enum needs a value repr constructed for it.
2124-
self._value_repr = (_repr.Repr(_repr.FormatEnum(orig_shape), self),)
2125-
else:
2126-
# Any other case is formatted as a plain integer.
2127-
self._value_repr = (_repr.Repr(_repr.FormatInt(), self),)
2128-
2129-
# Compute the value representation that will be used by Amaranth.
21302101
if isinstance(decoder, type) and issubclass(decoder, Enum):
2131-
# Violence. In the name of backwards compatibility!
2132-
def enum_decoder(value):
2133-
try:
2134-
return "{0.name:}/{0.value:}".format(decoder(value))
2135-
except ValueError:
2136-
return str(value)
2137-
self._decoder = enum_decoder
2138-
else:
2139-
self._decoder = decoder
2102+
self._format = Format.Enum(self, decoder, name=decoder.__name__)
2103+
2104+
self._decoder = decoder
21402105

21412106
def shape(self):
21422107
return Shape(self._width, self._signed)
@@ -3348,6 +3313,3 @@ class SignalDict(_MappedKeyDict):
33483313
class SignalSet(_MappedKeySet):
33493314
_map_key = SignalKey
33503315
_unmap_key = lambda self, key: key.signal
3351-
3352-
3353-
from . import _repr

amaranth/hdl/_repr.py

Lines changed: 0 additions & 58 deletions
This file was deleted.

amaranth/lib/data.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from amaranth._utils import final
88
from amaranth.hdl import *
9-
from amaranth.hdl._repr import Repr, FormatInt, FormatEnum
109
from amaranth import hdl
1110

1211

@@ -264,21 +263,6 @@ def format(self, value, format_spec):
264263
fields[str(key)] = Format("{}", field_value)
265264
return Format.Struct(value, fields)
266265

267-
def _value_repr(self, value):
268-
yield Repr(FormatInt(), value)
269-
for key, field in self:
270-
shape = Shape.cast(field.shape)
271-
field_value = value[field.offset:field.offset+shape.width]
272-
if shape.signed:
273-
field_value = field_value.as_signed()
274-
if isinstance(field.shape, ShapeCastable):
275-
for repr in field.shape._value_repr(field_value):
276-
yield Repr(repr.format, repr.value, path=(key,) + repr.path)
277-
elif isinstance(field.shape, type) and issubclass(field.shape, Enum):
278-
yield Repr(FormatEnum(field.shape), field_value, path=(key,))
279-
else:
280-
yield Repr(FormatInt(), field_value, path=(key,))
281-
282266

283267
class StructLayout(Layout):
284268
"""Description of a structure layout.

amaranth/lib/enum.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import operator
44

55
from ..hdl import Value, ValueCastable, Shape, ShapeCastable, Const, SyntaxWarning, Format
6-
from ..hdl._repr import Repr, FormatEnum
76

87

98
__all__ = py_enum.__all__ + ["EnumView", "FlagView"]
@@ -181,9 +180,6 @@ def format(cls, value, format_spec):
181180
raise ValueError(f"Format specifier {format_spec!r} is not supported for enums")
182181
return Format.Enum(value, cls, name=cls.__name__)
183182

184-
def _value_repr(cls, value):
185-
yield Repr(FormatEnum(cls), value)
186-
187183

188184
# In 3.11, Python renamed EnumMeta to EnumType. Like Python itself, we support both for
189185
# compatibility.

amaranth/sim/pysim.py

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
import itertools
33
import re
44
import os.path
5+
import enum as py_enum
56

67
from ..hdl import *
7-
from ..hdl._repr import *
8-
from ..hdl._mem import MemoryInstance
9-
from ..hdl._ast import SignalDict, Slice, Operator
8+
from ..hdl._ast import SignalDict
109
from ._base import *
10+
from ._pyeval import eval_format, eval_value
1111
from ._pyrtl import _FragmentCompiler
1212
from ._pycoro import PyCoroProcess
1313
from ._pyclock import PyClockProcess
@@ -21,23 +21,8 @@ class _VCDWriter:
2121
def decode_to_vcd(format, value):
2222
return format.format(value).expandtabs().replace(" ", "_")
2323

24-
@staticmethod
25-
def eval_field(field, signal, value):
26-
if isinstance(field, Signal):
27-
assert field is signal
28-
return value
29-
elif isinstance(field, Const):
30-
return field.value
31-
elif isinstance(field, Slice):
32-
sub = _VCDWriter.eval_field(field.value, signal, value)
33-
return (sub >> field.start) & ((1 << (field.stop - field.start)) - 1)
34-
elif isinstance(field, Operator) and field.operator in ('s', 'u'):
35-
sub = _VCDWriter.eval_field(field.operands[0], signal, value)
36-
return Const(sub, field.shape()).value
37-
else:
38-
raise NotImplementedError # :nocov:
39-
40-
def __init__(self, design, *, vcd_file, gtkw_file=None, traces=(), fs_per_delta=0, processes=()):
24+
def __init__(self, state, design, *, vcd_file, gtkw_file=None, traces=(), fs_per_delta=0, processes=()):
25+
self.state = state
4126
self.fs_per_delta = fs_per_delta
4227

4328
# Although pyvcd is a mandatory dependency, be resilient and import it as needed, so that
@@ -123,29 +108,21 @@ def __init__(self, design, *, vcd_file, gtkw_file=None, traces=(), fs_per_delta=
123108
for signal, names in itertools.chain(signal_names.items(), trace_names.items()):
124109
self.vcd_signal_vars[signal] = []
125110
self.gtkw_signal_names[signal] = []
126-
for repr in signal._value_repr:
127-
var_init = self.eval_field(repr.value, signal, signal.init)
128-
if isinstance(repr.format, FormatInt):
129-
var_type = "wire"
130-
var_size = repr.value.shape().width
131-
else:
132-
var_type = "string"
133-
var_size = 1
134-
var_init = self.decode_to_vcd(repr.format, var_init)
135111

112+
def add_var(path, var_type, var_size, var_init, value):
136113
vcd_var = None
137114
for (*var_scope, var_name) in names:
138115
if re.search(r"[ \t\r\n]", var_name):
139116
raise NameError("Signal '{}.{}' contains a whitespace character"
140117
.format(".".join(var_scope), var_name))
141118

142119
field_name = var_name
143-
for item in repr.path:
120+
for item in path:
144121
if isinstance(item, int):
145122
field_name += f"[{item}]"
146123
else:
147124
field_name += f".{item}"
148-
if repr.path:
125+
if path:
149126
field_name = "\\" + field_name
150127

151128
if vcd_var is None:
@@ -162,7 +139,35 @@ def __init__(self, design, *, vcd_file, gtkw_file=None, traces=(), fs_per_delta=
162139
scope=var_scope, name=field_name,
163140
var=vcd_var)
164141

165-
self.vcd_signal_vars[signal].append((vcd_var, repr))
142+
self.vcd_signal_vars[signal].append((vcd_var, value))
143+
144+
def add_wire_var(path, value):
145+
add_var(path, "wire", len(value), eval_value(self.state, value), value)
146+
147+
def add_format_var(path, fmt):
148+
add_var(path, "string", 1, eval_format(self.state, fmt), fmt)
149+
150+
def add_format(path, fmt):
151+
if isinstance(fmt, Format.Struct):
152+
add_wire_var(path, fmt._value)
153+
for name, subfmt in fmt._fields.items():
154+
add_format(path + (name,), subfmt)
155+
elif isinstance(fmt, Format.Array):
156+
add_wire_var(path, fmt._value)
157+
for idx, subfmt in enumerate(fmt._fields):
158+
add_format(path + (idx,), subfmt)
159+
elif (isinstance(fmt, Format) and
160+
len(fmt._chunks) == 1 and
161+
isinstance(fmt._chunks[0], tuple) and
162+
fmt._chunks[0][1] == ""):
163+
add_wire_var(path, fmt._chunks[0][0])
164+
else:
165+
add_format_var(path, fmt)
166+
167+
if signal._decoder is not None and not isinstance(signal._decoder, py_enum.EnumMeta):
168+
add_var((), "string", 1, signal._decoder(signal._init), signal._decoder)
169+
else:
170+
add_format((), signal._format)
166171

167172
for memory, memory_name in memories.items():
168173
self.vcd_memory_vars[memory] = vcd_vars = []
@@ -205,11 +210,15 @@ def __init__(self, design, *, vcd_file, gtkw_file=None, traces=(), fs_per_delta=
205210
except KeyError:
206211
pass # try another name
207212

208-
def update_signal(self, timestamp, signal, value):
213+
def update_signal(self, timestamp, signal):
209214
for (vcd_var, repr) in self.vcd_signal_vars.get(signal, ()):
210-
var_value = self.eval_field(repr.value, signal, value)
211-
if not isinstance(repr.format, FormatInt):
212-
var_value = self.decode_to_vcd(repr.format, var_value)
215+
if isinstance(repr, Value):
216+
var_value = eval_value(self.state, repr)
217+
elif isinstance(repr, (Format, Format.Enum)):
218+
var_value = eval_format(self.state, repr)
219+
else:
220+
# decoder
221+
var_value = repr(eval_value(self.state, signal))
213222
self.vcd_writer.change(vcd_var, timestamp, var_value)
214223

215224
def update_memory(self, timestamp, memory, addr, value):
@@ -512,7 +521,7 @@ def _step_rtl(self):
512521
if isinstance(change, _PySignalState):
513522
signal_state = change
514523
vcd_writer.update_signal(now_plus_deltas,
515-
signal_state.signal, signal_state.curr)
524+
signal_state.signal)
516525
elif isinstance(change, _PyMemoryChange):
517526
vcd_writer.update_memory(now_plus_deltas, change.state.memory,
518527
change.addr, change.state.data[change.addr])
@@ -559,7 +568,7 @@ def _now_plus_deltas(self, vcd_writer):
559568

560569
@contextmanager
561570
def write_vcd(self, *, vcd_file, gtkw_file, traces, fs_per_delta):
562-
vcd_writer = _VCDWriter(self._design,
571+
vcd_writer = _VCDWriter(self._state, self._design,
563572
vcd_file=vcd_file, gtkw_file=gtkw_file, traces=traces, fs_per_delta=fs_per_delta,
564573
processes=self._testbenches)
565574
try:

docs/changes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Implemented RFCs
6161
.. _RFC 59: https://amaranth-lang.org/rfcs/0059-no-domain-upwards-propagation.html
6262
.. _RFC 62: https://amaranth-lang.org/rfcs/0062-memory-data.html
6363
.. _RFC 63: https://amaranth-lang.org/rfcs/0063-remove-lib-coding.html
64+
.. _RFC 65: https://amaranth-lang.org/rfcs/0065-format-struct-enum.html
6465

6566
* `RFC 17`_: Remove ``log2_int``
6667
* `RFC 27`_: Testbench processes for the simulator
@@ -75,6 +76,7 @@ Implemented RFCs
7576
* `RFC 59`_: Get rid of upwards propagation of clock domains
7677
* `RFC 62`_: The ``MemoryData`` class
7778
* `RFC 63`_: Remove ``amaranth.lib.coding``
79+
* `RFC 65`_: Special formatting for structures and enums
7880

7981

8082
Language changes

tests/test_hdl_ast.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,27 +1326,25 @@ class Color(Enum):
13261326
RED = 1
13271327
BLUE = 2
13281328
s = Signal(decoder=Color)
1329-
self.assertEqual(s.decoder(1), "RED/1")
1330-
self.assertEqual(s.decoder(3), "3")
1331-
self.assertRepr(s._value_repr, "(Repr(FormatEnum(Color), (sig s), ()),)")
1329+
self.assertEqual(s.decoder, Color)
1330+
self.assertRepr(s._format, "(format-enum (sig s) 'Color' (1 'RED') (2 'BLUE'))")
13321331

13331332
def test_enum(self):
13341333
s1 = Signal(UnsignedEnum)
13351334
self.assertEqual(s1.shape(), unsigned(2))
13361335
s2 = Signal(SignedEnum)
13371336
self.assertEqual(s2.shape(), signed(2))
1338-
self.assertEqual(s2.decoder(SignedEnum.FOO), "FOO/-1")
1339-
self.assertRepr(s2._value_repr, "(Repr(FormatEnum(SignedEnum), (sig s2), ()),)")
1337+
self.assertRepr(s2._format, "(format-enum (sig s2) 'SignedEnum' (-1 'FOO') (0 'BAR') (1 'BAZ'))")
13401338

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

1347-
def test_value_repr(self):
1345+
def test_format_simple(self):
13481346
s = Signal()
1349-
self.assertRepr(s._value_repr, "(Repr(FormatInt(), (sig s), ()),)")
1347+
self.assertRepr(s._format, "(format '{}' (sig s))")
13501348

13511349

13521350
class ClockSignalTestCase(FHDLTestCase):

tests/test_hdl_rec.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def test_slice_tuple(self):
203203

204204
def test_enum_decoder(self):
205205
r1 = Record([("a", UnsignedEnum)])
206-
self.assertEqual(r1.a.decoder(UnsignedEnum.FOO), "FOO/1")
206+
self.assertRepr(r1.a._format, "(format-enum (sig r1__a) 'UnsignedEnum' (1 'FOO') (2 'BAR') (3 'BAZ'))")
207207

208208
def test_operators(self):
209209
r1 = Record([("a", 1)])

0 commit comments

Comments
 (0)