diff --git a/amaranth/lib/io.py b/amaranth/lib/io.py index c1d1e9ede..894f58790 100644 --- a/amaranth/lib/io.py +++ b/amaranth/lib/io.py @@ -488,6 +488,21 @@ def __init__(self, direction, port, *, i_domain=None, o_domain=None): if port.direction is Direction.Output and self.direction is not Direction.Output: raise ValueError(f"Output port cannot be used with {self.direction.name} buffer") + @classmethod + def create_from(cls, buffer): + """Called on classes which inherit from :class:`FFBuffer` but do not + overload the constructor. This method calls the :class:`FFBuffer` + constructor above to create a new instance. + """ + + if not isinstance(buffer, FFBuffer): + raise TypeError(f"'buffer' must be a FFBuffer to create a {cls!r}") + + i_domain = getattr(buffer, '_i_domain', None) + o_domain = getattr(buffer, '_o_domain', None) + + return cls(buffer.direction, buffer.port, i_domain=i_domain, o_domain=o_domain) + @property def port(self): return self._port @@ -619,6 +634,21 @@ def __init__(self, direction, port, *, i_domain=None, o_domain=None): if port.direction is Direction.Output and self.direction is not Direction.Output: raise ValueError(f"Output port cannot be used with {self.direction.name} buffer") + @classmethod + def create_from(cls, buffer): + """Called on classes which inherit from :class:`DDRBuffer` but do not + overload the constructor. This method calls the :class:`DDRBuffer` + constructor to create a new instance. + """ + + if not isinstance(buffer, DDRBuffer): + raise TypeError(f"'buffer' must be a DDRBuffer to create a {cls!r}") + + i_domain = getattr(buffer, '_i_domain', None) + o_domain = getattr(buffer, '_o_domain', None) + + return cls(buffer.direction, buffer.port, i_domain=i_domain, o_domain=o_domain) + @property def port(self): return self._port diff --git a/amaranth/vendor/_altera.py b/amaranth/vendor/_altera.py index 8d043a992..984ef3d39 100644 --- a/amaranth/vendor/_altera.py +++ b/amaranth/vendor/_altera.py @@ -475,9 +475,9 @@ def get_io_buffer(self, buffer): if isinstance(buffer, io.Buffer): result = IOBuffer(buffer.direction, buffer.port) elif isinstance(buffer, io.FFBuffer): - result = FFBuffer(buffer.direction, buffer.port) + result = FFBuffer.create_from(buffer) elif isinstance(buffer, io.DDRBuffer): - result = DDRBuffer(buffer.direction, buffer.port) + result = DDRBuffer.create_from(buffer) 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 17d7d73d8..741600534 100644 --- a/amaranth/vendor/_gowin.py +++ b/amaranth/vendor/_gowin.py @@ -543,9 +543,9 @@ def get_io_buffer(self, buffer): if isinstance(buffer, io.Buffer): result = IOBuffer(buffer.direction, buffer.port) elif isinstance(buffer, io.FFBuffer): - result = FFBuffer(buffer.direction, buffer.port) + result = FFBuffer.create_from(buffer) elif isinstance(buffer, io.DDRBuffer): - result = DDRBuffer(buffer.direction, buffer.port) + result = DDRBuffer.create_from(buffer) 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 43212285d..46a306919 100644 --- a/amaranth/vendor/_lattice.py +++ b/amaranth/vendor/_lattice.py @@ -951,14 +951,14 @@ def get_io_buffer(self, buffer): if isinstance(buffer, io.Buffer): result = IOBuffer(buffer.direction, buffer.port) elif isinstance(buffer, io.FFBuffer): - result = FFBuffer(buffer.direction, buffer.port) + result = FFBuffer.create_from(buffer) elif isinstance(buffer, io.DDRBuffer): if self.family == "ecp5": - result = DDRBufferECP5(buffer.direction, buffer.port) + result = DDRBufferECP5.create_from(buffer) elif self.family == "machxo2": - result = DDRBufferMachXO2(buffer.direction, buffer.port) + result = DDRBufferMachXO2.create_from(buffer) elif self.family == "nexus": - result = DDRBufferNexus(buffer.direction, buffer.port) + result = DDRBufferNexus.create_from(buffer) else: raise NotImplementedError # :nocov: else: diff --git a/amaranth/vendor/_xilinx.py b/amaranth/vendor/_xilinx.py index f27f013d9..eae687ef5 100644 --- a/amaranth/vendor/_xilinx.py +++ b/amaranth/vendor/_xilinx.py @@ -1168,16 +1168,16 @@ def get_io_buffer(self, buffer): if isinstance(buffer, io.Buffer): result = IOBuffer(buffer.direction, buffer.port) elif isinstance(buffer, io.FFBuffer): - result = FFBuffer(buffer.direction, buffer.port) + result = FFBuffer.create_from(buffer) elif isinstance(buffer, io.DDRBuffer): if self.family in ("virtex2", "virtex2p", "spartan3"): - result = DDRBufferVirtex2(buffer.direction, buffer.port) + result = DDRBufferVirtex2.create_from(buffer) elif self.family in ("spartan3e", "spartan3a", "spartan3adsp", "spartan6"): - result = DDRBufferSpartan3E(buffer.direction, buffer.port) + result = DDRBufferSpartan3E.create_from(buffer) elif self.family in ("virtex4", "virtex5", "virtex6", "series7"): - result = DDRBufferVirtex4(buffer.direction, buffer.port) + result = DDRBufferVirtex4.create_from(buffer) elif self.family in ("ultrascale", "ultrascaleplus"): - result = DDRBufferUltrascale(buffer.direction, buffer.port) + result = DDRBufferUltrascale.create_from(buffer) else: raise TypeError(f"Family {self.family} doesn't implement DDR buffers") else: diff --git a/tests/test_lib_io.py b/tests/test_lib_io.py index 6c4de8e6d..509cb0447 100644 --- a/tests/test_lib_io.py +++ b/tests/test_lib_io.py @@ -472,6 +472,53 @@ def test_construct_wrong(self): r"^Output buffer doesn't have an input domain$"): FFBuffer("o", port, i_domain="input") + def test_create_from(self): + io = IOPort(4) + port = SingleEndedPort(io) + original_buf = FFBuffer("i", port) + buf = FFBuffer.create_from(original_buf) + self.assertEqual(buf.direction, Direction.Input) + 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 + original_buf = FFBuffer("i", port, i_domain="inp") + buf = FFBuffer.create_from(original_buf) + self.assertEqual(buf.i_domain, "inp") + original_buf = FFBuffer("o", port) + buf = FFBuffer.create_from(original_buf) + self.assertEqual(buf.direction, Direction.Output) + self.assertIs(buf.port, port) + self.assertRepr(buf.signature, "FFBuffer.Signature(Direction.Output, 4).flip()") + self.assertEqual(buf.o_domain, "sync") + with self.assertRaisesRegex(AttributeError, + r"^Output buffer doesn't have an input domain$"): + buf.i_domain + original_buf = FFBuffer("o", port, o_domain="out") + buf = FFBuffer.create_from(original_buf) + self.assertEqual(buf.o_domain, "out") + original_buf = FFBuffer("io", port) + buf = FFBuffer.create_from(original_buf) + self.assertEqual(buf.direction, Direction.Bidir) + self.assertIs(buf.port, port) + self.assertRepr(buf.signature, "FFBuffer.Signature(Direction.Bidir, 4).flip()") + self.assertEqual(buf.i_domain, "sync") + self.assertEqual(buf.o_domain, "sync") + original_buf = FFBuffer("io", port, i_domain="input", o_domain="output") + buf = FFBuffer.create_from(original_buf) + self.assertEqual(buf.i_domain, "input") + self.assertEqual(buf.o_domain, "output") + + def test_create_from_wrong(self): + io = IOPort(4) + port = SingleEndedPort(io) + original_buf = Buffer("i", port) + with self.assertRaisesRegex(TypeError, + r"^'buffer' must be a FFBuffer to create a $"): + FFBuffer.create_from(original_buf) + def test_elaborate(self): io = IOPort(4) @@ -714,6 +761,53 @@ def test_construct_wrong(self): r"^Output buffer doesn't have an input domain$"): DDRBuffer("o", port, i_domain="input") + def test_create_from(self): + io = IOPort(4) + port = SingleEndedPort(io) + original_buf = DDRBuffer("i", port) + buf = DDRBuffer.create_from(original_buf) + self.assertEqual(buf.direction, Direction.Input) + 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 + original_buf = DDRBuffer("i", port, i_domain="inp") + buf = DDRBuffer.create_from(original_buf) + self.assertEqual(buf.i_domain, "inp") + original_buf = DDRBuffer("o", port) + buf = DDRBuffer.create_from(original_buf) + self.assertEqual(buf.direction, Direction.Output) + self.assertIs(buf.port, port) + self.assertRepr(buf.signature, "DDRBuffer.Signature(Direction.Output, 4).flip()") + self.assertEqual(buf.o_domain, "sync") + with self.assertRaisesRegex(AttributeError, + r"^Output buffer doesn't have an input domain$"): + buf.i_domain + original_buf = DDRBuffer("o", port, o_domain="out") + buf = DDRBuffer.create_from(original_buf) + self.assertEqual(buf.o_domain, "out") + original_buf = DDRBuffer("io", port) + buf = DDRBuffer.create_from(original_buf) + self.assertEqual(buf.direction, Direction.Bidir) + self.assertIs(buf.port, port) + self.assertRepr(buf.signature, "DDRBuffer.Signature(Direction.Bidir, 4).flip()") + self.assertEqual(buf.i_domain, "sync") + self.assertEqual(buf.o_domain, "sync") + original_buf = DDRBuffer("io", port, i_domain="input", o_domain="output") + buf = DDRBuffer.create_from(original_buf) + self.assertEqual(buf.i_domain, "input") + self.assertEqual(buf.o_domain, "output") + + def test_create_from_wrong(self): + io = IOPort(4) + port = SingleEndedPort(io) + original_buf = Buffer("i", port) + with self.assertRaisesRegex(TypeError, + r"^'buffer' must be a DDRBuffer to create a $"): + DDRBuffer.create_from(original_buf) + class PinSignatureTestCase(FHDLTestCase): def assertSignatureEqual(self, signature, expected):