circuit.py
1 # SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-or-later OR CERN-OHL-S-2.0+ OR Apache-2.0 2 import abc 3 from typing import Union, Optional, Iterable, Any, overload 4 5 from pdkmaster.typing import OptMultiT 6 7 from .. import _util 8 from ..technology import net as _net, primitive as _prm, technology_ as _tch 9 10 11 __all__ = [ 12 "InstanceT", "InstancesT", "InstanceNetT", "InstanceNetsT", 13 "PrimitiveInstanceT", "CellInstanceT", 14 "CircuitNetT", "CircuitNetsT", 15 "CircuitT", "CircuitsT", 16 "CircuitFactory", 17 ] 18 19 20 class _Instance(abc.ABC): 21 """Base classes representing an instance in a _Circuit. 22 23 Instances in a circuit are created with the `_Circuit.instantiate()` method. 24 Arguments to use are given in the docs of that method. 25 26 Attributes: 27 ports: the ports of this instances. 28 """ 29 @abc.abstractmethod 30 def __init__(self, *, name: str, ports: "InstanceNetsT"): 31 self.name = name 32 ports._freeze_() 33 self.ports = ports 34 InstanceT = _Instance 35 36 37 class _InstanceNet(_net._Net): 38 """Internal `_Instance` support class""" 39 def __init__(self, *, inst: InstanceT, net: _net.NetT): 40 super().__init__(net.name) 41 self.inst = inst 42 self.net = net 43 self.full_name = f"{inst.name}.{net.name}" 44 45 def __hash__(self) -> int: 46 return hash(self.full_name) 47 48 def __eq__(self, other) -> bool: 49 return isinstance(other, _InstanceNet) and ((self.full_name) == other.full_name) 50 InstanceNetT = _InstanceNet 51 52 53 class _InstanceNets(_util.ListStrMappingOverride[_InstanceNet], _net.Nets): 54 """Internal `_Instance` support class""" 55 # TODO: Make sure only nets from the same instance are added 56 pass 57 InstanceNetsT = _InstanceNets 58 59 60 class _Instances(_util.ExtendedListStrMapping[_Instance]): 61 pass 62 InstancesT = _Instances 63 64 65 class _PrimitiveInstance(_Instance): 66 """Internal `_Instance` support class""" 67 def __init__(self, *, name: str, prim: _prm.DevicePrimitiveT, **params: Any): 68 self.name = name 69 super().__init__( 70 name=name, ports=_InstanceNets( 71 (_InstanceNet(inst=self, net=port) for port in prim.ports), 72 ) 73 ) 74 75 self.prim = prim 76 self.params = params 77 PrimitiveInstanceT = _PrimitiveInstance 78 79 80 class _CellInstance(_Instance): 81 """Internal `_Instance` support class""" 82 def __init__(self, *, 83 name: str, cell: "_cell.Cell", circuitname: Optional[str]=None, 84 ): 85 self.name = name 86 self.cell = cell 87 88 if circuitname is None: 89 try: 90 circuit = self.cell.circuit 91 except AttributeError: # pragma: no cover 92 raise TypeError( 93 "no circuitname provided for cell without default circuit" 94 ) 95 else: 96 circuit = cell._circuits[circuitname] 97 self.circuitname = circuitname 98 self.circuit: CircuitT = circuit 99 100 super().__init__( 101 name=name, ports=_InstanceNets( 102 (_InstanceNet(inst=self, net=port) for port in circuit.ports), 103 ), 104 ) 105 CellInstanceT = _CellInstance 106 107 108 class _CircuitNet(_net._Net): 109 """A net in a `_Circuit` object. 110 It needs to be generated with the `_Circuit.new_net()` method. 111 112 Nets in a circuit are created with the `_Circuit.new_net()` method. 113 Arguments to use are given in the docs of that method. 114 115 Attributes: 116 circuit: the circuit to which this net belongs 117 childports: the ports of the instances that are connected by this net 118 See `_Circuit.new_net()` docs on how to populate this collection 119 external: wether this is an external net; e.g. a port of the circuit 120 """ 121 def __init__(self, *, 122 circuit: "CircuitT", name: str, external: bool, 123 ): 124 super().__init__(name) 125 self.circuit = circuit 126 self.childports: InstanceNetsT = _InstanceNets() 127 self.external = external 128 129 def freeze(self) -> None: 130 self.childports._freeze_() 131 CircuitNetT = _CircuitNet 132 133 134 class _CircuitNets(_util.ListStrMappingOverride[_CircuitNet], _net.Nets): 135 """Internal `_Circuit` support class""" 136 _elem_type = _CircuitNet 137 CircuitNetsT = _CircuitNets 138 139 140 class _Circuit: 141 """A circuit consists of instances of subelements and nets to connect 142 ports of the instances. Nets can be external and nets are then ports 143 that can be used in hierarchically instantiated cells. 144 145 New circuits are created with the `CircuitFactory.new_circuit()` method. 146 Arguments to use are given in the docs of that method. 147 148 Arguments: 149 instances: the instances of this circuit 150 nets: the nets of this circuit 151 porrts: the ports of this circuit; e.g. the external nets 152 """ 153 def __init__(self, *, name: str, fab: "CircuitFactory"): 154 self.name = name 155 self.fab = fab 156 157 self.instances: InstancesT = _Instances() 158 self.nets: CircuitNetsT = _CircuitNets() 159 self.ports: CircuitNetsT = _CircuitNets() 160 161 @overload 162 def instantiate(self, 163 object_: _prm.DevicePrimitiveT, *, name: str, **params, 164 ) -> PrimitiveInstanceT: 165 ... # pragma: no cover 166 @overload 167 def instantiate(self, 168 object_: "_cell.Cell", *, name: str, **params, 169 ) -> CellInstanceT: 170 ... # pragma: no cover 171 def instantiate(self, object_: Union[_prm.DevicePrimitiveT, "_cell.Cell"], *, 172 name: str, **params, 173 ) -> InstanceT: 174 """Instantiate an element in a circuit. 175 176 Arguments: 177 object_: the element to instantiate 178 Currently a `DevicePrimitiveT` object or a `_Cell` object are supported. 179 Conductors are not added to the circuit but added to the layout using the 180 `add_wire()` method. 181 name: name of the instance 182 This name can used to access the instance from the `instances` 183 attribute. 184 params: the params for the instance. 185 Currently params are only support when instantiating a `_Primitive`. 186 Parametric circuit are currently not supported. 187 """ 188 if isinstance(object_, _prm.DevicePrimitiveT): 189 params = object_.cast_params(params) 190 inst = _PrimitiveInstance(name=name, prim=object_, **params) 191 elif isinstance(object_, _cell.Cell): 192 circuitname = params.pop("circuitname", None) 193 if params: # pragma: no cover 194 raise NotImplementedError("Parametric Circuit instance") 195 inst = _CellInstance(name=name, cell=object_, circuitname=circuitname) 196 else: 197 raise TypeError( 198 f"object_ has to be of type '_Primitive' or '_Cell', not {type(object_)}", 199 ) 200 201 self.instances += inst 202 return inst 203 204 def new_net(self, *, 205 name: str, external: bool, childports: OptMultiT[_InstanceNet]=None, 206 ) -> CircuitNetT: 207 """Create a new net in a circuit. 208 209 Arguments: 210 name: the name of the net 211 external: wether this is an external net; e.g. a port of this circuit 212 childports: the ports of instances in this class that are connected 213 by this nets. 214 A strategy is to first instantiate all elements in a circuit and then 215 pass the ports for the nets during circuit generation. Alternative 216 is to not specify it during net creation but add ports to `childports` 217 attribute when one goes along. Or a combination of both. 218 """ 219 net = _CircuitNet(circuit=self, name=name, external=external) 220 self.nets += net 221 if external: 222 self.ports += net 223 if childports: 224 net.childports += childports 225 return net 226 227 @property 228 def subcells_sorted(self) -> Iterable["_cell.Cell"]: 229 """Return sorted iterable of the hierarchical cell instantiation. 230 The cells will be sorted such that a cell is in the list before a 231 cell where it is instantiated. 232 233 Main use of this attribute will be for the `Library.subcells_sorted` 234 attribute. 235 """ 236 cells = set() 237 for inst in self.instances.__iter_type__(_CellInstance): 238 if inst.cell not in cells: 239 for subcell in inst.cell.subcells_sorted: 240 if subcell not in cells: 241 yield subcell 242 cells.add(subcell) 243 yield inst.cell 244 cells.add(inst.cell) 245 246 def net_lookup(self, *, port: "InstanceNetT") -> "CircuitNetT": 247 """Look up to which net a instance port belongs. 248 249 Arguments: 250 port: the port to look up 251 """ 252 for net in self.nets: 253 for childport in net.childports: 254 if (childport.inst == port.inst) and (childport.name == port.name): 255 return net 256 else: 257 raise ValueError( 258 f"Net for port {port.name} of instance {port.inst.name} not found", 259 ) 260 CircuitT = _Circuit 261 262 263 class _Circuits(_util.ExtendedListStrMapping[_Circuit]): 264 pass 265 CircuitsT = _Circuits 266 267 268 class CircuitFactory: 269 """The user facing class for creating circuits. This class is also a base 270 class on which own factory classes can be built with specific extensions. 271 272 Parameters: 273 tech: the technology for which to create circuits. Created circuits may 274 only contain instances from primitives from this technology. 275 276 API Notes: 277 The contract for making subclasses has not been finaziled. Backwards 278 incompatible changes are still expected for subclasses of this class. 279 """ 280 def __init__(self, *, tech: _tch.Technology): 281 self.tech = tech 282 283 def new_circuit(self, *, name: str) -> CircuitT: 284 """Create a circuit. 285 286 This method is the user facing API to generate circuits. 287 Returns a `_Circuit` object; see docs for that class on user facing API 288 for that class. 289 """ 290 return _Circuit(name=name, fab=self) 291 292 293 # Imported at end to handle recursive imports 294 from . import cell as _cell