from typing import Any import nazca as nd from .layer_models import LayerSpec, XSectionSpec class Foundry: ## Generall parameters STD_SMWG_WIDTH = 0.45 SLAB_GROWTH = 2 W_METAL_MIN = 1 SPACING_HEATER_MIN = 1 SPACING_METAL_MIN = 1.5 W_HEATER_MIN = 1 W_VIA_H2M = 0.25 SPACING_VIA_H2M = 0.35 ISL_W_MIN = 4 ISL_SP_MIN = 5 LAYERS = {} ROLES = {} ROLE_CANDIDATES = { "strip_core": ("STRIP_COR", "WG_STRIP", "FECOR", "WG_COR", "RIB"), "strip_clad": ("STRIP_CLD", "WG_CLD", "FECLD"), "strip_trench": ("STRIP_TRE", "WG_TRE", "FETCH", "FETCH_TRE"), "rib_core": ("RIB_COR", "MECOR", "SKT_COR", "WG_LOWRIB"), "rib_clad": ("RIB_CLD", "MECLD", "SKT_CLD"), "rib_trench": ("RIB_TRE", "METCH", "SKT_TRE"), "shallow_rib_core": ("SRIB_COR", "SECOR", "FC_COR", "WG_HIGHRIB"), "shallow_rib_clad": ("SRIB_CLD", "SECLD", "FC_CLD"), "heater": ("HEATER", "HTR", "TIN", "MHD"), "metal1": ("METAL", "M1", "M1_DRW", "UTM"), "metal2": ("METAL_2", "M2", "M2_DRW", "UTM2"), "metal3": ("METAL_3", "M3", "RDL_MET"), "pad": ("PAD", "PAD_ELE", "PAD_AL", "BONDPAD", "BOND_PAD", "METPASS"), "pad_open": ("PAD_OPEN", "OPEN", "OX_OPEN", "PASS2"), "via_s2m": ("VIA_S2M", "CT_SI", "PCON", "CS"), "via_h2m": ("VIA_H2M", "PVH", "VIA1"), "via_m2m": ("VIA_M2M", "VIA12", "V1", "RDL_VIA"), "isolation": ("ISL", "DT", "EXCLUSION", "ISOLATION"), "n_implant": ("NLD", "N", "N1", "N2", "NBODY", "NW"), "p_implant": ("PLD", "P", "P1", "P2", "PBODY", "PW"), "np_implant": ("NP", "NPP", "N+", "NCONT"), "pp_implant": ("PP", "PPP", "P+", "PCONT"), "salicide": ("SALICIDE", "SA"), } show_pins = False def __init__(self, layermap: Any=None, roles: Any=None) -> None: self.layermap = dict(layermap or self.LAYERS) self.native_layers = {} self.layer_specs = {} self.aliases = {} self.roles = {} self.xsections = {} self._register_layermap(self.layermap) self.roles.update(self._derive_roles()) self.roles.update(getattr(self, "ROLES", {}) or {}) if roles: self.roles.update(roles) def layer(self, name_or_role): layer_name, spec, from_role = self._resolve_layer_ref(name_or_role) if from_role: return spec.native_name return layer_name def layer_gds(self, name_or_role): return self._resolve_layer_ref(name_or_role)[1].gds def native_name(self, name_or_role): return self._resolve_layer_ref(name_or_role)[1].native_name def add_xsection(self, xsection=None, layers=None, growth=None, growy=None): return self._add_xsection_(xsection=xsection, layers=layers, growth=growth, growy=growy) def _add_xsection_(self, xsection=None, layers=None, growth=None, growy=None): if layers is None: layers = [] if growth is None: growth = [] if len(layers) != len(growth): print("WARNING: In layer growth do not match number of layer") return 0 if xsection is not None: nd.add_xsection(name=xsection) resolved_layers = [] for idx in range(0, len(layers)): layer_ref = self._layer_for_nazca(layers[idx]) resolved_layers.append(layer_ref) nd.add_layer2xsection( xsection=xsection, layer=layer_ref, leftedge=(0.5, growth[idx]), rightedge=(-0.5, -growth[idx]), overwrite=True, growy1=growy, growy2=growy, ) self.xsections[xsection] = XSectionSpec( name=xsection, layers=tuple(resolved_layers), growth=tuple(growth), growy=growy, ) setattr(self, "XS_" + xsection.upper(), xsection) def _register_layermap(self, layermap): for layer_name, layer_value in layermap.items(): self._register_layer_spec(self._normalize_layer_entry(layer_name, layer_value)) def _register_layer_spec(self, spec): existing = self.native_layers.get(spec.native_name) if existing is not None: aliases = tuple(dict.fromkeys(existing.aliases + spec.aliases)) spec = LayerSpec( native_name=spec.native_name, gds=existing.gds, aliases=aliases, description=existing.description or spec.description, ) self.native_layers[spec.native_name] = spec names = (spec.native_name,) + spec.aliases for name in names: self.layer_specs[name] = spec if name != spec.native_name: self.aliases[name] = spec.native_name self._register_nazca_layer(name, spec.gds) self._register_default_xsection(name) setattr(self, "LAYER_" + name, name) setattr(self, "XS_" + name, name.lower()) def _normalize_layer_entry(self, layer_name, layer_value): if isinstance(layer_value, LayerSpec): return layer_value if self._is_legacy_layer_value(layer_value): gds, native_name = layer_value aliases = () if layer_name == native_name else (layer_name,) return LayerSpec( native_name=native_name, gds=self._normalize_gds(gds), aliases=aliases, ) return LayerSpec(native_name=layer_name, gds=self._normalize_gds(layer_value)) def _is_legacy_layer_value(self, layer_value): return ( isinstance(layer_value, tuple) and len(layer_value) == 2 and isinstance(layer_value[1], str) ) def _normalize_gds(self, gds): if isinstance(gds, list): gds = tuple(gds) if isinstance(gds, tuple) and len(gds) == 1: return gds[0] return gds def _register_nazca_layer(self, name, gds): nd.add_layer(name=name, layer=gds, overwrite=True) def _register_default_xsection(self, layer_name): xsection = layer_name.lower() nd.add_xsection(name=xsection) nd.add_layer2xsection(xsection=xsection, layer=layer_name) def _derive_roles(self): roles = {} for role, candidates in self.ROLE_CANDIDATES.items(): for candidate in candidates: if candidate in self.layer_specs: roles[role] = candidate break return roles def _resolve_layer_ref(self, name_or_role): if name_or_role in self.roles: target = self.roles[name_or_role] return self._resolve_layer_name(target, from_role=True) return self._resolve_layer_name(name_or_role, from_role=False) def _resolve_layer_name(self, layer_name, from_role=False): if layer_name not in self.layer_specs: raise KeyError("Layer or role not found in technology: " + str(layer_name)) return layer_name, self.layer_specs[layer_name], from_role def _layer_for_nazca(self, layer_ref): if isinstance(layer_ref, str) and (layer_ref in self.layer_specs or layer_ref in self.roles): return self.layer(layer_ref) return layer_ref