Source code for pdkmaster.design.circuit

# SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-or-later OR CERN-OHL-S-2.0+ OR Apache-2.0
import abc
from typing import Union, Optional, Iterable, Any, overload

from pdkmaster.typing import OptMultiT

from .. import _util
from ..technology import net as _net, primitive as _prm, technology_ as _tch


__all__ = [
    "InstanceT", "InstancesT", "InstanceNetT", "InstanceNetsT",
    "PrimitiveInstanceT", "CellInstanceT",
    "CircuitNetT", "CircuitNetsT",
    "CircuitT", "CircuitsT",
    "CircuitFactory",
]


class _Instance(abc.ABC):
    """Base classes representing an instance in a _Circuit.

    Instances in a circuit are created with the `_Circuit.instantiate()` method.
    Arguments to use are given in the docs of that method.

    Attributes:
        ports: the ports of this instances.
    """
    @abc.abstractmethod
    def __init__(self, *, name: str, ports: "InstanceNetsT"):
        self.name = name
        ports._freeze_()
        self.ports = ports
InstanceT = _Instance


class _InstanceNet(_net._Net):
    """Internal `_Instance` support class"""
    def __init__(self, *, inst: InstanceT, net: _net.NetT):
        super().__init__(net.name)
        self.inst = inst
        self.net = net
        self.full_name = f"{inst.name}.{net.name}"

    def __hash__(self) -> int:
        return hash(self.full_name)

    def __eq__(self, other) -> bool:
        return isinstance(other, _InstanceNet) and ((self.full_name) == other.full_name)
InstanceNetT = _InstanceNet


class _InstanceNets(_util.ListStrMappingOverride[_InstanceNet], _net.Nets):
    """Internal `_Instance` support class"""
    # TODO: Make sure only nets from the same instance are added
    pass
InstanceNetsT = _InstanceNets


class _Instances(_util.ExtendedListStrMapping[_Instance]):
    pass
InstancesT = _Instances


class _PrimitiveInstance(_Instance):
    """Internal `_Instance` support class"""
    def __init__(self, *, name: str, prim: _prm.DevicePrimitiveT, **params: Any):
        self.name = name
        super().__init__(
            name=name, ports=_InstanceNets(
                (_InstanceNet(inst=self, net=port) for port in prim.ports),
            )
        )

        self.prim = prim
        self.params = params
PrimitiveInstanceT = _PrimitiveInstance


class _CellInstance(_Instance):
    """Internal `_Instance` support class"""
    def __init__(self, *,
        name: str, cell: "_cell.Cell", circuitname: Optional[str]=None,
    ):
        self.name = name
        self.cell = cell

        if circuitname is None:
            try:
                circuit = self.cell.circuit
            except AttributeError: # pragma: no cover
                raise TypeError(
                    "no circuitname provided for cell without default circuit"
                )
        else:
            circuit = cell._circuits[circuitname]
        self.circuitname = circuitname
        self.circuit: CircuitT = circuit

        super().__init__(
            name=name, ports=_InstanceNets(
                (_InstanceNet(inst=self, net=port) for port in circuit.ports),
            ),
        )
CellInstanceT = _CellInstance


class _CircuitNet(_net._Net):
    """A net in a `_Circuit` object.
    It needs to be generated with the `_Circuit.new_net()` method.

    Nets in a circuit are created with the `_Circuit.new_net()` method.
    Arguments to use are given in the docs of that method.

    Attributes:
        circuit: the circuit to which this net belongs
        childports: the ports of the instances that are connected by this net
            See `_Circuit.new_net()` docs on how to populate this collection
        external: wether this is an external net; e.g. a port of the circuit
    """
    def __init__(self, *,
        circuit: "CircuitT", name: str, external: bool,
    ):
        super().__init__(name)
        self.circuit = circuit
        self.childports: InstanceNetsT = _InstanceNets()
        self.external = external

    def freeze(self) -> None:
        self.childports._freeze_()
CircuitNetT = _CircuitNet


class _CircuitNets(_util.ListStrMappingOverride[_CircuitNet], _net.Nets):
    """Internal `_Circuit` support class"""
    _elem_type = _CircuitNet
CircuitNetsT = _CircuitNets


class _Circuit:
    """A circuit consists of instances of subelements and nets to connect
    ports of the instances. Nets can be external and nets are then ports
    that can be used in hierarchically instantiated cells.

    New circuits are created with the `CircuitFactory.new_circuit()` method.
    Arguments to use are given in the docs of that method.

    Arguments:
        instances: the instances of this circuit
        nets: the nets of this circuit
        porrts: the ports of this circuit; e.g. the external nets
    """
    def __init__(self, *, name: str, fab: "CircuitFactory"):
        self.name = name
        self.fab = fab

        self.instances: InstancesT = _Instances()
        self.nets: CircuitNetsT = _CircuitNets()
        self.ports: CircuitNetsT = _CircuitNets()

    @overload
    def instantiate(self,
        object_: _prm.DevicePrimitiveT, *, name: str, **params,
    ) -> PrimitiveInstanceT:
        ... # pragma: no cover
    @overload
    def instantiate(self,
        object_: "_cell.Cell", *, name: str, **params,
    ) -> CellInstanceT:
        ... # pragma: no cover
    def instantiate(self, object_: Union[_prm.DevicePrimitiveT, "_cell.Cell"], *,
        name: str, **params,
    ) -> InstanceT:
        """Instantiate an element in a circuit.

        Arguments:
            object_: the element to instantiate
                Currently a `DevicePrimitiveT` object or a `_Cell` object are supported.
                Conductors are not added to the circuit but added to the layout using the
                `add_wire()` method.
            name: name of the instance
                This name can used to access the instance from the `instances`
                attribute.
            params: the params for the instance.
                Currently params are only support when instantiating a `_Primitive`.
                Parametric circuit are currently not supported.
        """
        if isinstance(object_, _prm.DevicePrimitiveT):
            params = object_.cast_params(params)
            inst = _PrimitiveInstance(name=name, prim=object_, **params)
        elif isinstance(object_, _cell.Cell):
            circuitname = params.pop("circuitname", None)
            if params: # pragma: no cover
                raise NotImplementedError("Parametric Circuit instance")
            inst = _CellInstance(name=name, cell=object_, circuitname=circuitname)
        else:
            raise TypeError(
                f"object_ has to be of type '_Primitive' or '_Cell', not {type(object_)}",
            )

        self.instances += inst
        return inst

    def new_net(self, *,
        name: str, external: bool, childports: OptMultiT[_InstanceNet]=None,
    ) -> CircuitNetT:
        """Create a new net in a circuit.

        Arguments:
            name: the name of the net
            external: wether this is an external net; e.g. a port of this circuit
            childports: the ports of instances in this class that are connected
                by this nets.
                A strategy is to first instantiate all elements in a circuit and then
                pass the ports for the nets during circuit generation. Alternative
                is to not specify it during net creation but add ports to `childports`
                attribute when one goes along. Or a combination of both.
        """
        net = _CircuitNet(circuit=self, name=name, external=external)
        self.nets += net
        if external:
            self.ports += net
        if childports:
            net.childports += childports
        return net

    @property
    def subcells_sorted(self) -> Iterable["_cell.Cell"]:
        """Return sorted iterable of the hierarchical cell instantiation.
        The cells will be sorted such that a cell is in the list before a
        cell where it is instantiated.

        Main use of this attribute will be for the `Library.subcells_sorted`
        attribute.
        """
        cells = set()
        for inst in self.instances.__iter_type__(_CellInstance):
            if inst.cell not in cells:
                for subcell in inst.cell.subcells_sorted:
                    if subcell not in cells:
                        yield subcell
                        cells.add(subcell)
                yield inst.cell
                cells.add(inst.cell)

    def net_lookup(self, *, port: "InstanceNetT") -> "CircuitNetT":
        """Look up to which net a instance port belongs.

        Arguments:
            port: the port to look up
        """
        for net in self.nets:
            for childport in net.childports:
                if (childport.inst == port.inst) and (childport.name == port.name):
                    return net
        else:
            raise ValueError(
                f"Net for port {port.name} of instance {port.inst.name} not found",
            )
CircuitT = _Circuit


class _Circuits(_util.ExtendedListStrMapping[_Circuit]):
    pass
CircuitsT = _Circuits


[docs]class CircuitFactory: """The user facing class for creating circuits. This class is also a base class on which own factory classes can be built with specific extensions. Parameters: tech: the technology for which to create circuits. Created circuits may only contain instances from primitives from this technology. API Notes: The contract for making subclasses has not been finaziled. Backwards incompatible changes are still expected for subclasses of this class. """ def __init__(self, *, tech: _tch.Technology): self.tech = tech
[docs] def new_circuit(self, *, name: str) -> CircuitT: """Create a circuit. This method is the user facing API to generate circuits. Returns a `_Circuit` object; see docs for that class on user facing API for that class. """ return _Circuit(name=name, fab=self)
# Imported at end to handle recursive imports from . import cell as _cell