Repository: amaranth-lang/amaranth
Pull request #1312 (2024-04-10 23:49:40)

lib.data: accept `data.Const` in `*.const()`.


Patch:
diff --git a/amaranth/build/plat.py b/amaranth/build/plat.py
index 3a5cd5e..508c9ec 100644
--- a/amaranth/build/plat.py
+++ b/amaranth/build/plat.py
@@ -142,34 +142,15 @@ class Platform(ResourceManager, metaclass=ABCMeta):
         def missing_domain_error(name):
             raise RuntimeError("Missing domain in pin fragment")
 
-        def add_pin_fragment(pin, pin_fragment):
-            pin_fragment = Fragment.get(pin_fragment, self)
-            pin_fragment._propagate_domains(missing_domain_error)
-            pin_fragment = DomainLowerer()(pin_fragment)
-            fragment.add_subfragment(pin_fragment, name=f"pin_{pin.name}")
-
-        for pin, port, attrs, invert in self.iter_single_ended_pins():
-            if pin.dir == "i":
-                add_pin_fragment(pin, self.get_input(pin, port, attrs, invert))
-            if pin.dir == "o":
-                add_pin_fragment(pin, self.get_output(pin, port, attrs, invert))
-            if pin.dir == "oe":
-                add_pin_fragment(pin, self.get_tristate(pin, port, attrs, invert))
-            if pin.dir == "io":
-                add_pin_fragment(pin, self.get_input_output(pin, port, attrs, invert))
-
-        for pin, port, attrs, invert in self.iter_differential_pins():
-            if pin.dir == "i":
-                add_pin_fragment(pin, self.get_diff_input(pin, port, attrs, invert))
-            if pin.dir == "o":
-                add_pin_fragment(pin, self.get_diff_output(pin, port, attrs, invert))
-            if pin.dir == "oe":
-                add_pin_fragment(pin, self.get_diff_tristate(pin, port, attrs, invert))
-            if pin.dir == "io":
-                add_pin_fragment(pin, self.get_diff_input_output(pin, port, attrs, invert))
-
-        fragment = Design(fragment, [], hierarchy=(name,))
-        return self.toolchain_prepare(fragment, name, **kwargs)
+        for pin, port, buffer in self.iter_pins():
+            buffer = Fragment.get(buffer, self)
+            buffer._propagate_domains(missing_domain_error)
+            buffer = DomainLowerer()(buffer)
+            fragment.add_subfragment(buffer, name=f"pin_{pin.name}")
+
+        ports = [(port.name, port, None) for port in self.iter_ports()]
+        design = Design(fragment, ports, hierarchy=(name,))
+        return self.toolchain_prepare(design, name, **kwargs)
 
     @abstractmethod
     def toolchain_prepare(self, fragment, name, **kwargs):
@@ -186,108 +167,6 @@ class Platform(ResourceManager, metaclass=ABCMeta):
         raise NotImplementedError("Platform '{}' does not support programming"
                                   .format(type(self).__name__))
 
-    def _check_feature(self, feature, pin, attrs, valid_xdrs, valid_attrs):
-        if len(valid_xdrs) == 0:
-            raise NotImplementedError("Platform '{}' does not support {}"
-                                      .format(type(self).__name__, feature))
-        elif pin.xdr not in valid_xdrs:
-            raise NotImplementedError("Platform '{}' does not support {} for XDR {}"
-                                      .format(type(self).__name__, feature, pin.xdr))
-
-        if not valid_attrs and attrs:
-            raise NotImplementedError("Platform '{}' does not support attributes for {}"
-                                      .format(type(self).__name__, feature))
-
-    @staticmethod
-    def _invert_if(invert, value):
-        if invert:
-            return ~value
-        else:
-            return value
-
-    def get_input(self, pin, port, attrs, invert):
-        self._check_feature("input", pin, attrs,
-                            valid_xdrs=(0, 1, 2), valid_attrs=True)
-
-        m = Module()
-        if pin.xdr == 0:
-            m.submodules.buf = buf = io.Buffer(io.Direction.Input, port)
-            m.d.comb += pin.i.eq(buf.i)
-        elif pin.xdr == 1:
-            m.domains.input = cd_input = ClockDomain(reset_less=True)
-            m.submodules.buf = buf = io.FFBuffer(io.Direction.Input, port, i_domain="input")
-            m.d.comb += pin.i.eq(buf.i)
-            m.d.comb += cd_input.clk.eq(pin.i_clk)
-        elif pin.xdr == 2:
-            m.domains.input = cd_input = ClockDomain(reset_less=True)
-            m.submodules.buf = buf = io.DDRBuffer(io.Direction.Input, port, i_domain="input")
-            m.d.comb += pin.i0.eq(buf.i[0])
-            m.d.comb += pin.i1.eq(buf.i[1])
-            m.d.comb += cd_input.clk.eq(pin.i_clk)
-        return m
-
-    def get_output(self, pin, port, attrs, invert):
-        self._check_feature("output", pin, attrs,
-                            valid_xdrs=(0, 1, 2), valid_attrs=True)
-
-        m = Module()
-        if pin.xdr == 0:
-            m.submodules.buf = buf = io.Buffer(io.Direction.Output, port)
-            m.d.comb += buf.o.eq(pin.o)
-        elif pin.xdr == 1:
-            m.domains.output = cd_output = ClockDomain(reset_less=True)
-            m.submodules.buf = buf = io.FFBuffer(io.Direction.Output, port, o_domain="output")
-            m.d.comb += buf.o.eq(pin.o)
-            m.d.comb += cd_output.clk.eq(pin.o_clk)
-        elif pin.xdr == 2:
-            m.domains.output = cd_output = ClockDomain(reset_less=True)
-            m.submodules.buf = buf = io.DDRBuffer(io.Direction.Output, port, o_domain="output")
-            m.d.comb += buf.o[0].eq(pin.o0)
-            m.d.comb += buf.o[1].eq(pin.o1)
-            m.d.comb += cd_output.clk.eq(pin.o_clk)
-        if pin.dir == "oe":
-            m.d.comb += buf.oe.eq(pin.oe)
-        return m
-
-    get_tristate = get_output
-
-    def get_input_output(self, pin, port, attrs, invert):
-        self._check_feature("single-ended input/output", pin, attrs,
-                            valid_xdrs=(0, 1, 2), valid_attrs=True)
-
-        m = Module()
-        if pin.xdr == 0:
-            m.submodules.buf = buf = io.Buffer(io.Direction.Bidir, port)
-            m.d.comb += pin.i.eq(buf.i)
-            m.d.comb += buf.o.eq(pin.o)
-            m.d.comb += buf.oe.eq(pin.oe)
-        elif pin.xdr == 1:
-            m.domains.input = cd_input = ClockDomain(reset_less=True)
-            m.domains.output = cd_output = ClockDomain(reset_less=True)
-            m.submodules.buf = buf = io.FFBuffer(io.Direction.Bidir, port, i_domain="input", o_domain="output")
-            m.d.comb += pin.i.eq(buf.i)
-            m.d.comb += buf.o.eq(pin.o)
-            m.d.comb += buf.oe.eq(pin.oe)
-            m.d.comb += cd_input.clk.eq(pin.i_clk)
-            m.d.comb += cd_output.clk.eq(pin.o_clk)
-        elif pin.xdr == 2:
-            m.domains.input = cd_input = ClockDomain(reset_less=True)
-            m.domains.output = cd_output = ClockDomain(reset_less=True)
-            m.submodules.buf = buf = io.DDRBuffer(io.Direction.Bidir, port, i_domain="input", o_domain="output")
-            m.d.comb += pin.i0.eq(buf.i[0])
-            m.d.comb += pin.i1.eq(buf.i[1])
-            m.d.comb += buf.o[0].eq(pin.o0)
-            m.d.comb += buf.o[1].eq(pin.o1)
-            m.d.comb += buf.oe.eq(pin.oe)
-            m.d.comb += cd_input.clk.eq(pin.i_clk)
-            m.d.comb += cd_output.clk.eq(pin.o_clk)
-        return m
-
-    get_diff_input = get_input
-    get_diff_output = get_output
-    get_diff_tristate = get_tristate
-    get_diff_input_output = get_input_output
-
 
 class TemplatedPlatform(Platform):
     toolchain         = property(abstractmethod(lambda: None))
diff --git a/amaranth/build/res.py b/amaranth/build/res.py
index 8686d0c..d800a67 100644
--- a/amaranth/build/res.py
+++ b/amaranth/build/res.py
@@ -1,8 +1,8 @@
 from collections import OrderedDict
 
-from ..hdl._ast import *
-from ..lib.io import *
-from ..lib import wiring
+from ..hdl import *
+from ..hdl._ast import SignalDict
+from ..lib import wiring, io
 
 from .dsl import *
 
@@ -24,6 +24,78 @@ class PortMetadata:
         self.attrs = attrs
 
 
+class PinBuffer(Elaboratable):
+    def __init__(self, pin, port):
+        if pin.xdr not in (0, 1, 2):
+            raise ValueError(f"Unsupported 'xdr' value {pin.xdr}")
+        self.pin = pin
+        self.port = port
+
+    def elaborate(self, platform):
+        m = Module()
+
+        if self.pin.dir == "i":
+            if self.pin.xdr == 0:
+                m.submodules.buf = buf = io.Buffer(io.Direction.Input, self.port)
+                m.d.comb += self.pin.i.eq(buf.i)
+            elif self.pin.xdr == 1:
+                m.domains.input = cd_input = ClockDomain(reset_less=True)
+                m.submodules.buf = buf = io.FFBuffer(io.Direction.Input, self.port, i_domain="input")
+                m.d.comb += self.pin.i.eq(buf.i)
+                m.d.comb += cd_input.clk.eq(self.pin.i_clk)
+            elif self.pin.xdr == 2:
+                m.domains.input = cd_input = ClockDomain(reset_less=True)
+                m.submodules.buf = buf = io.DDRBuffer(io.Direction.Input, self.port, i_domain="input")
+                m.d.comb += self.pin.i0.eq(buf.i[0])
+                m.d.comb += self.pin.i1.eq(buf.i[1])
+                m.d.comb += cd_input.clk.eq(self.pin.i_clk)
+        if self.pin.dir in ("o", "oe"):
+            if self.pin.xdr == 0:
+                m.submodules.buf = buf = io.Buffer(io.Direction.Output, self.port)
+                m.d.comb += buf.o.eq(self.pin.o)
+            elif self.pin.xdr == 1:
+                m.domains.output = cd_output = ClockDomain(reset_less=True)
+                m.submodules.buf = buf = io.FFBuffer(io.Direction.Output, self.port, o_domain="output")
+                m.d.comb += buf.o.eq(self.pin.o)
+                m.d.comb += cd_output.clk.eq(self.pin.o_clk)
+            elif self.pin.xdr == 2:
+                m.domains.output = cd_output = ClockDomain(reset_less=True)
+                m.submodules.buf = buf = io.DDRBuffer(io.Direction.Output, self.port, o_domain="output")
+                m.d.comb += buf.o[0].eq(self.pin.o0)
+                m.d.comb += buf.o[1].eq(self.pin.o1)
+                m.d.comb += cd_output.clk.eq(self.pin.o_clk)
+            if self.pin.dir == "oe":
+                m.d.comb += buf.oe.eq(self.pin.oe)
+        if self.pin.dir == "io":
+            if self.pin.xdr == 0:
+                m.submodules.buf = buf = io.Buffer(io.Direction.Bidir, self.port)
+                m.d.comb += self.pin.i.eq(buf.i)
+                m.d.comb += buf.o.eq(self.pin.o)
+                m.d.comb += buf.oe.eq(self.pin.oe)
+            elif self.pin.xdr == 1:
+                m.domains.input = cd_input = ClockDomain(reset_less=True)
+                m.domains.output = cd_output = ClockDomain(reset_less=True)
+                m.submodules.buf = buf = io.FFBuffer(io.Direction.Bidir, self.port, i_domain="input", o_domain="output")
+                m.d.comb += self.pin.i.eq(buf.i)
+                m.d.comb += buf.o.eq(self.pin.o)
+                m.d.comb += buf.oe.eq(self.pin.oe)
+                m.d.comb += cd_input.clk.eq(self.pin.i_clk)
+                m.d.comb += cd_output.clk.eq(self.pin.o_clk)
+            elif self.pin.xdr == 2:
+                m.domains.input = cd_input = ClockDomain(reset_less=True)
+                m.domains.output = cd_output = ClockDomain(reset_less=True)
+                m.submodules.buf = buf = io.DDRBuffer(io.Direction.Bidir, self.port, i_domain="input", o_domain="output")
+                m.d.comb += self.pin.i0.eq(buf.i[0])
+                m.d.comb += self.pin.i1.eq(buf.i[1])
+                m.d.comb += buf.o[0].eq(self.pin.o0)
+                m.d.comb += buf.o[1].eq(self.pin.o1)
+                m.d.comb += buf.oe.eq(self.pin.oe)
+                m.d.comb += cd_input.clk.eq(self.pin.i_clk)
+                m.d.comb += cd_output.clk.eq(self.pin.o_clk)
+
+        return m
+
+
 class ResourceManager:
     def __init__(self, resources, connectors):
         self.resources  = OrderedDict()
@@ -33,8 +105,11 @@ class ResourceManager:
         self.connectors = OrderedDict()
         self._conn_pins = OrderedDict()
 
-        # Constraint lists
+        # List of all IOPort instances created
         self._ports     = []
+        # List of (pin, port, buffer) pairs for non-dir="-" requests.
+        self._pins      = []
+        # Constraint list
         self._clocks    = SignalDict()
 
         self.add_resources(resources)
@@ -139,11 +214,12 @@ class ResourceManager:
                     direction = phys.dir
                 if isinstance(phys, Pins):
                     phys_names = phys.map_names(self._conn_pins, resource)
-                    io = IOPort(len(phys), name="__".join(path) + "__io", metadata=[
+                    iop = IOPort(len(phys), name="__".join(path) + "__io", metadata=[
                         PortMetadata(name, attrs)
                         for name in phys_names
                     ])
-                    port = SingleEndedPort(io, invert=phys.invert, direction=direction)
+                    self._ports.append(iop)
+                    port = io.SingleEndedPort(iop, invert=phys.invert, direction=direction)
                 if isinstance(phys, DiffPairs):
                     phys_names_p = phys.p.map_names(self._conn_pins, resource)
                     phys_names_n = phys.n.map_names(self._conn_pins, resource)
@@ -156,11 +232,8 @@ class ResourceManager:
                         PortMetadata(name, attrs)
                         for name in phys_names_n
                     ])
-                    port = DifferentialPort(p, n, invert=phys.invert, direction=direction)
-                if dir == "-":
-                    pin = None
-                else:
-                    pin = wiring.flipped(Pin(len(phys), dir, xdr=xdr, path=path))
+                    self._ports += [p, n]
+                    port = io.DifferentialPort(p, n, invert=phys.invert, direction=direction)
 
                 for phys_name in phys_names:
                     if phys_name in self._phys_reqd:
@@ -171,12 +244,16 @@ class ResourceManager:
                                                     ".".join(self._phys_reqd[phys_name])))
                     self._phys_reqd[phys_name] = path
 
-                self._ports.append((resource, pin, port, attrs))
-
-                if pin is not None and resource.clock is not None:
-                    self.add_clock_constraint(pin.i, resource.clock.frequency)
+                if dir == "-":
+                    return port
+                else:
+                    pin = wiring.flipped(io.Pin(len(phys), dir, xdr=xdr, path=path))
+                    buffer = PinBuffer(pin, port)
+                    self._pins.append((pin, port, buffer))
 
-                return pin if pin is not None else port
+                    if resource.clock is not None:
+                        self.add_clock_constraint(pin.i, resource.clock.frequency)
+                    return pin
 
             else:
                 assert False # :nocov:
@@ -188,56 +265,19 @@ class ResourceManager:
         self._requested[resource.name, resource.number] = value
         return value
 
-    def iter_single_ended_pins(self):
-        for res, pin, port, attrs in self._ports:
-            if pin is None:
-                continue
-            if isinstance(res.ios[0], Pins):
-                yield pin, port, attrs, res.ios[0].invert
-
-    def iter_differential_pins(self):
-        for res, pin, port, attrs in self._ports:
-            if pin is None:
-                continue
-            if isinstance(res.ios[0], DiffPairs):
-                yield pin, port, attrs, res.ios[0].invert
-
-    def should_skip_port_component(self, port, attrs, component):
-        return False
+    def iter_pins(self):
+        yield from self._pins
 
     def iter_ports(self):
-        for res, pin, port, attrs in self._ports:
-            if isinstance(res.ios[0], Pins):
-                if not self.should_skip_port_component(port, attrs, "io"):
-                    yield port.io
-            elif isinstance(res.ios[0], DiffPairs):
-                if not self.should_skip_port_component(port, attrs, "p"):
-                    yield port.p
-                if not self.should_skip_port_component(port, attrs, "n"):
-                    yield port.n
-            else:
-                assert False
-
-    def iter_port_constraints(self):
-        for res, pin, port, attrs in self._ports:
-            if isinstance(res.ios[0], Pins):
-                if not self.should_skip_port_component(port, attrs, "io"):
-                    yield port.io.name, res.ios[0].map_names(self._conn_pins, res), attrs
-            elif isinstance(res.ios[0], DiffPairs):
-                if not self.should_skip_port_component(port, attrs, "p"):
-                    yield port.p.name, res.ios[0].p.map_names(self._conn_pins, res), attrs
-                if not self.should_skip_port_component(port, attrs, "n"):
-                    yield port.n.name, res.ios[0].n.map_names(self._conn_pins, res), attrs
-            else:
-                assert False
+        yield from self._ports
 
     def iter_port_constraints_bits(self):
-        for port_name, pin_names, attrs in self.iter_port_constraints():
-            if len(pin_names) == 1:
-                yield port_name, pin_names[0], attrs
+        for port in self._ports:
+            if len(port) == 1:
+                yield port.name, port.metadata[0].name, port.metadata[0].attrs
             else:
-                for bit, pin_name in enumerate(pin_names):
-                    yield f"{port_name}[{bit}]", pin_name, attrs
+                for bit, meta in enumerate(port.metadata):
+                    yield f"{port.name}[{bit}]", meta.name, meta.attrs
 
     def add_clock_constraint(self, clock, frequency):
         if isinstance(clock, ClockSignal):
@@ -267,11 +307,11 @@ class ResourceManager:
         # Constraints on nets with no corresponding input pin (e.g. PLL or SERDES outputs) are not
         # affected.
         pin_i_to_port = SignalDict()
-        for res, pin, port, attrs in self._ports:
+        for pin, port, _fragment in self._pins:
             if hasattr(pin, "i"):
-                if isinstance(res.ios[0], Pins):
+                if isinstance(port, io.SingleEndedPort):
                     pin_i_to_port[pin.i] = port.io
-                elif isinstance(res.ios[0], DiffPairs):
+                elif isinstance(port, io.DifferentialPort):
                     pin_i_to_port[pin.i] = port.p
                 else:
                     assert False
diff --git a/amaranth/lib/coding.py b/amaranth/lib/coding.py
index bacedbd..f7a4b75 100644
--- a/amaranth/lib/coding.py
+++ b/amaranth/lib/coding.py
@@ -1,3 +1,10 @@
+import warnings
+
+warnings.warn("the `amaranth.lib.coding` module will be removed without a replacement; "
+              "copy the module into your project to continue using it",
+              DeprecationWarning, stacklevel=2)
+
+
 from .. import *
 
 
diff --git a/amaranth/lib/data.py b/amaranth/lib/data.py
index aaa0a06..2cebc1b 100644
--- a/amaranth/lib/data.py
+++ b/amaranth/lib/data.py
@@ -208,6 +208,11 @@ class Layout(ShapeCastable, metaclass=ABCMeta):
             an all-zero value and had every field assigned to the corresponding value in the order
             in which they appear in :py:`init`.
         """
+        if isinstance(init, Const):
+            if Layout.cast(init.shape()) != self:
+                raise ValueError(f"Const layout {init.shape()!r} differs from shape layout "
+                                 f"{self!r}")
+            return init
         if init is None:
             iterator = iter(())
         elif isinstance(init, Mapping):
@@ -1139,6 +1144,11 @@ class _AggregateMeta(ShapeCastable, type):
         return super().__call__(cls, target)
 
     def const(cls, init):
+        if isinstance(init, Const):
+            if Layout.cast(init.shape()) != Layout.cast(cls.__layout):
+                raise ValueError(f"Const layout {init.shape()!r} differs from shape layout "
+                                 f"{cls.__layout!r}")
+            return init
         if cls.__layout_cls is UnionLayout:
             if init is not None and len(init) > 1:
                 raise ValueError("Initializer for at most one field can be provided for "
diff --git a/docs/changes.rst b/docs/changes.rst
index c22dadc..c8a5bcb 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -41,6 +41,7 @@ Apply the following changes to code written against Amaranth 0.4 to migrate it t
 * Remove any usage of ``name=`` with assertions, possibly replacing them with custom messages
 * Ensure all elaboratables are subclasses of :class:`Elaboratable`
 * Ensure clock domains aren't used outside the module that defines them, or its submodules; move clock domain definitions upwards in the hierarchy as necessary
+* Remove uses of ``amaranth.lib.coding.*`` by inlining or copying the implementation of the modules.
 
 
 Implemented RFCs
@@ -59,6 +60,7 @@ Implemented RFCs
 .. _RFC 58: https://amaranth-lang.org/rfcs/0058-valuecastable-format.html
 .. _RFC 59: https://amaranth-lang.org/rfcs/0059-no-domain-upwards-propagation.html
 .. _RFC 62: https://amaranth-lang.org/rfcs/0062-memory-data.html
+.. _RFC 63: https://amaranth-lang.org/rfcs/0063-remove-lib-coding.html
 
 * `RFC 17`_: Remove ``log2_int``
 * `RFC 27`_: Testbench processes for the simulator
@@ -71,7 +73,8 @@ Implemented RFCs
 * `RFC 53`_: Low-level I/O primitives
 * `RFC 58`_: Core support for ``ValueCastable`` formatting
 * `RFC 59`_: Get rid of upwards propagation of clock domains
-* `RFC 62`_: The `MemoryData`` class
+* `RFC 62`_: The ``MemoryData`` class
+* `RFC 63`_: Remove ``amaranth.lib.coding``
 
 
 Language changes
@@ -115,6 +118,7 @@ Standard library changes
 * Changed: :meth:`amaranth.lib.wiring.Signature.is_compliant` no longer rejects reset-less signals.
 * Added: :class:`amaranth.lib.io.SingleEndedPort`, :class:`amaranth.lib.io.DifferentialPort`. (`RFC 55`_)
 * Added: :class:`amaranth.lib.io.Buffer`, :class:`amaranth.lib.io.FFBuffer`, :class:`amaranth.lib.io.DDRBuffer`. (`RFC 55`_)
+* Deprecated: :mod:`amaranth.lib.coding`. (`RFC 63`_)
 * Removed: (deprecated in 0.4) :mod:`amaranth.lib.scheduler`. (`RFC 19`_)
 * Removed: (deprecated in 0.4) :class:`amaranth.lib.fifo.FIFOInterface` with ``fwft=False``. (`RFC 20`_)
 * Removed: (deprecated in 0.4) :class:`amaranth.lib.fifo.SyncFIFO` with ``fwft=False``. (`RFC 20`_)


Test patch:
diff --git a/tests/test_build_res.py b/tests/test_build_res.py
index cacf964..c679cb9 100644
--- a/tests/test_build_res.py
+++ b/tests/test_build_res.py
@@ -63,12 +63,15 @@ class ResourceManagerTestCase(FHDLTestCase):
         self.assertEqual(user_led.width, 1)
         self.assertEqual(user_led.dir, "o")
 
-        ports = list(self.cm.iter_ports())
-        self.assertEqual(len(ports), 1)
+        pins = list(self.cm.iter_pins())
+        (pin, port, buffer), = pins
 
-        self.assertEqual(list(self.cm.iter_port_constraints()), [
-            ("user_led_0__io", ["A0"], {})
-        ])
+        self.assertIs(pin, user_led)
+        self.assertEqual(port.io.name, "user_led_0__io")
+        self.assertEqual(port.io.metadata[0].name, "A0")
+        self.assertEqual(port.io.metadata[0].attrs, {})
+        self.assertEqual(port.direction, Direction.Output)
+        self.assertEqual(port.invert, (False,))
 
     def test_request_with_dir(self):
         i2c = self.cm.request("i2c", 0, dir={"sda": "o"})
@@ -79,24 +82,13 @@ class ResourceManagerTestCase(FHDLTestCase):
         i2c = self.cm.request("i2c", 0)
         self.assertEqual(i2c.sda.dir, "io")
 
-        ports = list(self.cm.iter_ports())
-        self.assertEqual(len(ports), 2)
-        scl, sda = ports
-        self.assertEqual(ports[1].name, "i2c_0__sda__io")
-        self.assertEqual(ports[1].width, 1)
-
-        scl_info, sda_info = self.cm.iter_single_ended_pins()
-        self.assertIs(scl_info[0], i2c.scl)
-        self.assertIs(scl_info[1].io, scl)
-        self.assertEqual(scl_info[2], {})
-        self.assertEqual(scl_info[3], False)
-        self.assertIs(sda_info[0], i2c.sda)
-        self.assertIs(sda_info[1].io, sda)
-
-        self.assertEqual(list(self.cm.iter_port_constraints()), [
-            ("i2c_0__scl__io", ["N10"], {}),
-            ("i2c_0__sda__io", ["N11"], {})
-        ])
+        ((scl_pin, scl_port, _), (sda_pin, sda_port, _)) = self.cm.iter_pins()
+        self.assertIs(scl_pin, i2c.scl)
+        self.assertIs(sda_pin, i2c.sda)
+        self.assertEqual(scl_port.io.name, "i2c_0__scl__io")
+        self.assertEqual(scl_port.io.metadata[0].name, "N10")
+        self.assertEqual(sda_port.io.name, "i2c_0__sda__io")
+        self.assertEqual(sda_port.io.metadata[0].name, "N11")
 
     def test_request_diffpairs(self):
         clk100 = self.cm.request("clk100", 0)
@@ -104,25 +96,14 @@ class ResourceManagerTestCase(FHDLTestCase):
         self.assertEqual(clk100.dir, "i")
         self.assertEqual(clk100.width, 1)
 
-        ports = list(self.cm.iter_ports())
-        self.assertEqual(len(ports), 2)
-        p, n = ports
-        self.assertEqual(p.name, "clk100_0__p")
-        self.assertEqual(p.width, clk100.width)
-        self.assertEqual(n.name, "clk100_0__n")
-        self.assertEqual(n.width, clk100.width)
-
-        clk100_info, = self.cm.iter_differential_pins()
-        self.assertIs(clk100_info[0], clk100)
-        self.assertIs(clk100_info[1].p, p)
-        self.assertIs(clk100_info[1].n, n)
-        self.assertEqual(clk100_info[2], {})
-        self.assertEqual(clk100_info[3], False)
-
-        self.assertEqual(list(self.cm.iter_port_constraints()), [
-            ("clk100_0__p", ["H1"], {}),
-            ("clk100_0__n", ["H2"], {}),
-        ])
+        (clk100_pin, clk100_port, _), = self.cm.iter_pins()
+        self.assertIs(clk100_pin, clk100)
+        self.assertEqual(clk100_port.p.name, "clk100_0__p")
+        self.assertEqual(clk100_port.p.width, clk100.width)
+        self.assertEqual(clk100_port.n.name, "clk100_0__n")
+        self.assertEqual(clk100_port.n.width, clk100.width)
+        self.assertEqual(clk100_port.p.metadata[0].name, "H1")
+        self.assertEqual(clk100_port.n.metadata[0].name, "H2")
 
     def test_request_inverted(self):
         new_resources = [
@@ -133,39 +114,27 @@ class ResourceManagerTestCase(FHDLTestCase):
 
         cs = self.cm.request("cs")
         clk = self.cm.request("clk")
-        cs_io, clk_p, clk_n = self.cm.iter_ports()
-
-        cs_info, = self.cm.iter_single_ended_pins()
-        self.assertIs(cs_info[0], cs)
-        self.assertIs(cs_info[1].io, cs_io)
-        self.assertEqual(cs_info[2], {})
-        self.assertEqual(cs_info[3], True)
+        (
+            (cs_pin, cs_port, _),
+            (clk_pin, clk_port, _),
+        ) = self.cm.iter_pins()
 
-        clk_info, = self.cm.iter_differential_pins()
-        self.assertIs(clk_info[0], clk)
-        self.assertIs(clk_info[1].p, clk_p)
-        self.assertIs(clk_info[1].n, clk_n)
-        self.assertEqual(clk_info[2], {})
-        self.assertEqual(clk_info[3], True)
+        self.assertIs(cs_pin, cs)
+        self.assertEqual(cs_port.invert, (True,))
+        self.assertIs(clk_pin, clk)
+        self.assertEqual(clk_port.invert, (True,))
 
     def test_request_raw(self):
         clk50 = self.cm.request("clk50", 0, dir="-")
+        self.assertIsInstance(clk50, SingleEndedPort)
         self.assertIsInstance(clk50.io, IOPort)
 
-        ports = list(self.cm.iter_ports())
-        self.assertEqual(len(ports), 1)
-        self.assertIs(ports[0], clk50.io)
-
     def test_request_raw_diffpairs(self):
         clk100 = self.cm.request("clk100", 0, dir="-")
+        self.assertIsInstance(clk100, DifferentialPort)
         self.assertIsInstance(clk100.p, IOPort)
         self.assertIsInstance(clk100.n, IOPort)
 
-        ports = list(self.cm.iter_ports())
-        self.assertEqual(len(ports), 2)
-        self.assertIs(ports[0], clk100.p)
-        self.assertIs(ports[1], clk100.n)
-
     def test_request_via_connector(self):
         self.cm.add_resources([
             Resource("spi", 0,
@@ -176,12 +145,20 @@ class ResourceManagerTestCase(FHDLTestCase):
             )
         ])
         spi0 = self.cm.request("spi", 0)
-        self.assertEqual(list(self.cm.iter_port_constraints()), [
-            ("spi_0__ss__io",   ["B0"], {}),
-            ("spi_0__clk__io",  ["B1"], {}),
-            ("spi_0__miso__io", ["B2"], {}),
-            ("spi_0__mosi__io", ["B3"], {}),
-        ])
+        (
+            (ss_pin, ss_port, _),
+            (clk_pin, clk_port, _),
+            (miso_pin, miso_port, _),
+            (mosi_pin, mosi_port, _),
+        ) = self.cm.iter_pins()
+        self.assertIs(ss_pin, spi0.ss)
+        self.assertIs(clk_pin, spi0.clk)
+        self.assertIs(miso_pin, spi0.miso)
+        self.assertIs(mosi_pin, spi0.mosi)
+        self.assertEqual(ss_port.io.metadata[0].name, "B0")
+        self.assertEqual(clk_port.io.metadata[0].name, "B1")
+        self.assertEqual(miso_port.io.metadata[0].name, "B2")
+        self.assertEqual(mosi_port.io.metadata[0].name, "B3")
 
     def test_request_via_nested_connector(self):
         new_connectors = [
@@ -197,20 +174,31 @@ class ResourceManagerTestCase(FHDLTestCase):
             )
         ])
         spi0 = self.cm.request("spi", 0)
-        self.assertEqual(list(self.cm.iter_port_constraints()), [
-            ("spi_0__ss__io",   ["B0"], {}),
-            ("spi_0__clk__io",  ["B1"], {}),
-            ("spi_0__miso__io", ["B2"], {}),
-            ("spi_0__mosi__io", ["B3"], {}),
-        ])
+        (
+            (ss_pin, ss_port, _),
+            (clk_pin, clk_port, _),
+            (miso_pin, miso_port, _),
+            (mosi_pin, mosi_port, _),
+        ) = self.cm.iter_pins()
+        self.assertIs(ss_pin, spi0.ss)
+        self.assertIs(clk_pin, spi0.clk)
+        self.assertIs(miso_pin, spi0.miso)
+        self.assertIs(mosi_pin, spi0.mosi)
+        self.assertEqual(ss_port.io.metadata[0].name, "B0")
+        self.assertEqual(clk_port.io.metadata[0].name, "B1")
+        self.assertEqual(miso_port.io.metadata[0].name, "B2")
+        self.assertEqual(mosi_port.io.metadata[0].name, "B3")
 
     def test_request_clock(self):
         clk100 = self.cm.request("clk100", 0)
         clk50 = self.cm.request("clk50", 0, dir="i")
-        clk100_port_p, clk100_port_n, clk50_port = self.cm.iter_ports()
+        (
+            (clk100_pin, clk100_port, _),
+            (clk50_pin, clk50_port, _),
+        ) = self.cm.iter_pins()
         self.assertEqual(list(self.cm.iter_clock_constraints()), [
-            (clk100.i, clk100_port_p, 100e6),
-            (clk50.i, clk50_port, 50e6)
+            (clk100.i, clk100_port.p, 100e6),
+            (clk50.i, clk50_port.io, 50e6)
         ])
 
     def test_add_clock(self):
diff --git a/tests/test_lib_coding.py b/tests/test_lib_coding.py
index ca1da31..45e37d4 100644
--- a/tests/test_lib_coding.py
+++ b/tests/test_lib_coding.py
@@ -1,6 +1,10 @@
+import warnings
+
 from amaranth.hdl import *
 from amaranth.sim import *
-from amaranth.lib.coding import *
+with warnings.catch_warnings():
+    warnings.filterwarnings(action="ignore", category=DeprecationWarning)
+    from amaranth.lib.coding import *
 
 from .utils import *
 
diff --git a/tests/test_lib_data.py b/tests/test_lib_data.py
index 0426053..1dd4399 100644
--- a/tests/test_lib_data.py
+++ b/tests/test_lib_data.py
@@ -387,6 +387,7 @@ class LayoutTestCase(FHDLTestCase):
         })
         self.assertRepr(sl.const(None).as_value(), "(const 3'd0)")
         self.assertRepr(sl.const({"a": 0b1, "b": 0b10}).as_value(), "(const 3'd5)")
+        self.assertRepr(sl.const(sl.const({"a": 0b1, "b": 0b10})).as_value(), "(const 3'd5)")
 
         fl = data.FlexibleLayout(2, {
             "a": data.Field(unsigned(1), 0),
@@ -408,6 +409,10 @@ class LayoutTestCase(FHDLTestCase):
                 r"^Layout constant initializer must be a mapping or a sequence, not "
                 r"<.+?object.+?>$"):
             sl.const(object())
+        sl2 = data.StructLayout({"f": unsigned(2)})
+        with self.assertRaisesRegex(ValueError,
+                r"^Const layout StructLayout.* differs from shape layout StructLayout.*$"):
+            sl2.const(sl.const({}))
 
     def test_const_field_shape_castable(self):
         class CastableFromHex(ShapeCastable):
@@ -1013,6 +1018,21 @@ class StructTestCase(FHDLTestCase):
         self.assertEqual(v2.as_value().init, 0b010011)
         v3 = Signal(S, init=dict(p=0b0011, q=0b00))
         self.assertEqual(v3.as_value().init, 0b000011)
+        v3 = Signal(S, init=S.const({"p": 0b0011, "q": 0b00}))
+        self.assertEqual(v3.as_value().init, 0b000011)
+
+    def test_const_wrong(self):
+        class S(data.Struct):
+            p: 4
+            q: 2 = 1
+
+        class S2(data.Struct):
+            p: 2
+            q: 4
+
+        with self.assertRaisesRegex(ValueError,
+                f"^Const layout StructLayout.* differs from shape layout StructLayout.*$"):
+            S.const(S2.const({"p": 0b11, "q": 0b0000}))
 
     def test_shape_undefined_wrong(self):
         class S(data.Struct):