Files

196 lines
7.5 KiB
Python

"""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