Coverage for klayout_pex/schematic_devices.py: 100%
40 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-06 13:14 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-06 13:14 +0000
1#
2# --------------------------------------------------------------------------------
3# SPDX-FileCopyrightText: 2024-2026 Martin Jan Köhler and Harald Pretl
4# Johannes Kepler University, Institute for Integrated Circuits.
5#
6# This file is part of KPEX
7# (see https://github.com/iic-jku/klayout-pex).
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21# SPDX-License-Identifier: GPL-3.0-or-later
22# --------------------------------------------------------------------------------
23#
25"""
26The devices of a schematic, in the LVS device classes of the extracted netlist (#345)
27"""
29from __future__ import annotations
31from collections import defaultdict
32from dataclasses import dataclass, field
33from itertools import chain
34from typing import *
36from .device_models import DeviceModels
37from .device_totals import DeviceTotalParameters
38from .netlist.spice_hierarchy import SpiceHierarchy
39from .netlist.spice_netlist import SpiceElement, SpiceNetlist
40from .types import LVSDeviceClassName
43@dataclass
44class SchematicDevice:
45 name: str # e.g. X1.XM2
47 # by the device model mapping in reverse, e.g. {'W': 1.0, 'L': 0.13} (µm) of an IHP MOS with w=1u l=0.13u
48 lvs_parameters: Dict[str, float] = field(default_factory=dict)
50 m: float = 1.0 # the multiplier of SPICE
53@dataclass
54class SchematicDevices:
55 # e.g. {'sg13_lv_nmos': [SchematicDevice('X1.XM1', ...), ...]}
56 devices_by_class_name: Dict[LVSDeviceClassName, List[SchematicDevice]] = field(default_factory=dict)
58 # the devices of models that no device model mapping writes, by model,
59 # e.g. a device without a model mapping, or a call of a subcircuit the schematic doesn't define
60 device_names_by_unknown_model: Dict[str, List[str]] = field(default_factory=dict)
62 @property
63 def device_names_by_class_name(self) -> Dict[LVSDeviceClassName, List[str]]:
64 return {c: [d.name for d in devices] for c, devices in self.devices_by_class_name.items()}
67def schematic_devices(netlist: SpiceNetlist,
68 device_models: DeviceModels,
69 top_cell_name: str) -> SchematicDevices:
70 """
71 :param top_cell_name: the cell of the layout, whose subcircuit in the schematic has the devices,
72 else those of the subcircuits that no element calls, and the elements outside of them
73 :raises SpiceEvaluationError: if a value has no number that a device class or the totals of a device depend on
74 """
75 def parameter_names(element: SpiceElement) -> Set[str]:
76 # NOTE: the others may have no number here, e.g. feed=double of an IHP MOM cap as xschem writes it,
77 # whose double is a .param of the model library, which the schematic doesn't include
78 return {name for mapping in device_models.mappings_of_model(element)
79 for name in (*device_models.constant_parameters(mapping),
80 *DeviceTotalParameters.model_parameter_names(mapping))}
82 hierarchy = SpiceHierarchy(netlist)
83 top = hierarchy.subcircuit(top_cell_name)
84 if top is not None:
85 elements = hierarchy.flat_elements(top, parameter_names)
86 else:
87 elements = chain(*(hierarchy.flat_elements(s, parameter_names) for s in hierarchy.uncalled_subcircuits()),
88 hierarchy.flat_elements(parameter_names=parameter_names))
90 devices_by_class_name: Dict[LVSDeviceClassName, List[SchematicDevice]] = defaultdict(list)
91 device_names_by_unknown_model: Dict[str, List[str]] = defaultdict(list)
92 for e in elements:
93 class_name = device_models.lvs_device_class_name(e.element)
94 if class_name is not None:
95 mapping = device_models.mapping_by_lvs_device_class_name[class_name]
96 devices_by_class_name[class_name].append(
97 SchematicDevice(name=e.path,
98 lvs_parameters=device_models.lvs_parameter_values(mapping, e.element),
99 m=e.element.parameters.get('m', 1.0)))
100 elif e.element.model is not None:
101 device_names_by_unknown_model[e.element.model].append(e.path)
103 return SchematicDevices(devices_by_class_name=dict(devices_by_class_name),
104 device_names_by_unknown_model=dict(device_names_by_unknown_model))