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`.
This commit is contained in:
Wanda 2024-04-12 23:08:41 +02:00 committed by Catherine
parent 122be7849c
commit 8bf4f77616
12 changed files with 88 additions and 179 deletions

View file

@ -5,7 +5,7 @@ import io
from ..utils import bits_for
from .._utils import to_binary
from ..lib import wiring
from ..hdl import _repr, _ast, _ir, _nir
from ..hdl import _ast, _ir, _nir
__all__ = ["convert", "convert_fragment"]

View file

@ -406,10 +406,6 @@ class ShapeCastable:
"""
return Format(f"{{:{spec}}}", Value.cast(obj))
# TODO: write an RFC for turning this into a proper interface method
def _value_repr(self, value):
return (_repr.Repr(_repr.FormatInt(), value),)
class _ShapeLikeMeta(type):
def __subclasscheck__(cls, subclass):
@ -2097,46 +2093,15 @@ class Signal(Value, DUID, metaclass=_SignalMeta):
if isinstance(orig_shape, ShapeCastable):
self._format = orig_shape.format(orig_shape(self), "")
elif isinstance(orig_shape, type) and issubclass(orig_shape, Enum):
self._format = Format.Enum(self, orig_shape, name=orig_shape.__name__)
else:
self._format = Format("{}", self)
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.
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
# shape-castables that implement their own `_value_repr`.
if isinstance(orig_shape, type) and issubclass(orig_shape, Enum):
decoder = orig_shape
else:
decoder = None
# The value representation is specified implicitly in the shape of the signal.
if isinstance(orig_shape, ShapeCastable):
# A custom shape-castable always has a `_value_repr`, at least the default one.
self._value_repr = tuple(orig_shape._value_repr(self))
elif isinstance(orig_shape, type) and issubclass(orig_shape, Enum):
# A non-Amaranth enum needs a value repr constructed for it.
self._value_repr = (_repr.Repr(_repr.FormatEnum(orig_shape), self),)
else:
# Any other case is formatted as a plain integer.
self._value_repr = (_repr.Repr(_repr.FormatInt(), self),)
# Compute the value representation that will be used by Amaranth.
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
self._format = Format.Enum(self, decoder, name=decoder.__name__)
self._decoder = decoder
def shape(self):
return Shape(self._width, self._signed)
@ -3348,6 +3313,3 @@ class SignalDict(_MappedKeyDict):
class SignalSet(_MappedKeySet):
_map_key = SignalKey
_unmap_key = lambda self, key: key.signal
from . import _repr

View file

@ -1,58 +0,0 @@
from abc import ABCMeta, abstractmethod
__all__ = ["Format", "FormatInt", "FormatEnum", "FormatCustom", "Repr"]
class Format(metaclass=ABCMeta):
@abstractmethod
def format(self, value):
raise NotImplementedError
class FormatInt(Format):
def format(self, value):
return f"{value:d}"
def __repr__(self):
return f"FormatInt()"
class FormatEnum(Format):
def __init__(self, enum):
self.enum = enum
def format(self, value):
try:
return f"{self.enum(value).name}/{value:d}"
except ValueError:
return f"?/{value:d}"
def __repr__(self):
return f"FormatEnum({self.enum.__name__})"
class FormatCustom(Format):
def __init__(self, formatter):
self.formatter = 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=()):
from ._ast import Value # avoid a circular dependency
assert isinstance(format, Format)
assert isinstance(value, Value)
assert isinstance(path, tuple) and all(isinstance(part, (str, int)) for part in 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})"

View file

@ -6,7 +6,6 @@ import operator
from amaranth._utils import final
from amaranth.hdl import *
from amaranth.hdl._repr import Repr, FormatInt, FormatEnum
from amaranth import hdl
@ -264,21 +263,6 @@ class Layout(ShapeCastable, metaclass=ABCMeta):
fields[str(key)] = Format("{}", field_value)
return Format.Struct(value, fields)
def _value_repr(self, value):
yield Repr(FormatInt(), value)
for key, field in self:
shape = Shape.cast(field.shape)
field_value = value[field.offset:field.offset+shape.width]
if shape.signed:
field_value = field_value.as_signed()
if isinstance(field.shape, ShapeCastable):
for repr in field.shape._value_repr(field_value):
yield Repr(repr.format, repr.value, path=(key,) + repr.path)
elif isinstance(field.shape, type) and issubclass(field.shape, Enum):
yield Repr(FormatEnum(field.shape), field_value, path=(key,))
else:
yield Repr(FormatInt(), field_value, path=(key,))
class StructLayout(Layout):
"""Description of a structure layout.

View file

@ -3,7 +3,6 @@ import warnings
import operator
from ..hdl import Value, ValueCastable, Shape, ShapeCastable, Const, SyntaxWarning, Format
from ..hdl._repr import Repr, FormatEnum
__all__ = py_enum.__all__ + ["EnumView", "FlagView"]
@ -181,9 +180,6 @@ class EnumType(ShapeCastable, py_enum.EnumMeta):
raise ValueError(f"Format specifier {format_spec!r} is not supported for enums")
return Format.Enum(value, cls, name=cls.__name__)
def _value_repr(cls, value):
yield Repr(FormatEnum(cls), value)
# In 3.11, Python renamed EnumMeta to EnumType. Like Python itself, we support both for
# compatibility.

View file

@ -2,12 +2,12 @@ from contextlib import contextmanager
import itertools
import re
import os.path
import enum as py_enum
from ..hdl import *
from ..hdl._repr import *
from ..hdl._mem import MemoryInstance
from ..hdl._ast import SignalDict, Slice, Operator
from ..hdl._ast import SignalDict
from ._base import *
from ._pyeval import eval_format, eval_value
from ._pyrtl import _FragmentCompiler
from ._pycoro import PyCoroProcess
from ._pyclock import PyClockProcess
@ -21,23 +21,8 @@ class _VCDWriter:
def decode_to_vcd(format, value):
return format.format(value).expandtabs().replace(" ", "_")
@staticmethod
def eval_field(field, signal, value):
if isinstance(field, Signal):
assert field is signal
return value
elif isinstance(field, Const):
return field.value
elif isinstance(field, Slice):
sub = _VCDWriter.eval_field(field.value, signal, value)
return (sub >> field.start) & ((1 << (field.stop - field.start)) - 1)
elif isinstance(field, Operator) and field.operator in ('s', 'u'):
sub = _VCDWriter.eval_field(field.operands[0], signal, value)
return Const(sub, field.shape()).value
else:
raise NotImplementedError # :nocov:
def __init__(self, design, *, vcd_file, gtkw_file=None, traces=(), fs_per_delta=0, processes=()):
def __init__(self, state, design, *, vcd_file, gtkw_file=None, traces=(), fs_per_delta=0, processes=()):
self.state = state
self.fs_per_delta = fs_per_delta
# Although pyvcd is a mandatory dependency, be resilient and import it as needed, so that
@ -123,16 +108,8 @@ class _VCDWriter:
for signal, names in itertools.chain(signal_names.items(), trace_names.items()):
self.vcd_signal_vars[signal] = []
self.gtkw_signal_names[signal] = []
for repr in signal._value_repr:
var_init = self.eval_field(repr.value, signal, signal.init)
if isinstance(repr.format, FormatInt):
var_type = "wire"
var_size = repr.value.shape().width
else:
var_type = "string"
var_size = 1
var_init = self.decode_to_vcd(repr.format, var_init)
def add_var(path, var_type, var_size, var_init, value):
vcd_var = None
for (*var_scope, var_name) in names:
if re.search(r"[ \t\r\n]", var_name):
@ -140,12 +117,12 @@ class _VCDWriter:
.format(".".join(var_scope), var_name))
field_name = var_name
for item in repr.path:
for item in path:
if isinstance(item, int):
field_name += f"[{item}]"
else:
field_name += f".{item}"
if repr.path:
if path:
field_name = "\\" + field_name
if vcd_var is None:
@ -162,7 +139,35 @@ class _VCDWriter:
scope=var_scope, name=field_name,
var=vcd_var)
self.vcd_signal_vars[signal].append((vcd_var, repr))
self.vcd_signal_vars[signal].append((vcd_var, value))
def add_wire_var(path, value):
add_var(path, "wire", len(value), eval_value(self.state, value), value)
def add_format_var(path, fmt):
add_var(path, "string", 1, eval_format(self.state, fmt), fmt)
def add_format(path, fmt):
if isinstance(fmt, Format.Struct):
add_wire_var(path, fmt._value)
for name, subfmt in fmt._fields.items():
add_format(path + (name,), subfmt)
elif isinstance(fmt, Format.Array):
add_wire_var(path, fmt._value)
for idx, subfmt in enumerate(fmt._fields):
add_format(path + (idx,), subfmt)
elif (isinstance(fmt, Format) and
len(fmt._chunks) == 1 and
isinstance(fmt._chunks[0], tuple) and
fmt._chunks[0][1] == ""):
add_wire_var(path, fmt._chunks[0][0])
else:
add_format_var(path, fmt)
if signal._decoder is not None and not isinstance(signal._decoder, py_enum.EnumMeta):
add_var((), "string", 1, signal._decoder(signal._init), signal._decoder)
else:
add_format((), signal._format)
for memory, memory_name in memories.items():
self.vcd_memory_vars[memory] = vcd_vars = []
@ -205,11 +210,15 @@ class _VCDWriter:
except KeyError:
pass # try another name
def update_signal(self, timestamp, signal, value):
def update_signal(self, timestamp, signal):
for (vcd_var, repr) in self.vcd_signal_vars.get(signal, ()):
var_value = self.eval_field(repr.value, signal, value)
if not isinstance(repr.format, FormatInt):
var_value = self.decode_to_vcd(repr.format, var_value)
if isinstance(repr, Value):
var_value = eval_value(self.state, repr)
elif isinstance(repr, (Format, Format.Enum)):
var_value = eval_format(self.state, repr)
else:
# decoder
var_value = repr(eval_value(self.state, signal))
self.vcd_writer.change(vcd_var, timestamp, var_value)
def update_memory(self, timestamp, memory, addr, value):
@ -512,7 +521,7 @@ class PySimEngine(BaseEngine):
if isinstance(change, _PySignalState):
signal_state = change
vcd_writer.update_signal(now_plus_deltas,
signal_state.signal, signal_state.curr)
signal_state.signal)
elif isinstance(change, _PyMemoryChange):
vcd_writer.update_memory(now_plus_deltas, change.state.memory,
change.addr, change.state.data[change.addr])
@ -559,7 +568,7 @@ class PySimEngine(BaseEngine):
@contextmanager
def write_vcd(self, *, vcd_file, gtkw_file, traces, fs_per_delta):
vcd_writer = _VCDWriter(self._design,
vcd_writer = _VCDWriter(self._state, self._design,
vcd_file=vcd_file, gtkw_file=gtkw_file, traces=traces, fs_per_delta=fs_per_delta,
processes=self._testbenches)
try: