Technolgy file archetecture revised with dictionary input method
This commit is contained in:
+168
-176
@@ -1,203 +1,195 @@
|
||||
from typing import Any
|
||||
import nazca as nd
|
||||
"""Manifest-backed foundry technology registration."""
|
||||
|
||||
from .layer_models import LayerSpec, XSectionSpec
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional, Set, Tuple
|
||||
|
||||
import nazca as nd
|
||||
import yaml
|
||||
|
||||
from .layer_models import LayerSpec, MaterialSpec, XSectionLayerSpec, XSectionSpec
|
||||
from .manifest_loader import TECHNOLOGIES_ROOT, load_technology_manifest
|
||||
|
||||
|
||||
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
|
||||
"""Load a technology manifest and register its layers/xsections in Nazca."""
|
||||
|
||||
LAYERS = {}
|
||||
ROLES = {}
|
||||
# Generic technology defaults used when a manifest does not override them.
|
||||
# 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
|
||||
|
||||
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: bool = False
|
||||
|
||||
show_pins = False
|
||||
def __init__(self, manifest: Optional[str] = None) -> None:
|
||||
"""Create a foundry technology from a YAML manifest path."""
|
||||
self.manifest: Dict[str, Any] = {}
|
||||
self.manifest_path: Optional[Path] = None
|
||||
self.materials: Dict[str, MaterialSpec] = {}
|
||||
self.process: Dict[str, Any] = {}
|
||||
self.defaults: Dict[str, Any] = {}
|
||||
self.routing_types: Tuple[str, ...] = ()
|
||||
|
||||
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 = {}
|
||||
if manifest:
|
||||
# Manifest hydration: YAML becomes typed technology specs.
|
||||
technology_manifest = load_technology_manifest(manifest)
|
||||
self.manifest = dict(technology_manifest.raw)
|
||||
self.manifest_path = technology_manifest.path
|
||||
self.materials = dict(technology_manifest.materials)
|
||||
self.process = dict(self.manifest.get("process", {}))
|
||||
self.defaults = dict(self.manifest.get("defaults", {}))
|
||||
self.routing_types = tuple(self.manifest.get("routing_types", ()))
|
||||
self.layers = dict(technology_manifest.layers)
|
||||
self.xsections = dict(technology_manifest.xsections)
|
||||
for name, value in technology_manifest.constants.items():
|
||||
setattr(self, name, value)
|
||||
else:
|
||||
# Empty fallback keeps Foundry() usable as a neutral registry object.
|
||||
self.layers = {}
|
||||
self.xsections = {}
|
||||
|
||||
self._register_layermap(self.layermap)
|
||||
self.roles.update(self._derive_roles())
|
||||
self.roles.update(getattr(self, "ROLES", {}) or {})
|
||||
self.layer_names: Set[str] = set()
|
||||
|
||||
if roles:
|
||||
self.roles.update(roles)
|
||||
# Nazca registration: make manifest entries available to layout code.
|
||||
self._register_layers()
|
||||
self._register_xsections()
|
||||
|
||||
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 _register_layers(self) -> None:
|
||||
"""Register technology layers and aliases in Nazca."""
|
||||
for layer_name, spec in self.layers.items():
|
||||
if not isinstance(spec, LayerSpec):
|
||||
raise TypeError(
|
||||
"Technology layer '" + str(layer_name) + "' must be a LayerSpec."
|
||||
)
|
||||
if spec.name != layer_name:
|
||||
raise ValueError(
|
||||
"Technology layer key '" + str(layer_name)
|
||||
+ "' must match LayerSpec.name '" + str(spec.name) + "'."
|
||||
)
|
||||
if not isinstance(spec.layer, tuple) or len(spec.layer) != 2:
|
||||
raise TypeError(
|
||||
"Technology layer '" + str(layer_name)
|
||||
+ "' must use a (layer, datatype) tuple."
|
||||
)
|
||||
|
||||
def layer_gds(self, name_or_role):
|
||||
return self._resolve_layer_ref(name_or_role)[1].gds
|
||||
self._register_layer_name(layer_name, spec)
|
||||
for alias in spec.aliases:
|
||||
self._register_layer_name(alias, spec)
|
||||
|
||||
def native_name(self, name_or_role):
|
||||
return self._resolve_layer_ref(name_or_role)[1].native_name
|
||||
def _register_layer_name(self, name: str, spec: LayerSpec) -> None:
|
||||
"""Register one Nazca layer name for a LayerSpec."""
|
||||
self.layer_names.add(name)
|
||||
nd.add_layer(name=name, layer=spec.layer, overwrite=True)
|
||||
setattr(self, "LAYER_" + name, 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 _register_xsections(self) -> None:
|
||||
"""Register technology xsections and their layer mappings in Nazca."""
|
||||
for xsection_name, spec in self.xsections.items():
|
||||
if not isinstance(spec, XSectionSpec):
|
||||
raise TypeError(
|
||||
"Technology xsection '" + str(xsection_name) + "' must be an XSectionSpec."
|
||||
)
|
||||
if spec.name != xsection_name:
|
||||
raise ValueError(
|
||||
"Technology xsection key '" + str(xsection_name)
|
||||
+ "' must match XSectionSpec.name '" + str(spec.name) + "'."
|
||||
)
|
||||
|
||||
def _add_xsection_(self, xsection=None, layers=None, growth=None, growy=None):
|
||||
if layers is None:
|
||||
layers = []
|
||||
if growth is None:
|
||||
growth = []
|
||||
nd.add_xsection(name=xsection_name)
|
||||
for layer_spec in spec.layers:
|
||||
self._register_xsection_layer(xsection_name, layer_spec)
|
||||
setattr(self, "XS_" + xsection_name.upper(), xsection_name)
|
||||
|
||||
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,
|
||||
def _register_xsection_layer(
|
||||
self,
|
||||
xsection_name: str,
|
||||
layer_spec: XSectionLayerSpec,
|
||||
) -> None:
|
||||
"""Register one layer contribution inside a Nazca xsection."""
|
||||
if not isinstance(layer_spec, XSectionLayerSpec):
|
||||
raise TypeError(
|
||||
"Xsection '" + str(xsection_name)
|
||||
+ "' contains a layer entry that is not an XSectionLayerSpec."
|
||||
)
|
||||
if layer_spec.layer not in self.layer_names:
|
||||
raise KeyError(
|
||||
"Xsection '" + str(xsection_name)
|
||||
+ "' references unknown layer '" + str(layer_spec.layer) + "'."
|
||||
)
|
||||
|
||||
self.xsections[xsection] = XSectionSpec(
|
||||
name=xsection,
|
||||
layers=tuple(resolved_layers),
|
||||
growth=tuple(growth),
|
||||
growy=growy,
|
||||
)
|
||||
setattr(self, "XS_" + xsection.upper(), xsection)
|
||||
kwargs = {"overwrite": layer_spec.overwrite}
|
||||
if layer_spec.growx is not None:
|
||||
kwargs["growx"] = layer_spec.growx
|
||||
if layer_spec.growy is not None:
|
||||
kwargs["growy"] = layer_spec.growy
|
||||
if layer_spec.leftedge is not None:
|
||||
kwargs["leftedge"] = layer_spec.leftedge
|
||||
if layer_spec.rightedge is not None:
|
||||
kwargs["rightedge"] = layer_spec.rightedge
|
||||
|
||||
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)
|
||||
nd.add_layer2xsection(
|
||||
xsection=xsection_name,
|
||||
layer=layer_spec.layer,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
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)
|
||||
@dataclass(frozen=True)
|
||||
class TechnologyLoader:
|
||||
"""Lazy manifest reference that creates a Foundry instance when called."""
|
||||
|
||||
def _register_default_xsection(self, layer_name):
|
||||
xsection = layer_name.lower()
|
||||
nd.add_xsection(name=xsection)
|
||||
nd.add_layer2xsection(xsection=xsection, layer=layer_name)
|
||||
foundry: str
|
||||
technology: str
|
||||
manifest: str
|
||||
|
||||
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 __call__(self) -> Foundry:
|
||||
"""Load and register this technology manifest."""
|
||||
return Foundry(self.manifest)
|
||||
|
||||
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 build_foundry_registry(root: Path = TECHNOLOGIES_ROOT) -> Dict[str, Dict[str, TechnologyLoader]]:
|
||||
"""Build a foundry/technology registry from YAML manifests."""
|
||||
registry: Dict[str, Dict[str, TechnologyLoader]] = {}
|
||||
for manifest_path in sorted(root.glob("*/*.yml")):
|
||||
raw = _read_manifest_header(manifest_path)
|
||||
foundry_name = str(raw.get("foundry") or manifest_path.parent.name)
|
||||
technology_name = str(raw.get("technology") or manifest_path.stem)
|
||||
manifest = manifest_path.relative_to(root).as_posix()
|
||||
loader = TechnologyLoader(
|
||||
foundry=foundry_name,
|
||||
technology=technology_name,
|
||||
manifest=manifest,
|
||||
)
|
||||
|
||||
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
|
||||
technology_registry = registry.setdefault(foundry_name, {})
|
||||
_register_technology_loader(technology_registry, technology_name, loader)
|
||||
|
||||
return registry
|
||||
|
||||
|
||||
def _read_manifest_header(manifest_path: Path) -> Dict[str, Any]:
|
||||
"""Read top-level manifest metadata used by the registry."""
|
||||
with manifest_path.open("r", encoding="utf-8") as file:
|
||||
raw = yaml.safe_load(file) or {}
|
||||
if not isinstance(raw, dict):
|
||||
raise TypeError("Technology manifest must contain a YAML mapping.")
|
||||
return raw
|
||||
|
||||
|
||||
def _register_technology_loader(
|
||||
technology_registry: Dict[str, TechnologyLoader],
|
||||
name: str,
|
||||
loader: TechnologyLoader,
|
||||
) -> None:
|
||||
"""Register one technology key in a foundry registry."""
|
||||
existing = technology_registry.get(name)
|
||||
if existing is not None and existing.manifest != loader.manifest:
|
||||
raise ValueError("Duplicate technology key '" + name + "' in foundry registry.")
|
||||
technology_registry[name] = loader
|
||||
|
||||
Reference in New Issue
Block a user