Skip to content

Commit

Permalink
Fix a lot of bugs in kcell.py
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastian-goeldi committed Aug 29, 2023
1 parent ef38488 commit 1fbf044
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 45 deletions.
89 changes: 89 additions & 0 deletions docs/source/notebooks/04_KCL.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# ---
# jupyter:
# jupytext:
# text_representation:
# extension: .py
# format_name: light
# format_version: '1.5'
# jupytext_version: 1.15.0
# kernelspec:
# display_name: Python 3 (ipykernel)
# language: python
# name: python3
# ---

# # KCLayout / PDK: Using multiple KCLayout objects as PDKs or Libraries of KCells and parametric KCell-Functions

# ## Use multiple KCLayout objects as PDKs/Libraries
#
# KCLayouts can act as PDKs. They can be seamlessly instantiated into each other

# +
import kfactory as kf

kcl_default = kf.kcl
# -

# Empty default KCLayout

kcl_default.kcells

# Create a default straight waveguide in the default KCLayout with dbu==0.001 (1nm grid)
s_default = kf.cells.straight.straight(
width=1, length=10, layer=kcl_default.layer(1, 0)
)

# There is now a a KCell in the KCLayout
kcl_default.kcells

# Control the dbu is still 1nm
kcl_default.dbu

# Create a new KCLayout to simulate pdk (could be package with e.g. `from test_pdk import kcl as pdk` or similar)
kcl2 = kf.KCLayout("TEST_PDK")
# Set the dbu to 0.005 (5nm)
kcl2.dbu = 0.005
kcl2.layout.dbu

# Since it's a new KCLayout, it's empty
kcl2

# Create an parametric KCell-Function for straights on the new pdk
sf2 = kf.cells.dbu.Straight(kcl=kcl2)

# The function hasn't been added to the function yes, so it's still empty
sf2.kcl

# Add it to the pdk factories
kcl2.factories.update({"straight": kf.cells.dbu.Straight(kcl2)})

# Make an instance with
s2 = kcl2.factories["straight"](length=10000, width=200, layer=kcl2.layer(1, 0))
s2.settings

# The default kcl's straight uses 1nm grid and is therefore 1000dbu (1um) high and 10000dbu (10um) wide
print(f"{s_default.bbox().height()=}")
print(f"{s_default.dbbox().height()=}")
print(f"{s_default.bbox().width()=}")
print(f"{s_default.dbbox().width()=}")
# The test pdk uses a 5nm grid, so it will be 200dbu (1um) high and 10000dbu (50um) wide
print(f"{s2.bbox().height()=}")
print(f"{s2.dbbox().height()=}")
print(f"{s2.bbox().width()=}")
print(f"{s2.dbbox().width()=}")

# The ports of the default kcl also have different dbu dimensions, but are the same in um
print(f"{s_default.ports=}")
print(f"{s2.ports=}")
# But in um they are the same
print(f"{[port.d for port in s_default.ports]=}")
print(f"{[port.d for port in s2.ports]=}")

# Both can be instantiated into the same KCell
c = kcl_default.kcell()
si_d = c << s_default
si_2 = c << s2

si_2.connect("o1", si_d, "o2")

c
2 changes: 1 addition & 1 deletion src/kfactory/cells/bezier.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def __call__(
t_stop: end
enclosure: Slab/Exclude definition. [dbu]
"""
c = KCell()
c = self.kcl.kcell()
_length, _height = length, height
pts = bezier_curve(
control_points=[
Expand Down
2 changes: 1 addition & 1 deletion src/kfactory/cells/dbu/straight.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def __call__(
layer: Main layer of the waveguide.
enclosure: Definition of slab/excludes. [dbu]
"""
c = KCell()
c = self.kcl.kcell()

if width // 2 * 2 != width:
raise ValueError("The width (w) must be a multiple of 2 database units")
Expand Down
2 changes: 1 addition & 1 deletion src/kfactory/cells/dbu/taper.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def __call__(
layer: Main layer of the taper.
enclosure: Definition of the slab/exclude.
"""
c = KCell()
c = self.kcl.kcell()

c.shapes(layer).insert(
kdb.Polygon(
Expand Down
125 changes: 83 additions & 42 deletions src/kfactory/kcell.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from hashlib import sha3_512
from pathlib import Path
from tempfile import gettempdir
from typing import TYPE_CHECKING, Any, Literal, TypeAlias, TypeVar, overload
from typing import Any, Literal, TypeAlias, TypeVar, overload

import cachetools.func
import numpy as np
Expand All @@ -41,10 +41,6 @@
)
from .port import rename_clockwise

if TYPE_CHECKING:
# from .pdk import Pdk
from types import ModuleType

T = TypeVar("T")

KCellParams = ParamSpec("KCellParams")
Expand Down Expand Up @@ -220,13 +216,13 @@ def __init__(
if isinstance(other_inst, Instance):
super().__init__(
f'Width mismatch between the ports {inst.cell.name}["{p1.name}"]'
f'and {other_inst.cell.name}["{p2.name}"] ({p1.width}/{p2.width})',
f'and {other_inst.cell.name}["{p2.name}"] ("{p1.width}"/"{p2.width}")',
*args,
)
else:
super().__init__(
f'Width mismatch between the ports {inst.cell.name}["{p1.name}"]'
f' and Port "{p2.name}" ({p1.width}/{p2.width})',
f' and Port "{p2.name}" ("{p1.width}"/"{p2.width}")',
*args,
)

Expand Down Expand Up @@ -258,13 +254,13 @@ def __init__(
if isinstance(other_inst, Instance):
super().__init__(
f'Layer mismatch between the ports {inst.cell.name}["{p1.name}"]'
f' and {other_inst.cell.name}["{p2.name}"] ({l1}/{l2})',
f' and {other_inst.cell.name}["{p2.name}"] ("{l1}"/"{l2}")',
*args,
)
else:
super().__init__(
f'Layer mismatch between the ports {inst.cell.name}["{p1.name}"]'
f' and Port "{p2.name}" ({l1}/{l2})',
f' and Port "{p2.name}" ("{l1}"/"{l2}")',
*args,
)

Expand Down Expand Up @@ -863,7 +859,8 @@ def create_inst(
b: kdb.Vector | None = None,
na: int = 1,
nb: int = 1,
libcell_as_static: bool = True,
libcell_as_static: bool = False,
static_name_separator: str = "__",
) -> Instance:
"""Add an instance of another KCell.
Expand All @@ -882,6 +879,9 @@ def create_inst(
(different KCLayout object), convert it to a static cell. This can cause
name collisions that are automatically resolved by appending $1[..n] on
the newly created cell.
static_name_separator: Stringt to separate the KCLayout name from the cell
name when converting library cells (other KCLayout object than the one
of this KCell) to static cells (copy them into this KCell's KCLayout).
Returns:
The created instance
Expand All @@ -898,8 +898,37 @@ def create_inst(
lib_ci = self.kcl.layout.add_lib_cell(
cell.kcl.library, cell.cell_index()
)
kcell = self.kcl[lib_ci]
for port in cell.ports:
pl = port.layer
_layer = self.kcl.layer(cell.kcl.get_info(pl))
try:
_layer = self.kcl.layers(_layer) # type: ignore[call-arg]
except ValueError:
pass
kcell.create_port(
name=port.name,
dwidth=port.d.width,
dcplx_trans=port.dcplx_trans,
layer=_layer,
)
if libcell_as_static:
ci = self.kcl.convert_cell_to_static(lib_ci)
kcell = self.kcl[ci]
for port in cell.ports:
pl = port.layer
_layer = self.kcl.layer(cell.kcl.get_info(pl))
try:
_layer = self.kcl.layers(_layer) # type: ignore[call-arg]
except ValueError:
pass
kcell.create_port(
name=port.name,
dwidth=port.d.width,
dcplx_trans=port.dcplx_trans,
layer=_layer,
)
kcell.name = cell.kcl.name + static_name_separator + cell.name
else:
ci = lib_ci

Expand Down Expand Up @@ -1622,16 +1651,16 @@ def __init__(

self.library.register(self.name)

def _set_name_and_library(self, name: str) -> None:
self._name = name
self.library.register(name)

@computed_field # type: ignore[misc]
@property
def name(self) -> str:
"""Name of the KCLayout."""
return self._name

@name.setter
def name(self, new_name: str) -> None:
self._name = new_name
self.library.register(new_name)

def kcell(self, name: str | None = None, ports: Ports | None = None) -> KCell:
"""Create a new cell based ont he pdk's layout object."""
return KCell(name=name, kcl=self, ports=ports)
Expand All @@ -1648,23 +1677,21 @@ def __getattr__(self, name): # type: ignore[no-untyped-def]
return self.layout.__getattribute__(name)

def __setattr__(self, name: str, value: Any) -> None:
if name == "_name":
object.__setattr__(self, name, value)
else:
try:
super().__setattr__(name, value)
except AttributeError:
self.layout.__setattr__(name, value)

# if getattr(self.layout, name):
# setattr(self.layout, name, value)
# else:
# super().__setattr__(name, value)
# if name == "_name":
# super().__setattr__(name, value)
# elif hasattr(self.layout, name):
# setattr(self.layout, name, value)
# else:
"""Use a custom setter to automatically set attributes.
If the attribute is not in this object, set it on the
Layout object.
"""
match name:
case "_name":
object.__setattr__(self, name, value)
case "name":
self._set_name_and_library(value)
case _:
if hasattr(super(), name):
super().__setattr__(name, value)
else:
self.layout.__setattr__(name, value)

def layerenum_from_dict(
self, name: str = "LAYER", *, layers: dict[str, tuple[int, int]]
Expand Down Expand Up @@ -2358,6 +2385,19 @@ def width(self, value: float) -> None:
f"({self.width} / {value})!"
)

def __repr__(self) -> str:
"""String representation of port."""
ln = (
self.parent.layer.name
if isinstance(self.parent.layer, LayerEnum)
else self.parent.layer
)
return (
f"Port({'name: ' + self.parent.name if self.parent.name else ''}"
f", width: {self.width}, position: {self.position}, angle: {self.angle}"
f", layer: {ln}, port_type: {self.parent.port_type})"
)


class UMKCell:
"""Make the port able to dynamically give um based info."""
Expand Down Expand Up @@ -2704,25 +2744,26 @@ def connect(
else:
p = self.cell.ports[port]
if p.width != op.width and not allow_width_mismatch:
# The ports are not the same width
raise PortWidthMismatch(
self,
other,
p,
op,
)
elif int(p.layer) != int(op.layer) and not allow_layer_mismatch:
if p.layer != op.layer and not allow_layer_mismatch:
# The ports are not on the same layer
raise PortLayerMismatch(self.cell.kcl, self, other, p, op)
elif p.port_type != op.port_type and not allow_type_mismatch:
if p.port_type != op.port_type and not allow_type_mismatch:
raise PortTypeMismatch(self, other, p, op)
if p._dcplx_trans or op._dcplx_trans:
dconn_trans = kdb.DCplxTrans.M90 if mirror else kdb.DCplxTrans.R180
self._instance.dcplx_trans = (
op.dcplx_trans * dconn_trans * p.dcplx_trans.inverted()
)
else:
if p._dcplx_trans or op._dcplx_trans:
dconn_trans = kdb.DCplxTrans.M90 if mirror else kdb.DCplxTrans.R180
self._instance.dcplx_trans = (
op.dcplx_trans * dconn_trans * p.dcplx_trans.inverted()
)
else:
conn_trans = kdb.Trans.M90 if mirror else kdb.Trans.R180
self._instance.trans = op.trans * conn_trans * p.trans.inverted()
conn_trans = kdb.Trans.M90 if mirror else kdb.Trans.R180
self._instance.trans = op.trans * conn_trans * p.trans.inverted()

@classmethod
def to_yaml(cls, representer, node): # type: ignore[no-untyped-def]
Expand Down

0 comments on commit 1fbf044

Please sign in to comment.