Source code for circuitgenome.sizer.shared.loader
"""Load technology parameters and performance specs from YAML config files."""
from __future__ import annotations
from pathlib import Path
import yaml
from .models import GridSpec, MosfetParams, SizingSpec, SpiceLib, TechParams
_BUILTIN_DIR = Path(__file__).parent / "config"
[docs]
def load_spec(path: Path | str) -> SizingSpec:
"""Load a :class:`~.models.SizingSpec` from a YAML file.
Keys that are not ``SizingSpec`` fields are ignored, so a spec file may
carry extra annotations (comments, provenance) without breaking loading.
"""
with open(path) as f:
data = yaml.safe_load(f)
return SizingSpec(
**{k: v for k, v in data.items() if k in SizingSpec.__dataclass_fields__})
[docs]
def load_tech(path: Path | str | None = None) -> TechParams:
"""Load :class:`~.models.TechParams` from a YAML file.
:param path: Path to the technology YAML file, or the short name of a
built-in config (e.g. ``"ptm45"``, ``"generic"``, resolving to the
bundled ``tech_<name>.yaml``). Defaults to the built-in
``tech_generic.yaml`` when ``None``.
:returns: Parsed :class:`~.models.TechParams`.
:raises FileNotFoundError: If the given path does not exist and is not a
built-in config name.
:raises KeyError: If a required field is missing from the YAML.
"""
if path is None:
path = _BUILTIN_DIR / "tech_generic.yaml"
elif not Path(path).exists():
# Not a real path → treat as a built-in config name ("ptm45" → tech_ptm45.yaml).
builtin = _BUILTIN_DIR / f"tech_{Path(path).name}.yaml"
if builtin.exists():
path = builtin
with open(path) as f:
data = yaml.safe_load(f)
def _mosfet(d: dict) -> MosfetParams:
return MosfetParams(
mu_cox=float(d["mu_cox"]),
vth=float(d["vth"]),
lam=float(d["lam"]),
)
def _grid(d: dict, min_key: str = "min", max_key: str = "max") -> GridSpec:
return GridSpec(
min=float(d[min_key]),
max=float(d[max_key]),
step=float(d["step"]),
)
cap_d = data["cap"]
def _resolve(rel: str | None) -> str | None:
if not rel:
return None
p = Path(rel)
if not p.is_absolute():
p = Path(path).parent / p
return str(p)
# Resolve relative to the config file's directory.
spice_model = _resolve(data.get("spice_model"))
gmid_lut = _resolve(data.get("gmid_lut"))
spice_lib = None
lib_d = data.get("spice_lib")
if lib_d:
spice_lib = SpiceLib(
file=_resolve(lib_d["file"]),
corner=str(lib_d.get("corner", "typical")),
design=_resolve(lib_d.get("design")),
corners=[str(c) for c in lib_d.get("corners", [])],
)
device_map = data.get("device_map")
if device_map is not None:
device_map = {str(k): str(v) for k, v in device_map.items()}
device_handle = data.get("device_handle")
if device_handle is not None:
device_handle = {str(k): str(v) for k, v in device_handle.items()}
return TechParams(
name=str(data["name"]),
nmos=_mosfet(data["nmos"]),
pmos=_mosfet(data["pmos"]),
width=_grid(data["width"]),
length=_grid(data["length"]),
cap=GridSpec(
min=float(cap_d["min_pf"]),
max=float(cap_d["max_pf"]),
step=float(cap_d["step_pf"]),
),
spice_model=spice_model,
gmid_lut=gmid_lut,
spice_lib=spice_lib,
device_map=device_map,
device_handle=device_handle,
wl_units=str(data.get("wl_units", "m")),
)