/ pdkmaster / design / circuit.py
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