# SPDX-License-Identifier: GPL-2.0-or-later OR AGPL-3.0-or-later OR CERN-OHL-S-2.0+
import abc
from typing import Iterable, Tuple, Dict, Generic, Optional, TypeVar, cast
from ..typing import IntFloat, SingleOrMulti, OptSingleOrMulti
from .. import _util
from ..technology import geometry as geo, primitive as prm, technology_ as tch
from . import layout as lay, circuit as ckt
__all__ = ["RoutingGauge", "Library", "StdCellLibrary"]
LibraryType = TypeVar("LibraryType", bound="Library")
class _Cell(Generic[LibraryType]):
"""A cell is an element from a `Library` and represents the building blocks
of a circuit. A cell may contain one or more circuits and one or more layouts.
API Notes:
User supported ways for creating cells is not fixed. Backwards incompatible
changes are still expected.
"""
def __init__(self, *, lib: LibraryType, name: str):
self.lib = lib
self.name = name
self.circuits = ckt._Circuits()
self.layouts = _CellLayouts()
@property
def tech(self):
return self.lib.tech
@property
def cktfab(self):
return self.lib.cktfab
@property
def layoutfab(self):
return self.lib.layoutfab
@property
def circuit(self):
"""The default circuit of the cell;' it's the one with the same name as
the cell"""
try:
return self.circuits[self.name]
except KeyError:
raise ValueError(f"Cell '{self.name}' has no default circuit")
@property
def layout(self):
"""The default layout of the cell;' it's the one with the same name as
the cell"""
try:
return self.layouts[self.name]
except KeyError:
raise ValueError(f"Cell '{self.name}' has no default layout")
def new_circuit(self, *, name: Optional[str]=None):
"""Create a new empty circuit for the cell.
Arguments:
name: the name of the circuit. If not specified the same name as the
cell will be used.
"""
if name is None:
name = self.name
circuit = self.cktfab.new_circuit(name=name)
self.circuits += circuit
return circuit
def new_layout(self, *,
name: Optional[str]=None, boundary: Optional[geo._Rectangular]=None,
):
"""Create a new empty layout for the cell.
Arguments:
name: the name of the circuit. If not specified the same name as the
cell will be used.
boundary: optional boundary for the layout
"""
if name is None:
name = self.name
layout = self.layoutfab.new_layout(boundary=boundary)
self.layouts += _CellLayout(name=name, layout=layout)
return layout
def new_circuitlayouter(self, *,
name: Optional[str]=None, boundary: Optional[geo._Rectangular]=None,
) -> "lay._CircuitLayouter":
"""Create a circuit layouter for a circuit of the cell.
Arguments:
name: optional name for the circuit
API Notes:
_CircuitLayouter API is not fixed.
see: https://gitlab.com/Chips4Makers/PDKMaster/-/issues/25
"""
if name is None:
name = self.name
circuit = self.circuit
else:
try:
circuit = self.circuits[name]
except KeyError:
raise ValueError(f"circuit with name '{name}' not present")
layouter = self.layoutfab.new_circuitlayouter(
circuit=circuit, boundary=boundary,
)
self.layouts += _CellLayout(name=name, layout=layouter.layout)
return layouter
@property
def subcells_sorted(self):
cells = set()
for circuit in self.circuits:
for cell in circuit.subcells_sorted:
if cell not in cells:
yield cell
cells.add(cell)
class _OnDemandCell(_Cell[LibraryType], abc.ABC, Generic[LibraryType]):
"""_Cell with on demand circuit and layout creation
The circuit and layout will only be generated the first time it is accessed.
"""
@property
def circuit(self):
try:
return self.circuits[self.name]
except KeyError:
self._create_circuit()
try:
return self.circuits[self.name]
except:
raise NotImplementedError(
f"Cell '{self.name}' default circuit generation"
)
@property
def layout(self):
try:
return self.layouts[self.name]
except KeyError:
self._create_layout()
try:
return self.layouts[self.name]
except:
raise NotImplementedError(
f"Cell '{self.name}' default layout generation"
)
@abc.abstractmethod
def _create_circuit(self):
... # pragma: no cover
@abc.abstractmethod
def _create_layout(self):
... # pragma: no cover
class _Cells(_util.TypedListStrMapping[_Cell[LibraryType]], Generic[LibraryType]):
@property
def _elem_type_(self):
return _Cell
class _CellLayout:
def __init__(self, *, name: str, layout: "lay._Layout"):
self.name = name
self.layout = layout
class _CellLayouts(_util.TypedListStrMapping[_CellLayout]):
@property
def _elem_type_(self):
return _CellLayout
def __getitem__(self, item: str) -> "lay._Layout":
elem = super().__getitem__(item)
assert isinstance(elem, _CellLayout)
return elem.layout
[docs]class RoutingGauge:
"""
API Notes:
API for RoutingGause is not fixed. Backwards incompatible changes may still be
expected.
see: https://gitlab.com/Chips4Makers/PDKMaster/-/issues/36
code likely to be moved to c4m-flexcell in the future
"""
directions = frozenset(("horizontal", "vertical"))
def __init__(self, *,
tech: tch.Technology,
bottom: prm.MetalWire, bottom_direction: str, top: prm.MetalWire,
pitches: Dict[prm.MetalWire, IntFloat]={},
offsets: Dict[prm.MetalWire, IntFloat]={},
):
self.tech = tech
metals = tuple(tech.primitives.__iter_type__(prm.MetalWire))
if bottom not in metals:
raise ValueError(f"bottom is not a MetalWire of technology '{tech.name}'")
if top not in metals:
raise ValueError(f"top is not a MetalWire of technology '{tech.name}'")
bottom_idx = metals.index(bottom)
top_idx = metals.index(top)
if bottom_idx >= top_idx:
raise ValueError("bottom layer has to be below top layer")
self.bottom = bottom
self.top = top
if not bottom_direction in self.directions:
raise ValueError(f"bottom_direction has to be one of {self.directions}")
self.bottom_direction = bottom_direction
for wire, _ in pitches.items():
if not (
(wire in metals)
and (bottom_idx <= metals.index(wire) <= top_idx)
):
raise ValueError(f"wire '{wire.name}' is not part of the Gauge set")
pitches2: Dict[prm.MetalWire, float] = {
wire: _util.i2f(pitch) for wire, pitch in pitches.items()
}
self.pitches = pitches2
for wire, _ in offsets.items():
if not (
(wire in metals)
and (bottom_idx <= metals.index(wire) <= top_idx)
):
raise ValueError(f"wire '{wire.name}' is not part of the Gauge set")
offsets2: Dict[prm.MetalWire, float] = {
wire: _util.i2f(offset) for wire, offset in offsets.items()
}
self.offsets = offsets2
[docs]class Library:
"""
API Notes:
API with global_nets not None is not fully clear yet. Libraries with
global_nets not None may get backwards incompatible changes in the
future.
"""
def __init__(self, *,
name: str, tech: tch.Technology, cktfab: Optional["ckt.CircuitFactory"]=None,
layoutfab: Optional["lay.LayoutFactory"]=None,
global_nets: OptSingleOrMulti[str].T=None,
):
self.name = name
self.tech = tech
if cktfab is None:
cktfab = ckt.CircuitFactory(tech=tech)
self.cktfab = cktfab
if layoutfab is None:
layoutfab = lay.LayoutFactory(tech=tech)
self.layoutfab = layoutfab
if global_nets is not None:
global_nets2 = cast(Tuple[str, ...], _util.v2t(global_nets))
self.global_nets = frozenset(global_nets2)
else:
self.global_nets = None
self.cells = _Cells[Library]()
[docs] def new_cell(self, *, name: str) -> _Cell["Library"]:
cell = _Cell[Library](lib=self, name=name)
self.cells += cell
return cell
@property
def sorted_cells(self) -> Iterable[_Cell["Library"]]:
cells = set()
for cell in self.cells:
if cell not in cells:
for subcell in cell.subcells_sorted:
if subcell not in cells:
yield subcell
cells.add(subcell)
yield cell
cells.add(cell)
[docs]class StdCellLibrary(Library):
"""
API Notes:
API for StdCellLibrary is not fixed. Backwards incompatible changes may still be
expected.
see: https://gitlab.com/Chips4Makers/PDKMaster/-/issues/36
code likely to be moved to c4m-flexcell in the future
"""
def __init__(self, *,
name: str, tech: tch.Technology, cktfab: Optional["ckt.CircuitFactory"]=None,
layoutfab: Optional["lay.LayoutFactory"]=None,
global_nets: OptSingleOrMulti[str].T=None,
routinggauge: SingleOrMulti[RoutingGauge].T,
pingrid_pitch: IntFloat, row_height: IntFloat,
):
super().__init__(
name=name, tech=tech, cktfab=cktfab, layoutfab=layoutfab,
global_nets=global_nets,
)
self.routinggauge = _util.v2t(routinggauge)
self.pingrid_pitch = _util.i2f(pingrid_pitch)
self.row_height = _util.i2f(row_height)
@property
def sorted_cells(self) -> Iterable[_Cell["StdCellLibrary"]]:
return cast(Iterable[_Cell["StdCellLibrary"]], super().sorted_cells)