Skip to content

Commit 71aaa63

Browse files
authored
Merge pull request #10 from BYUCamachoLab/skandan
Add KLayout extension
2 parents b60e420 + 032398c commit 71aaa63

File tree

4 files changed

+207
-6
lines changed

4 files changed

+207
-6
lines changed

setup.py

+10
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,13 @@ def get_install_requires():
2929
"Programming Language :: Python :: 3.7",
3030
],
3131
)
32+
33+
try:
34+
from gdsfactory.install import _install_to_klayout
35+
import pathlib
36+
cwd = pathlib.Path(__file__).resolve().parent
37+
_install_to_klayout(
38+
src=cwd / "sky130ph" / "klayout", klayout_subdir_name="macros", package_name="sky130ph"
39+
)
40+
except:
41+
pass

sky130ph/components.py

+11-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33

44
import gdsfactory.components as gc
5-
from gdsfactory import Component, cell, get_component
5+
import gdsfactory as gf
6+
from gdsfactory import Component, cell
67
from gdsfactory.snap import snap_to_grid
78
from gdsfactory.types import ComponentSpec
89

@@ -59,8 +60,8 @@ def _dbr_cell(
5960
w1 = snap_to_grid(w1, 2)
6061
w2 = snap_to_grid(w2, 2)
6162
c = Component()
62-
c1 = c << get_component(straight, length=l1, width=w1, cross_section="nitride")
63-
c2 = c << get_component(straight, length=l2, width=w2, cross_section="strip")
63+
c1 = c << gf.get_component(straight, length=l1, width=w1, cross_section="nitride")
64+
c2 = c << gf.get_component(straight, length=l2, width=w2, cross_section="strip")
6465
c2.connect(port="o1", destination=c1.ports["o2"])
6566
c.add_port("o1", port=c1.ports["o1"])
6667
c.add_port("o2", port=c2.ports["o2"])
@@ -87,7 +88,7 @@ def dbr() -> Component:
8788
l2 = snap_to_grid(0.288)
8889
cell = _dbr_cell()
8990
c.add_array(cell, columns=10, rows=1, spacing=(l1 + l2, 100))
90-
starting_rect = c << get_component(
91+
starting_rect = c << gf.get_component(
9192
"straight", length=l2, width=0.65, cross_section="strip"
9293
)
9394
starting_rect.move(starting_rect.center, (-0.144, 0))
@@ -98,7 +99,7 @@ def dbr() -> Component:
9899

99100

100101
@cell
101-
def coupler(gap: float = 0.2, power_ratio: float = 0.5):
102+
def coupler(gap: float = 0.2, power_ratio: float = 0.5) -> Component:
102103
"""Return a symmetric coupler.
103104
104105
.. code::
@@ -116,9 +117,13 @@ def coupler(gap: float = 0.2, power_ratio: float = 0.5):
116117
gap: coupling gap (um) (0.15, 0.2, 0.25, 0.3)
117118
power_ratio: float (0.1, 0.2, 0.3, ... 1)
118119
"""
119-
return gc.coupler(gap, coupler_lengths[power_ratio][gap])
120+
return gc.coupler(gap, coupler_lengths[round(power_ratio, 2)][round(gap, 2)])
120121

121122

122123
if __name__ == "__main__":
124+
cells = {
125+
'dbr': dbr,
126+
'coupler': coupler,
127+
}
123128
c = coupler()
124129
c.show()
+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<klayout-macro>
3+
<description>import_sky130ph_PCells</description>
4+
<version/>
5+
<category>pymacros</category>
6+
<prolog/>
7+
<epilog/>
8+
<doc/>
9+
<autorun>false</autorun>
10+
<autorun-early>false</autorun-early>
11+
<shortcut/>
12+
<show-in-menu>true</show-in-menu>
13+
<group-name/>
14+
<menu-path>sky130ph.begin</menu-path>
15+
<interpreter>python</interpreter>
16+
<dsl-interpreter-name/>
17+
<text>
18+
from typing import Callable
19+
import pya
20+
import sys
21+
import pathlib
22+
import os
23+
from inspect import Parameter, signature, Signature
24+
import json
25+
import numpy as np
26+
27+
config_file = os.path.expanduser(pathlib.Path('~/.gdsfactory/gf-config.json'))
28+
29+
cfg = {}
30+
with open(config_file, 'a+') as j:
31+
j.seek(0)
32+
try:
33+
cfg = json.loads(j.read())
34+
except:
35+
cfg = {}
36+
37+
if "conda-env" not in cfg.keys():
38+
env_dir_str = pya.FileDialog.ask_existing_dir("Select directory of Python environment to link:", "")
39+
40+
if env_dir_str is None:
41+
quit()
42+
43+
cfg["conda-env"] = env_dir_str
44+
45+
json.dump(cfg, j, sort_keys=True, indent=4)
46+
j.close()
47+
48+
env_dir = pathlib.Path(cfg["conda-env"])
49+
50+
if env_dir is None:
51+
quit()
52+
53+
sys.path.append(str(pathlib.Path(f'{env_dir}/site-packages/')))
54+
55+
try:
56+
import flayout as fl
57+
from flayout.pcell import _klayout_type, _validate_parameter, _validate_on_error, copy_tree
58+
import gdsfactory as gf
59+
import sky130ph.components as skyc
60+
except Exception as e:
61+
pya.MessageBox.info('import error', str(e), pya.MessageBox.Ok)
62+
63+
# Create layout for the library
64+
layout = pya.Layout()
65+
66+
# PCell class that creates the PCell
67+
class PCellFactory(pya.PCellDeclarationHelper):
68+
def __init__(self, component) -> None:
69+
"""Create a PCell from a gdsfactory component."""
70+
super().__init__()
71+
self.component = component
72+
self.sig = self._extract_sig(self.component) or {}
73+
self.func_name = self.gdsfactory_to_klayout().name
74+
params = self._pcell_parameters(self.sig, on_error="raise")
75+
self._param_keys = list(params.keys())
76+
self._param_values = []
77+
for name, param in params.items():
78+
# Add the parameter to the PCell
79+
self._param_values.append(
80+
self.param(
81+
name=name,
82+
value_type=_klayout_type(param),
83+
description=name.replace("_", " "),
84+
default=param.default,
85+
)
86+
)
87+
88+
def produce_impl(self):
89+
"""Produce the PCell."""
90+
params = dict(zip(self._param_keys, self._param_values))
91+
cell = self.gdsfactory_to_klayout(**params)
92+
93+
# Add the cell to the layout
94+
copy_tree(cell, self.cell, on_same_name="replace")
95+
self.cell.name = self.func_name
96+
97+
def _pcell_parameters(self, sig: Signature, on_error="ignore"):
98+
"""Get the parameters of a function."""
99+
# NOTE: There could be a better way to do this, than use __signature__.
100+
new_params = {}
101+
102+
if len(sig.parameters) == 0:
103+
return new_params
104+
105+
new_params = {'name': Parameter('name', kind=Parameter.KEYWORD_ONLY, default=self.func_name, annotation=str)}
106+
params = sig.parameters
107+
on_error = _validate_on_error(on_error)
108+
for name, param in params.items():
109+
try:
110+
new_params[name] = _validate_parameter(name, param)
111+
except ValueError:
112+
if on_error == "raise":
113+
raise
114+
return new_params
115+
116+
def _extract_sig(self, component):
117+
"""Extract the signature of a function."""
118+
sig = signature(component[1])
119+
ignore_params = []
120+
params = sig.parameters
121+
122+
for name, param in params.items():
123+
try:
124+
_validate_parameter(name, param)
125+
except:
126+
# Ignore parameters that are not accepted by KLayout
127+
ignore_params.append(name)
128+
129+
ignore_params.append('cross_section')
130+
131+
sig_new = Signature(
132+
[param for name, param in params.items() if name not in ignore_params]
133+
) or {}
134+
return sig_new
135+
136+
def gdsfactory_to_klayout(self, **kwargs):
137+
gf.clear_cache() # Clear cache to be able to reload components without changing the name
138+
139+
# Get the component
140+
print(cells)
141+
c = self.component[1](**kwargs)
142+
c.name = self.component[0]
143+
144+
# Get the cell
145+
top = layout.create_cell(c.name)
146+
polygons = c.get_polygons(True)
147+
for layer, polygons in polygons.items():
148+
layer_idx = layout.layer(*layer)
149+
150+
# Add pya.Polygon for every gdsfactory Polygon
151+
for polygon in polygons:
152+
polygon = np.array(polygon)
153+
polygon = polygon * 1000
154+
points_pya = [pya.Point(*p) for p in polygon]
155+
top.shapes(layer_idx).insert(pya.Polygon(points_pya))
156+
157+
top.name = c.name
158+
top.__doc__ = self.component[1].__doc__.split('\n\n')[0] # Cell description is the first line of the docstring
159+
return top
160+
161+
cells = gf.get_cells(skyc)
162+
cells.pop('_dbr_cell')
163+
cells.pop('get_component')
164+
generic_lib = fl.library(
165+
"Skywater 130 Photonics PDK",
166+
pcells=[],
167+
cells=[],
168+
description="Skywater 130 photonics PCell Library",
169+
)
170+
# Would be nice to add SiEPIC pins to all of these
171+
pb = pya.AbsoluteProgress("Importing sky130ph components", len(cells.keys()))
172+
for i, cell in enumerate(cells.items()):
173+
174+
pb.set(i / len(cells.items()) * 100, True)
175+
176+
try:
177+
# Cell function signature, used by flayout
178+
179+
func = PCellFactory(cell) # Cell function
180+
generic_lib.layout().register_pcell(cell[0], func) # Register the PCell
181+
182+
except Exception as e:
183+
raise e # Ignore components that cannot be converted to PCells
184+
pb.set(100, True)
185+
</text>
186+
</klayout-macro>

sky130ph/klayout/layers.lyp

Whitespace-only changes.

0 commit comments

Comments
 (0)