330 lines
9.5 KiB
Python
330 lines
9.5 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib
|
|
import inspect
|
|
import pkgutil
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
import nazca as nd
|
|
import time
|
|
|
|
import mxpic as mx
|
|
|
|
OUTPUT_GDS = ROOT / "tests" / "build_all_primitives.gds"
|
|
PRIMITIVES_PACKAGE = "mxpic.components.primitives"
|
|
SKIP_CLASSES = {"Route"}
|
|
|
|
|
|
def bootstrap_technology() -> None:
|
|
"""Register broad layer and xsection defaults used by primitive examples."""
|
|
mx.technologies.foundry["CUMEC"]["CUMEC_CSiP130Cu"]()
|
|
mx.technologies.foundry["Silterra"]["EMO1_2ML_CU_Al_RDL"]()
|
|
# Compatibility layer aliases used by older primitive modules.
|
|
alias_layers = {
|
|
"STRIP_COR": (31, 1),
|
|
"STRIP_CLD": (31, 2),
|
|
"STRIP_TRE": (31, 3),
|
|
"SRIB_COR": (32, 1),
|
|
"SRIB_CLD": (32, 2),
|
|
"SRIB_TRE": (32, 3),
|
|
"RIB_COR": (33, 1),
|
|
"RIB_CLD": (33, 2),
|
|
"RIB_TRE": (33, 3),
|
|
"FETCH": (31, 3),
|
|
"METCH": (33, 3),
|
|
"SETCH": (32, 3),
|
|
"FCW_TRE": (31, 3),
|
|
"FECOR": (31, 1),
|
|
"MECOR": (33, 1),
|
|
"FECLD": (31, 2),
|
|
"MECLD": (33, 2),
|
|
"GC_OPEN": (61, 0),
|
|
"PLD": (24, 0),
|
|
"NLD": (23, 0),
|
|
"PW": (22, 0),
|
|
"NW": (21, 0),
|
|
"SA": (128, 60),
|
|
}
|
|
|
|
for alias, layer in alias_layers.items():
|
|
nd.add_layer(name=alias, layer=layer, overwrite=True)
|
|
|
|
xsection_layers = {
|
|
"heater": ["HEATER"],
|
|
"metal": ["METAL"],
|
|
"metal_1": ["UTM"],
|
|
"metal_2": ["UTM2"],
|
|
"via_h2m": ["CT_SI"],
|
|
"via_s2m": ["CT_SI"],
|
|
"p": ["P"],
|
|
"n": ["N"],
|
|
"pp": ["PP"],
|
|
"np": ["NP"],
|
|
"pw": ["PW"],
|
|
"nw": ["NW"],
|
|
"pld": ["PLD"],
|
|
"nld": ["NLD"],
|
|
"sa": ["SA"],
|
|
"sin": ["SiN_Rib_WG"],
|
|
"air_trench": ["OXIDE_FACET"],
|
|
"grating": ["STRIP_COR"],
|
|
}
|
|
|
|
for xsection, layers in xsection_layers.items():
|
|
nd.add_xsection(name=xsection)
|
|
for layer in layers:
|
|
try:
|
|
nd.add_layer2xsection(
|
|
xsection=xsection,
|
|
layer=layer,
|
|
overwrite=True,
|
|
)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def discover_primitive_classes() -> list[type[Any]]:
|
|
package = importlib.import_module(PRIMITIVES_PACKAGE)
|
|
classes: list[type[Any]] = []
|
|
|
|
for module_info in pkgutil.walk_packages(package.__path__, package.__name__ + "."):
|
|
if module_info.ispkg:
|
|
continue
|
|
|
|
module = importlib.import_module(module_info.name)
|
|
for _, cls in inspect.getmembers(module, inspect.isclass):
|
|
if cls.__module__ != module.__name__:
|
|
continue
|
|
if cls.__name__ in SKIP_CLASSES:
|
|
continue
|
|
if "__init__" not in cls.__dict__:
|
|
continue
|
|
classes.append(cls)
|
|
|
|
return classes
|
|
|
|
|
|
def safe_name(prefix: str, index: int, class_name: str) -> str:
|
|
cleaned = re.sub(r"[^A-Za-z0-9_]", "_", class_name)
|
|
return f"{prefix}{index:03d}_{cleaned}"[:32]
|
|
|
|
|
|
def get_cell(device: Any) -> nd.Cell:
|
|
if isinstance(device, nd.Cell):
|
|
return device
|
|
|
|
cell = getattr(device, "cell", None)
|
|
if isinstance(cell, nd.Cell):
|
|
return cell
|
|
|
|
raise TypeError(f"{type(device).__name__} did not expose a nazca Cell through .cell")
|
|
|
|
|
|
def build_fiber_coupler_seed() -> nd.Cell:
|
|
with nd.Cell(name="_seed_fiber_coupler", instantiate=False) as cell:
|
|
straight = nd.strt(length=10, width=0.45, xs="strip").put(0, 0, 0)
|
|
nd.Pin(name="g1", pin=straight.pin["a0"]).put()
|
|
nd.Pin(name="a0", pin=straight.pin["a0"]).put()
|
|
nd.Pin(name="b0", pin=straight.pin["b0"]).put()
|
|
return cell
|
|
|
|
|
|
def build_context() -> dict[str, Any]:
|
|
from mxpic.components.electronics import Vias
|
|
from mxpic.components.primitives.pic.bragg import Bragg
|
|
from mxpic.components.primitives.pic.couplers import MDM, ring_bus_wg
|
|
from mxpic.components.primitives.pic.gratings import Grating_2D_Hole
|
|
from mxpic.components.primitives.pic.rings import AED_ring
|
|
from mxpic.components.primitives.pic.taper import PSR
|
|
|
|
ring = AED_ring(name="_seed_aed_ring")
|
|
bus = ring_bus_wg()
|
|
via_i2m = Vias(
|
|
xs="via_s2m",
|
|
area=[1.0, 1.0],
|
|
sz=[0.25, 0.25],
|
|
spacing=[0.35, 0.35],
|
|
xs_l1="p",
|
|
xs_l2="metal",
|
|
)
|
|
via_h2m = Vias(
|
|
xs="via_h2m",
|
|
area=[1.0, 1.0],
|
|
sz=[0.25, 0.25],
|
|
spacing=[0.35, 0.35],
|
|
xs_l1="heater",
|
|
xs_l2="metal",
|
|
)
|
|
|
|
return {
|
|
"fiber_coupler": build_fiber_coupler_seed(),
|
|
"grating_unit": Grating_2D_Hole(),
|
|
"psr": PSR(name="_seed_psr"),
|
|
"mdm": MDM(name="_seed_mdm"),
|
|
"bragg": Bragg(),
|
|
"crow_ring": ring.cell,
|
|
"crow_bus": bus.cell,
|
|
"crow_w_bus": bus.w,
|
|
"crow_w_ring": [0.45, 0.65],
|
|
"crow_sz_ring": [20, 20],
|
|
"crow_sz_bus": bus.sz,
|
|
"via_h2m": via_h2m,
|
|
"via_i2m": via_i2m,
|
|
}
|
|
|
|
|
|
REQUIRED_VALUES: dict[str, Any] = {
|
|
"A_cp": 10,
|
|
"Brag": lambda context: context["bragg"],
|
|
"MDM": lambda context: context["mdm"],
|
|
"PSR": lambda context: context["psr"],
|
|
"R0": 10,
|
|
"R1": 6,
|
|
"bus": lambda context: context["crow_bus"],
|
|
"dLx": 0,
|
|
"dLy": 10,
|
|
"fiber_coupler": lambda context: context["fiber_coupler"],
|
|
"gap": 0.2,
|
|
"grating_unit": lambda context: context["grating_unit"],
|
|
"number": 4,
|
|
"pitch": 127,
|
|
"r0_rck": 6,
|
|
"r1_rck": 10,
|
|
"r_ring": 10,
|
|
"r_rck": 10,
|
|
"ring": lambda context: context["crow_ring"],
|
|
"sz_bus": lambda context: context["crow_sz_bus"],
|
|
"sz_ring": lambda context: context["crow_sz_ring"],
|
|
"w0": 0.45,
|
|
"w0_ring": 0.35,
|
|
"w0_rck": 0.45,
|
|
"w1": 0.45,
|
|
"w1_ring": 0.55,
|
|
"w1_rck": 0.45,
|
|
"w_bus": 0.45,
|
|
"w_ring": 0.45,
|
|
"w_rck": 0.45,
|
|
}
|
|
|
|
|
|
def class_overrides(
|
|
class_path: str,
|
|
context: dict[str, Any],
|
|
) -> dict[str, Any]:
|
|
grating_arrays = {
|
|
"w_teeth_SiN": [0.5] * 30,
|
|
"gap_teeth_SiN": [0.5] * 30,
|
|
"w_teeth_Si": [0.45] * 30,
|
|
"gap_teeth_Si": [0.45] * 30,
|
|
"layer_Si_teeth": "WG_HM",
|
|
"layer_Si_slab": "WG_HIGHRIB",
|
|
"layer_SiN_etch": "SiN_Rib_WG",
|
|
"layer_SiN_slab": "WG_N",
|
|
}
|
|
|
|
overrides = {
|
|
"mxpic.components.primitives.grating_couplers.GC_SiN_Si_Dual_Layer": grating_arrays,
|
|
"mxpic.components.primitives.pic.couplers.ADC_STD_2x2": {"Rd1": 20},
|
|
"mxpic.components.primitives.pic.gratings.GC_STD_1D": {"sector_gc": False},
|
|
"mxpic.components.primitives.passive.rings.SOCR": {
|
|
"via_h2m": context["via_h2m"],
|
|
"show_pins": False,
|
|
},
|
|
"mxpic.components.primitives.passive.rings.SOCR_Adiabatic": {
|
|
"via_h2m": context["via_h2m"],
|
|
"show_pins": False,
|
|
},
|
|
"mxpic.components.primitives.passive.rings.SOCR_Adiabatic_Cband": {
|
|
"via_h2m": context["via_h2m"],
|
|
"show_pins": False,
|
|
},
|
|
"mxpic.components.primitives.passive.rings.SOCR_Cband": {
|
|
"via_h2m": context["via_h2m"],
|
|
"show_pins": False,
|
|
},
|
|
}
|
|
|
|
return dict(overrides.get(class_path, {}))
|
|
|
|
|
|
def required_value(parameter: str, context: dict[str, Any]) -> Any:
|
|
value = REQUIRED_VALUES[parameter]
|
|
if callable(value):
|
|
return value(context)
|
|
return value
|
|
|
|
|
|
def build_kwargs(cls: type[Any], device_name: str, context: dict[str, Any]) -> dict[str, Any]:
|
|
signature = inspect.signature(cls)
|
|
kwargs: dict[str, Any] = {}
|
|
class_path = f"{cls.__module__}.{cls.__name__}"
|
|
|
|
for parameter_name, parameter in signature.parameters.items():
|
|
if parameter_name == "self":
|
|
continue
|
|
|
|
if parameter_name == "name":
|
|
kwargs[parameter_name] = device_name
|
|
continue
|
|
|
|
if parameter_name == "cell_name":
|
|
kwargs[parameter_name] = device_name
|
|
continue
|
|
|
|
if parameter_name == "via_i2m":
|
|
kwargs[parameter_name] = context["via_i2m"]
|
|
continue
|
|
|
|
if parameter.default is inspect.Parameter.empty:
|
|
kwargs[parameter_name] = required_value(parameter_name, context)
|
|
|
|
kwargs.update(class_overrides(class_path, context))
|
|
return kwargs
|
|
|
|
|
|
def build_top_cell(cell: nd.Cell, top_name: str) -> nd.Cell:
|
|
with nd.Cell(name=top_name, instantiate=False) as top_cell:
|
|
cell.put(0, 0, 0)
|
|
return top_cell
|
|
|
|
|
|
def main() -> None:
|
|
bootstrap_technology()
|
|
context = build_context()
|
|
topcells = []
|
|
failures = []
|
|
|
|
for index, cls in enumerate(discover_primitive_classes(), start=1):
|
|
device_name = safe_name("DEV", index, cls.__name__)
|
|
top_name = safe_name("TOP", index, cls.__name__)
|
|
class_path = f"{cls.__module__}.{cls.__name__}"
|
|
|
|
try:
|
|
kwargs = build_kwargs(cls, device_name, context)
|
|
device = cls(**kwargs)
|
|
topcells.append(build_top_cell(get_cell(device), top_name))
|
|
print(f"built {top_name}: {class_path}")
|
|
except Exception as exc:
|
|
failures.append((class_path, exc))
|
|
print(f"failed {class_path}: {exc}")
|
|
|
|
if failures:
|
|
print("\nPrimitive build failures:")
|
|
for class_path, exc in failures:
|
|
print(f"- {class_path}: {exc}")
|
|
raise RuntimeError(f"{len(failures)} primitive(s) failed to build")
|
|
|
|
nd.export_gds(topcells=topcells, filename=str(OUTPUT_GDS))
|
|
print(f"\nWrote {len(topcells)} top cells to {OUTPUT_GDS}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|