"""Manifest-backed foundry technology registration.""" 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: """Load a technology manifest and register its layers/xsections in Nazca.""" # 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 show_pins: bool = 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, ...] = () 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.layer_names: Set[str] = set() # Nazca registration: make manifest entries available to layout code. self._register_layers() self._register_xsections() 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." ) self._register_layer_name(layer_name, spec) for alias in spec.aliases: self._register_layer_name(alias, spec) 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 _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) + "'." ) 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) 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) + "'." ) 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 nd.add_layer2xsection( xsection=xsection_name, layer=layer_spec.layer, **kwargs, ) @dataclass(frozen=True) class TechnologyLoader: """Lazy manifest reference that creates a Foundry instance when called.""" foundry: str technology: str manifest: str def __call__(self) -> Foundry: """Load and register this technology manifest.""" return Foundry(self.manifest) 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, ) 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