Coverage for klayout_pex/device_totals.py: 100%

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

26Totals of the devices of an LVS device class that don't depend on how the devices are split 

27(into fingers, by m, into series or parallel segments), which --check compares between the schematic 

28and the LVS netlist (#345): 

29 - the area: W·L, if the device model mapping writes model parameters of the LVS parameters W and L 

30 (in any case), or else A, if it writes those of the area (A, or the rectangle of an area and perimeter) 

31 - the width W of a MOS 

32 - the multiplier, if the mapping writes the model's m of an LVS parameter (e.g. NE of a BJT) 

33each times the multiplier: that LVS parameter, else (or if a device of the schematic doesn't give it) 

34the m of a device of the schematic (SPICE's multiplier, 1 if not given). 

35 

36The totals are in the units of the LVS parameters (e.g. µm² for W·L of a MOS in µm), a device of the schematic 

37gets its LVS parameters by the mapping in reverse (see DeviceModels.lvs_parameter_values). 

38A class whose mapping writes none of them has no totals (e.g. taps, or caps of a fixed size). 

39""" 

40 

41from __future__ import annotations 

42from dataclasses import dataclass 

43import math 

44from typing import * 

45 

46import klayout.db as kdb 

47 

48from .device_models import DeviceModels 

49import klayout_pex_protobuf.kpex.tech.device_models_pb2 as device_models_pb2 

50 

51 

52@dataclass(frozen=True) 

53class DeviceTotalParameters: 

54 """ 

55 The LVS parameters of the totals of a device class (see the module docs) 

56 """ 

57 width: Optional[str] = None 

58 length: Optional[str] = None 

59 area: Optional[str] = None 

60 multiplier: Optional[str] = None 

61 mos: bool = False 

62 

63 @staticmethod 

64 def of(device_class: Optional[kdb.DeviceClass], 

65 mapping: device_models_pb2.DeviceModelMapping) -> DeviceTotalParameters: 

66 """ 

67 :param device_class: None if only the names of the LVS parameters matter 

68 """ 

69 names: Dict[str, str] = {} 

70 area: Optional[str] = None 

71 for p in mapping.parameters: 

72 match p.WhichOneof('value'): 

73 case 'lvs_parameter_name': 

74 names.setdefault(p.lvs_parameter_name.upper(), p.lvs_parameter_name) 

75 case 'lvs_area_perimeter_side': 

76 area = p.lvs_area_perimeter_side.area_parameter_name 

77 return DeviceTotalParameters(width=names.get('W'), 

78 length=names.get('L'), 

79 area=area or names.get('A'), 

80 multiplier=DeviceModels.multiplier_lvs_parameter_name(mapping), 

81 mos=isinstance(device_class, kdb.DeviceClassMOS3Transistor)) 

82 

83 @staticmethod 

84 def model_parameter_names(mapping: device_models_pb2.DeviceModelMapping) -> Set[str]: 

85 """ 

86 The parameters of a device of a schematic that its totals depend on, in lowercase, with the m of SPICE, 

87 e.g. {'w', 'l', 'm'} of a MOS, but not the feed of an IHP MOM cap 

88 """ 

89 totals = DeviceTotalParameters.of(None, mapping) 

90 lvs_parameter_names = {totals.width, totals.length, totals.area, totals.multiplier} 

91 names = {'m'} 

92 for p in mapping.parameters: 

93 match p.WhichOneof('value'): 

94 case 'lvs_parameter_name' if p.lvs_parameter_name in lvs_parameter_names: 

95 names.add(p.name.lower()) 

96 case 'lvs_area_perimeter_side': 

97 names.add(p.name.lower()) 

98 return names 

99 

100 @property 

101 def quantity_names(self) -> List[str]: 

102 """ 

103 e.g. ['W·L', 'W'] of a MOS 

104 """ 

105 return list(self.quantities({})) 

106 

107 def quantities(self, 

108 lvs_parameters: Mapping[str, float], 

109 m: float = 1.0) -> Dict[str, Optional[float]]: 

110 """ 

111 The quantities of a device whose totals are compared, e.g. {'W·L': 0.13, 'W': 1.0} of a MOS 

112 

113 :param lvs_parameters: of the device, by name 

114 :param m: the multiplier of SPICE, if the mapping writes no multiplier, or the device doesn't give it 

115 :return: None for a quantity the device has no value for 

116 (e.g. a parameter the schematic leaves to the model's default) 

117 """ 

118 multiplier = lvs_parameters.get(self.multiplier, m) if self.multiplier else m 

119 

120 def product(*names: str) -> Optional[float]: 

121 values = [lvs_parameters.get(n) for n in names] 

122 if None in values: 

123 return None 

124 return math.prod(values) * multiplier 

125 

126 quantities: Dict[str, Optional[float]] = {} 

127 if self.width and self.length: 

128 quantities[f"{self.width}·{self.length}"] = product(self.width, self.length) 

129 elif self.area: 

130 quantities[self.area] = product(self.area) 

131 if self.mos and self.width: 

132 quantities[self.width] = product(self.width) 

133 if self.multiplier: 

134 quantities[self.multiplier] = multiplier 

135 return quantities