Files
mxpic_forge/mxpic/technologies/Foundry.py
T
2026-06-04 23:21:39 +08:00

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