Repository: amaranth-lang/amaranth
Pull request #1348 (2024-05-05 02:45:25)

vendor.*: fix `[io]_domain` propagation when creating buffers.
Also makes `[io]_domain` attributes always 

Having conditionally-present attributes causes more problems than it's worth (see #1347). Just make them contain `None` when irrelevant.

Closes #1347.


Patch:
diff --git a/amaranth/lib/io.py b/amaranth/lib/io.py
index c1d1e9e..2d94bfb 100644
--- a/amaranth/lib/io.py
+++ b/amaranth/lib/io.py
@@ -477,12 +477,16 @@ class FFBuffer(wiring.Component):
         super().__init__(FFBuffer.Signature(direction, len(port)).flip())
         if self.signature.direction is not Direction.Output:
             self._i_domain = i_domain or "sync"
-        elif i_domain is not None:
-            raise ValueError("Output buffer doesn't have an input domain")
+        else:
+            if i_domain is not None:
+                raise ValueError("Output buffer doesn't have an input domain")
+            self._i_domain = None
         if self.signature.direction is not Direction.Input:
             self._o_domain = o_domain or "sync"
-        elif o_domain is not None:
-            raise ValueError("Input buffer doesn't have an output domain")
+        else:
+            if o_domain is not None:
+                raise ValueError("Input buffer doesn't have an output domain")
+            self._o_domain = None
         if port.direction is Direction.Input and self.direction is not Direction.Input:
             raise ValueError(f"Input port cannot be used with {self.direction.name} buffer")
         if port.direction is Direction.Output and self.direction is not Direction.Output:
@@ -498,14 +502,10 @@ class FFBuffer(wiring.Component):
 
     @property
     def i_domain(self):
-        if self.direction is Direction.Output:
-            raise AttributeError("Output buffer doesn't have an input domain")
         return self._i_domain
 
     @property
     def o_domain(self):
-        if self.direction is Direction.Input:
-            raise AttributeError("Input buffer doesn't have an output domain")
         return self._o_domain
 
     def elaborate(self, platform):
@@ -608,12 +608,16 @@ class DDRBuffer(wiring.Component):
         super().__init__(DDRBuffer.Signature(direction, len(port)).flip())
         if self.signature.direction is not Direction.Output:
             self._i_domain = i_domain or "sync"
-        elif i_domain is not None:
-            raise ValueError("Output buffer doesn't have an input domain")
+        else:
+            if i_domain is not None:
+                raise ValueError("Output buffer doesn't have an input domain")
+            self._i_domain = None
         if self.signature.direction is not Direction.Input:
             self._o_domain = o_domain or "sync"
-        elif o_domain is not None:
-            raise ValueError("Input buffer doesn't have an output domain")
+        else:
+            if o_domain is not None:
+                raise ValueError("Input buffer doesn't have an output domain")
+            self._o_domain = None
         if port.direction is Direction.Input and self.direction is not Direction.Input:
             raise ValueError(f"Input port cannot be used with {self.direction.name} buffer")
         if port.direction is Direction.Output and self.direction is not Direction.Output:
@@ -629,14 +633,10 @@ class DDRBuffer(wiring.Component):
 
     @property
     def i_domain(self):
-        if self.direction is Direction.Output:
-            raise AttributeError("Output buffer doesn't have an input domain")
         return self._i_domain
 
     @property
     def o_domain(self):
-        if self.direction is Direction.Input:
-            raise AttributeError("Input buffer doesn't have an output domain")
         return self._o_domain
 
     def elaborate(self, platform):
diff --git a/amaranth/vendor/_altera.py b/amaranth/vendor/_altera.py
index 8d043a9..0b206a5 100644
--- a/amaranth/vendor/_altera.py
+++ b/amaranth/vendor/_altera.py
@@ -475,9 +475,13 @@ class AlteraPlatform(TemplatedPlatform):
         if isinstance(buffer, io.Buffer):
             result = IOBuffer(buffer.direction, buffer.port)
         elif isinstance(buffer, io.FFBuffer):
-            result = FFBuffer(buffer.direction, buffer.port)
+            result = FFBuffer(buffer.direction, buffer.port,
+                              i_domain=buffer.i_domain,
+                              o_domain=buffer.o_domain)
         elif isinstance(buffer, io.DDRBuffer):
-            result = DDRBuffer(buffer.direction, buffer.port)
+            result = DDRBuffer(buffer.direction, buffer.port,
+                               i_domain=buffer.i_domain,
+                               o_domain=buffer.o_domain)
         else:
             raise TypeError(f"Unsupported buffer type {buffer!r}") # :nocov:
         if buffer.direction is not io.Direction.Output:
diff --git a/amaranth/vendor/_gowin.py b/amaranth/vendor/_gowin.py
index 17d7d73..beae85a 100644
--- a/amaranth/vendor/_gowin.py
+++ b/amaranth/vendor/_gowin.py
@@ -543,9 +543,13 @@ class GowinPlatform(TemplatedPlatform):
         if isinstance(buffer, io.Buffer):
             result = IOBuffer(buffer.direction, buffer.port)
         elif isinstance(buffer, io.FFBuffer):
-            result = FFBuffer(buffer.direction, buffer.port)
+            result = FFBuffer(buffer.direction, buffer.port,
+                              i_domain=buffer.i_domain,
+                              o_domain=buffer.o_domain)
         elif isinstance(buffer, io.DDRBuffer):
-            result = DDRBuffer(buffer.direction, buffer.port)
+            result = DDRBuffer(buffer.direction, buffer.port,
+                               i_domain=buffer.i_domain,
+                               o_domain=buffer.o_domain)
         else:
             raise TypeError(f"Unsupported buffer type {buffer!r}") # :nocov:
         if buffer.direction is not io.Direction.Output:
diff --git a/amaranth/vendor/_lattice.py b/amaranth/vendor/_lattice.py
index 4e6125f..94d03d7 100644
--- a/amaranth/vendor/_lattice.py
+++ b/amaranth/vendor/_lattice.py
@@ -998,18 +998,28 @@ class LatticePlatform(TemplatedPlatform):
             result = IOBuffer(buffer.direction, buffer.port)
         elif isinstance(buffer, io.FFBuffer):
             if self.family in ("ecp5", "machxo2"):
-                result = FFBufferECP5(buffer.direction, buffer.port)
+                result = FFBufferECP5(buffer.direction, buffer.port,
+                                      i_domain=buffer.i_domain,
+                                      o_domain=buffer.o_domain)
             elif self.family == "nexus":
-                result = FFBufferNexus(buffer.direction, buffer.port)
+                result = FFBufferNexus(buffer.direction, buffer.port,
+                                       i_domain=buffer.i_domain,
+                                       o_domain=buffer.o_domain)
             else:
                 raise NotImplementedError # :nocov:
         elif isinstance(buffer, io.DDRBuffer):
             if self.family == "ecp5":
-                result = DDRBufferECP5(buffer.direction, buffer.port)
+                result = DDRBufferECP5(buffer.direction, buffer.port,
+                                       i_domain=buffer.i_domain,
+                                       o_domain=buffer.o_domain)
             elif self.family == "machxo2":
-                result = DDRBufferMachXO2(buffer.direction, buffer.port)
+                result = DDRBufferMachXO2(buffer.direction, buffer.port,
+                                          i_domain=buffer.i_domain,
+                                          o_domain=buffer.o_domain)
             elif self.family == "nexus":
-                result = DDRBufferNexus(buffer.direction, buffer.port)
+                result = DDRBufferNexus(buffer.direction, buffer.port,
+                                        i_domain=buffer.i_domain,
+                                        o_domain=buffer.o_domain)
             else:
                 raise NotImplementedError # :nocov:
         else:
diff --git a/amaranth/vendor/_xilinx.py b/amaranth/vendor/_xilinx.py
index f27f013..e762839 100644
--- a/amaranth/vendor/_xilinx.py
+++ b/amaranth/vendor/_xilinx.py
@@ -1168,16 +1168,26 @@ class XilinxPlatform(TemplatedPlatform):
         if isinstance(buffer, io.Buffer):
             result = IOBuffer(buffer.direction, buffer.port)
         elif isinstance(buffer, io.FFBuffer):
-            result = FFBuffer(buffer.direction, buffer.port)
+            result = FFBuffer(buffer.direction, buffer.port,
+                              i_domain=buffer.i_domain,
+                              o_domain=buffer.o_domain)
         elif isinstance(buffer, io.DDRBuffer):
             if self.family in ("virtex2", "virtex2p", "spartan3"):
-                result = DDRBufferVirtex2(buffer.direction, buffer.port)
+                result = DDRBufferVirtex2(buffer.direction, buffer.port,
+                                          i_domain=buffer.i_domain,
+                                          o_domain=buffer.o_domain)
             elif self.family in ("spartan3e", "spartan3a", "spartan3adsp", "spartan6"):
-                result = DDRBufferSpartan3E(buffer.direction, buffer.port)
+                result = DDRBufferSpartan3E(buffer.direction, buffer.port,
+                                            i_domain=buffer.i_domain,
+                                            o_domain=buffer.o_domain)
             elif self.family in ("virtex4", "virtex5", "virtex6", "series7"):
-                result = DDRBufferVirtex4(buffer.direction, buffer.port)
+                result = DDRBufferVirtex4(buffer.direction, buffer.port,
+                                          i_domain=buffer.i_domain,
+                                          o_domain=buffer.o_domain)
             elif self.family in ("ultrascale", "ultrascaleplus"):
-                result = DDRBufferUltrascale(buffer.direction, buffer.port)
+                result = DDRBufferUltrascale(buffer.direction, buffer.port,
+                                             i_domain=buffer.i_domain,
+                                             o_domain=buffer.o_domain)
             else:
                 raise TypeError(f"Family {self.family} doesn't implement DDR buffers")
         else:


Test patch:
diff --git a/tests/test_lib_io.py b/tests/test_lib_io.py
index 6c4de8e..aabf945 100644
--- a/tests/test_lib_io.py
+++ b/tests/test_lib_io.py
@@ -426,19 +426,15 @@ class FFBufferTestCase(FHDLTestCase):
         self.assertIs(buf.port, port)
         self.assertRepr(buf.signature, "FFBuffer.Signature(Direction.Input, 4).flip()")
         self.assertEqual(buf.i_domain, "sync")
-        with self.assertRaisesRegex(AttributeError,
-                r"^Input buffer doesn't have an output domain$"):
-            buf.o_domain
+        self.assertIs(buf.o_domain, None)
         buf = FFBuffer("i", port, i_domain="inp")
         self.assertEqual(buf.i_domain, "inp")
         buf = FFBuffer("o", port)
         self.assertEqual(buf.direction, Direction.Output)
         self.assertIs(buf.port, port)
         self.assertRepr(buf.signature, "FFBuffer.Signature(Direction.Output, 4).flip()")
+        self.assertIs(buf.i_domain, None)
         self.assertEqual(buf.o_domain, "sync")
-        with self.assertRaisesRegex(AttributeError,
-                r"^Output buffer doesn't have an input domain$"):
-            buf.i_domain
         buf = FFBuffer("o", port, o_domain="out")
         self.assertEqual(buf.o_domain, "out")
         buf = FFBuffer("io", port)
@@ -668,19 +664,15 @@ class DDRBufferTestCase(FHDLTestCase):
         self.assertIs(buf.port, port)
         self.assertRepr(buf.signature, "DDRBuffer.Signature(Direction.Input, 4).flip()")
         self.assertEqual(buf.i_domain, "sync")
-        with self.assertRaisesRegex(AttributeError,
-                r"^Input buffer doesn't have an output domain$"):
-            buf.o_domain
+        self.assertIs(buf.o_domain, None)
         buf = DDRBuffer("i", port, i_domain="inp")
         self.assertEqual(buf.i_domain, "inp")
         buf = DDRBuffer("o", port)
         self.assertEqual(buf.direction, Direction.Output)
         self.assertIs(buf.port, port)
         self.assertRepr(buf.signature, "DDRBuffer.Signature(Direction.Output, 4).flip()")
+        self.assertIs(buf.i_domain, None)
         self.assertEqual(buf.o_domain, "sync")
-        with self.assertRaisesRegex(AttributeError,
-                r"^Output buffer doesn't have an input domain$"):
-            buf.i_domain
         buf = DDRBuffer("o", port, o_domain="out")
         self.assertEqual(buf.o_domain, "out")
         buf = DDRBuffer("io", port)