Technolgy file archetecture revised with dictionary input method

This commit is contained in:
=
2026-06-07 17:07:20 +08:00
parent 8a17f1dde0
commit 54d20eb154
163 changed files with 5948 additions and 1297 deletions
+168 -176
View File
@@ -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