204 lines
7.3 KiB
Python
204 lines
7.3 KiB
Python
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 <mxpic::Foundry> 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
|