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

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# 

24 

25""" 

26The devices of a schematic, in the LVS device classes of the extracted netlist (#345) 

27""" 

28 

29from __future__ import annotations 

30 

31from collections import defaultdict 

32from dataclasses import dataclass, field 

33from itertools import chain 

34from typing import * 

35 

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 

41 

42 

43@dataclass 

44class SchematicDevice: 

45 name: str # e.g. X1.XM2 

46 

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) 

49 

50 m: float = 1.0 # the multiplier of SPICE 

51 

52 

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) 

57 

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) 

61 

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()} 

65 

66 

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))} 

81 

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

89 

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) 

102 

103 return SchematicDevices(devices_by_class_name=dict(devices_by_class_name), 

104 device_names_by_unknown_model=dict(device_names_by_unknown_model))