196 lines
7.5 KiB
Python
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
|