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()