From 822ddd10f538d3501487c37972a8c674696ddb3e Mon Sep 17 00:00:00 2001 From: Raj Setaluri Date: Thu, 25 Jan 2024 19:48:48 -0800 Subject: [PATCH 1/4] [Circuit] Support mutable IO --- magma/interface.py | 74 +++++++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/magma/interface.py b/magma/interface.py index d14bc8f0f..c8111b330 100644 --- a/magma/interface.py +++ b/magma/interface.py @@ -417,9 +417,9 @@ def make_interface(self): def __add__(self, other): raise NotImplementedError() + @abstractmethod def __iadd__(self, other): - # __iadd__ is explicitly overriden to enforce that it is non-mutating. - return self + other + raise NotImplementedError() def flip(self) -> "IOInterface": raise NotImplementedError() @@ -436,11 +436,9 @@ class IO(IOInterface): # https://www.python.org/dev/peps/pep-0468/. def __init__(self, **kwargs): self._ports = {} - self._decl = [] self._bound = False for name, typ in kwargs.items(): - self.add(name, typ) - self._decl.extend((name, typ)) + self._add(name, typ) @property def ports(self): @@ -454,7 +452,9 @@ def bind(self, defn): self._bound = True def decl(self): - return self._decl + return _flatten( + (name, type(port).flip()) for name, port in self._ports.items() + ) def make_interface(self): decl = self.decl() @@ -462,28 +462,42 @@ def make_interface(self): dct = dict(_io=self, _decl=decl, _initialized=False) return InterfaceKind(name, (_DeclareSingletonInterface,), dct) - def __add__(self, other): - """ - Attempts to combine this IO and @other. Returns a new IO object with the - combined ports, unless: - * @other is not of type IOInterface, in which case a TypeError is - raised + def __add__(self, other: 'IO') -> 'IO': + """Attempts to combine this IO and @other. Returns a new IO object with + the combined ports, unless: + * @other is not of type IO, in which case a TypeError is raised * this or @other has already been bound, in which case an Exception is raised * this and @other have common port names, in which case an Exception is raised """ - if not isinstance(other, IOInterface): - raise TypeError(f"unsupported operand type(s) for +: 'IO' and " - f"'{type(other).__name__}'") + if not isinstance(other, IO): + raise TypeError( + f"unsupported operand type(s) for +: 'IO' and " + f"'{type(other).__name__}'" + ) + if self._bound or other._bound: + raise Exception("Adding bound IO not allowed") + if self._ports.keys() & other._ports.keys(): + raise Exception("Adding IO with duplicate port names not allowed") + return IO(**_dict_from_decl(self.decl() + other.decl())) + + def __iadd__(self, other: 'IO') -> 'IO': + """Attempts to combine this @IO and other in place, with the same + caveats as __add__. + """ + if not isinstance(other, IO): + raise TypeError( + f"unsupported operand type(s) for +: 'IO' and " + f"'{type(other).__name__}'" + ) if self._bound or other._bound: raise Exception("Adding bound IO not allowed") if self._ports.keys() & other._ports.keys(): raise Exception("Adding IO with duplicate port names not allowed") - decl = self._decl + other._decl - return IO(**_dict_from_decl(decl)) + self._ports.update(other._ports) - def add(self, name, typ): + def _add(self, name, typ): if self._bound: raise RuntimeError("Can not add to a bound IO") # Definition port. @@ -505,7 +519,7 @@ def __getattr__(self, key: str): return super().__getattribute__(key) def fields(self): - return _dict_from_decl(self._decl) + return _dict_from_decl(self.decl()) def flip(self): return IO(**{name: T.flip() for name, T in self.fields().items()}) @@ -534,26 +548,32 @@ def inst_ports(self): return self._inst_ports.copy() def decl(self): - return _flatten((name, type(port)) - for name, port in self._ports.items()) + return _flatten( + (name, type(port)) for name, port in self._ports.items() + ) def make_interface(self): decl = self.decl() name = _make_interface_name(decl) - dct = dict(_io=self, _decl=decl, _initialized=False, - _initialized_inst=False) + dct = { + "_io": self, + "_decl": decl, + "_initialized": False, + "_initialized_inst": False, + } return InterfaceKind(name, (_DeclareSingletonInstanceInterface,), dct) - def add(self, name, typ): - super().add(name, typ) + def _add(self, name, typ): + super()._add(name, typ) # Instance port. inst_ref = LazyInstRef(name=name) inst_port = _make_port(typ, inst_ref, flip=False) self._inst_ports[name] = inst_port def __add__(self, other): - raise NotImplementedError(f"Addition operator disallowed on " - f"{cls.__name__}") + raise NotImplementedError( + f"Addition operator disallowed on {cls.__name__}" + ) def flip(self): raise NotImplementedError() From 03d5aa910eb894656eb3002b2293c207f3d02677 Mon Sep 17 00:00:00 2001 From: Raj Setaluri Date: Thu, 25 Jan 2024 19:53:40 -0800 Subject: [PATCH 2/4] [Circuit] Add missing return statement --- magma/interface.py | 1 + 1 file changed, 1 insertion(+) diff --git a/magma/interface.py b/magma/interface.py index c8111b330..214c0f3c1 100644 --- a/magma/interface.py +++ b/magma/interface.py @@ -496,6 +496,7 @@ def __iadd__(self, other: 'IO') -> 'IO': if self._ports.keys() & other._ports.keys(): raise Exception("Adding IO with duplicate port names not allowed") self._ports.update(other._ports) + return self def _add(self, name, typ): if self._bound: From 5189d9be16e88d202b52cd535b077740da4e5ab9 Mon Sep 17 00:00:00 2001 From: Raj Setaluri Date: Thu, 25 Jan 2024 21:18:26 -0800 Subject: [PATCH 3/4] [Circuit] Make IO.add() public --- magma/interface.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/magma/interface.py b/magma/interface.py index 214c0f3c1..9ad0385e0 100644 --- a/magma/interface.py +++ b/magma/interface.py @@ -438,7 +438,7 @@ def __init__(self, **kwargs): self._ports = {} self._bound = False for name, typ in kwargs.items(): - self._add(name, typ) + self.add(name, typ) @property def ports(self): @@ -498,7 +498,7 @@ def __iadd__(self, other: 'IO') -> 'IO': self._ports.update(other._ports) return self - def _add(self, name, typ): + def add(self, name, typ): if self._bound: raise RuntimeError("Can not add to a bound IO") # Definition port. @@ -564,8 +564,8 @@ def make_interface(self): } return InterfaceKind(name, (_DeclareSingletonInstanceInterface,), dct) - def _add(self, name, typ): - super()._add(name, typ) + def add(self, name, typ): + super().add(name, typ) # Instance port. inst_ref = LazyInstRef(name=name) inst_port = _make_port(typ, inst_ref, flip=False) From 5230a5e1b8d9c787e1bb6438d9598ccd047ff92e Mon Sep 17 00:00:00 2001 From: Raj Setaluri Date: Thu, 25 Jan 2024 21:29:32 -0800 Subject: [PATCH 4/4] [Circuit] Add IO mutation test --- tests/test_interface/test_io.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_interface/test_io.py b/tests/test_interface/test_io.py index 939a58356..f4bbc2fd7 100644 --- a/tests/test_interface/test_io.py +++ b/tests/test_interface/test_io.py @@ -102,6 +102,13 @@ def test_add_intersecting_io(caplog): ) +def test_iadd(): + io = m.IO(a=m.In(m.Bit)) + a = io.a + io += m.IO(b=m.In(m.Bit)) + assert io.a is a + + def test_flip(): A = m.Product.from_fields("anon", dict(x=m.In(m.Bit), y=m.Out(m.Bit))) B = m.In(m.Bits[8])