From 4cb2dde25f8a66463bea14d10a039d11dce60d3b Mon Sep 17 00:00:00 2001 From: Wanda Date: Thu, 11 Apr 2024 17:33:41 +0200 Subject: [PATCH] lib.data: add `.format()` implementation. --- amaranth/lib/data.py | 37 +++++++++++++++++- tests/test_lib_data.py | 86 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 119 insertions(+), 4 deletions(-) diff --git a/amaranth/lib/data.py b/amaranth/lib/data.py index 2cebc1b..ea35950 100644 --- a/amaranth/lib/data.py +++ b/amaranth/lib/data.py @@ -6,7 +6,7 @@ import operator from amaranth._utils import final from amaranth.hdl import * -from amaranth.hdl._repr import * +from amaranth.hdl._repr import Repr, FormatInt, FormatEnum from amaranth import hdl @@ -248,6 +248,22 @@ class Layout(ShapeCastable, metaclass=ABCMeta): """ return Const(self, raw) + def format(self, value, format_spec): + if format_spec != "": + raise ValueError(f"Format specifier {format_spec!r} is not supported for layouts") + value = Value.cast(value) + fields = {} + for key, field in self: + shape = Shape.cast(field.shape) + field_value = value[field.offset:field.offset+shape.width] + if isinstance(field.shape, ShapeCastable): + fields[str(key)] = field.shape.format(field.shape(field_value), "") + else: + if shape.signed: + field_value = field_value.as_signed() + 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: @@ -539,6 +555,22 @@ class ArrayLayout(Layout): def __repr__(self): return f"ArrayLayout({self._elem_shape!r}, {self.length})" + def format(self, value, format_spec): + if format_spec != "": + raise ValueError(f"Format specifier {format_spec!r} is not supported for layouts") + value = Value.cast(value) + fields = [] + shape = Shape.cast(self._elem_shape) + for index in range(self._length): + field_value = value[shape.width * index:shape.width * (index + 1)] + if isinstance(self._elem_shape, ShapeCastable): + fields.append(self._elem_shape.format(self._elem_shape(field_value), "")) + else: + if shape.signed: + field_value = field_value.as_signed() + fields.append(Format("{}", field_value)) + return Format.Array(value, fields) + class FlexibleLayout(Layout): """Description of a flexible layout. @@ -1163,6 +1195,9 @@ class _AggregateMeta(ShapeCastable, type): def from_bits(cls, bits): return cls.as_shape().from_bits(bits) + def format(cls, value, format_spec): + return cls.__layout.format(value, format_spec) + def _value_repr(cls, value): return cls.__layout._value_repr(value) diff --git a/tests/test_lib_data.py b/tests/test_lib_data.py index 6618eea..3384935 100644 --- a/tests/test_lib_data.py +++ b/tests/test_lib_data.py @@ -75,7 +75,7 @@ class FieldTestCase(TestCase): data.Field(1, 0).offset = 1 -class StructLayoutTestCase(TestCase): +class StructLayoutTestCase(FHDLTestCase): def test_construct(self): sl = data.StructLayout({ "a": unsigned(1), @@ -126,8 +126,21 @@ class StructLayoutTestCase(TestCase): r"^Struct layout member shape must be a shape-castable object, not 1\.0$"): data.StructLayout({"a": 1.0}) + def test_format(self): + sl = data.StructLayout({ + "a": unsigned(1), + "b": signed(2), + }) + sig = Signal(sl) + self.assertRepr(sl.format(sig, ""), """ + (format-struct (sig sig) + ('a' (format '{}' (slice (sig sig) 0:1))) + ('b' (format '{}' (s (slice (sig sig) 1:3)))) + ) + """) -class UnionLayoutTestCase(TestCase): + +class UnionLayoutTestCase(FHDLTestCase): def test_construct(self): ul = data.UnionLayout({ "a": unsigned(1), @@ -184,8 +197,21 @@ class UnionLayoutTestCase(TestCase): r"\(specified: a, b\)$"): data.UnionLayout({"a": 1, "b": 2}).const(dict(a=1, b=2)) + def test_format(self): + ul = data.UnionLayout({ + "a": unsigned(1), + "b": 2 + }) + sig = Signal(ul) + self.assertRepr(ul.format(sig, ""), """ + (format-struct (sig sig) + ('a' (format '{}' (slice (sig sig) 0:1))) + ('b' (format '{}' (slice (sig sig) 0:2))) + ) + """) -class ArrayLayoutTestCase(TestCase): + +class ArrayLayoutTestCase(FHDLTestCase): def test_construct(self): al = data.ArrayLayout(unsigned(2), 3) self.assertEqual(al.elem_shape, unsigned(2)) @@ -243,6 +269,48 @@ class ArrayLayoutTestCase(TestCase): r"^Cannot index array layout with 'a'$"): al["a"] + def test_format(self): + al = data.ArrayLayout(unsigned(2), 3) + sig = Signal(al) + self.assertRepr(al.format(sig, ""), """ + (format-array (sig sig) + (format '{}' (slice (sig sig) 0:2)) + (format '{}' (slice (sig sig) 2:4)) + (format '{}' (slice (sig sig) 4:6)) + ) + """) + + def test_format_signed(self): + al = data.ArrayLayout(signed(2), 3) + sig = Signal(al) + self.assertRepr(al.format(sig, ""), """ + (format-array (sig sig) + (format '{}' (s (slice (sig sig) 0:2))) + (format '{}' (s (slice (sig sig) 2:4))) + (format '{}' (s (slice (sig sig) 4:6))) + ) + """) + + def test_format_nested(self): + al = data.ArrayLayout(data.ArrayLayout(unsigned(2), 2), 3) + sig = Signal(al) + self.assertRepr(al.format(sig, ""), """ + (format-array (sig sig) + (format-array (slice (sig sig) 0:4) + (format '{}' (slice (slice (sig sig) 0:4) 0:2)) + (format '{}' (slice (slice (sig sig) 0:4) 2:4)) + ) + (format-array (slice (sig sig) 4:8) + (format '{}' (slice (slice (sig sig) 4:8) 0:2)) + (format '{}' (slice (slice (sig sig) 4:8) 2:4)) + ) + (format-array (slice (sig sig) 8:12) + (format '{}' (slice (slice (sig sig) 8:12) 0:2)) + (format '{}' (slice (slice (sig sig) 8:12) 2:4)) + ) + ) + """) + class FlexibleLayoutTestCase(TestCase): def test_construct(self): @@ -1012,6 +1080,18 @@ class StructTestCase(FHDLTestCase): self.assertRepr(v.b.q.as_value(), "(slice (slice (sig v) 1:9) 4:8)") self.assertRepr(v.b.q.r, "(s (slice (slice (slice (sig v) 1:9) 4:8) 0:2))") self.assertRepr(v.b.q.s, "(s (slice (slice (slice (sig v) 1:9) 4:8) 2:4))") + self.assertRepr(S.format(v, ""), """ + (format-struct (sig v) + ('a' (format '{}' (slice (sig v) 0:1))) + ('b' (format-struct (slice (sig v) 1:9) + ('p' (format '{}' (slice (slice (sig v) 1:9) 0:4))) + ('q' (format-struct (slice (slice (sig v) 1:9) 4:8) + ('r' (format '{}' (s (slice (slice (slice (sig v) 1:9) 4:8) 0:2)))) + ('s' (format '{}' (s (slice (slice (slice (sig v) 1:9) 4:8) 2:4)))) + )) + )) + ) + """) def test_construct_init(self): class S(data.Struct):