Coverage for klayout_pex/klayout/netlist_expander.py: 97%

71 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-31 20:14 +0000

1# 

2# -------------------------------------------------------------------------------- 

3# SPDX-FileCopyrightText: 2024-2025 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# 

24from __future__ import annotations 

25 

26import re 

27from typing import * 

28 

29import klayout.db as kdb 

30 

31from ..log import ( 

32 info, 

33 warning, 

34) 

35from ..common.capacitance_matrix import CapacitanceMatrix 

36from ..util.unit_formatter import format_spice_number 

37 

38 

39class NetlistExpander: 

40 @staticmethod 

41 def expand(extracted_netlist: kdb.Netlist, 

42 top_cell_name: str, 

43 cap_matrix: CapacitanceMatrix, 

44 blackbox_devices: bool) -> kdb.Netlist: 

45 expanded_netlist: kdb.Netlist = extracted_netlist.dup() 

46 top_circuit: kdb.Circuit = expanded_netlist.circuit_by_name(top_cell_name) 

47 

48 if not blackbox_devices: 

49 for d in top_circuit.each_device(): 

50 name = d.name or d.expanded_name() 

51 info(f"Removing whiteboxed device {name}") 

52 top_circuit.remove_device(d) 

53 

54 # create capacitor class 

55 cap = kdb.DeviceClassCapacitor() 

56 cap.name = 'PEX_CAP' 

57 cap.description = "Extracted by kpex/FasterCap PEX" 

58 expanded_netlist.add(cap) 

59 

60 fc_gnd_net = top_circuit.create_net('FC_GND') # create GROUND net 

61 vsubs_net = top_circuit.create_net("VSUBS") 

62 nets: List[kdb.Net] = [] 

63 

64 # build table: name -> net 

65 name2net: Dict[str, kdb.Net] = {n.expanded_name(): n for n in top_circuit.each_net()} 

66 

67 # find nets for the matrix axes 

68 pattern = re.compile(r'^g\d+_(.*)$') 

69 for idx, nn in enumerate(cap_matrix.conductor_names): 

70 m = pattern.match(nn) 

71 nn = m.group(1) 

72 if nn not in name2net: 

73 raise Exception(f"No net found with name {nn}, net names are: {list(name2net.keys())}") 

74 n = name2net[nn] 

75 nets.append(n) 

76 

77 cap_threshold = 0.0 

78 

79 def add_parasitic_cap(i: int, 

80 j: int, 

81 net1: kdb.Net, 

82 net2: kdb.Net, 

83 cap_value: float): 

84 if cap_value > cap_threshold: 

85 c: kdb.Device = top_circuit.create_device(cap, f"Cext_{i}_{j}") 

86 c.connect_terminal('A', net1) 

87 c.connect_terminal('B', net2) 

88 c.set_parameter('C', cap_value) # Farad 

89 if net1 == net2: 

90 raise Exception(f"Invalid attempt to create cap {c.name} between " 

91 f"same net {net1} with value format_capacitance(cap_value)") 

92 else: 

93 warning(f"Ignoring capacitance matrix cell [{i},{j}], " 

94 f"{format_spice_number(cap_value)} is below threshold {format_spice_number(cap_threshold)}") 

95 

96 # ------------------------------------------------------------- 

97 # Example capacitance matrix: 

98 # [C11+C12+C13 -C12 -C13] 

99 # [-C21 C21+C22+C23 -C23] 

100 # [-C31 -C32 C31+C32+C33] 

101 # ------------------------------------------------------------- 

102 # 

103 # - Diagonal elements m[i][i] contain the capacitance over GND (Cii), 

104 # but in a sum including all the other values of the row 

105 # 

106 # https://www.fastfieldsolvers.com/Papers/The_Maxwell_Capacitance_Matrix_WP110301_R03.pdf 

107 # 

108 for i in range(0, cap_matrix.dimension): 

109 row = cap_matrix[i] 

110 cap_ii = row[i] 

111 for j in range(0, cap_matrix.dimension): 

112 if i == j: 

113 continue 

114 cap_value = -row[j] # off-diagonals are always stored as negative values 

115 cap_ii -= cap_value # subtract summands to filter out Cii 

116 if j > i: 

117 add_parasitic_cap(i=i, j=j, 

118 net1=nets[i], net2=nets[j], 

119 cap_value=cap_value) 

120 if i > 0: 

121 add_parasitic_cap(i=i, j=i, 

122 net1=nets[i], net2=nets[0], 

123 cap_value=cap_ii) 

124 

125 # Short VSUBS and FC_GND together 

126 # VSUBS ... substrate block 

127 # FC_GND ... FasterCap's GND, i.e. the diagonal Cii elements 

128 # create capacitor class 

129 

130 res = kdb.DeviceClassResistor() 

131 res.name = 'PEX_RES' 

132 res.description = "Extracted by kpex/FasterCap PEX" 

133 expanded_netlist.add(res) 

134 

135 gnd_net = name2net.get('GND', None) 

136 if not gnd_net: 

137 gnd_net = top_circuit.create_net('GND') # create GROUND net 

138 

139 c: kdb.Device = top_circuit.create_device(res, f"Rext_FC_GND_GND") 

140 c.connect_terminal('A', fc_gnd_net) 

141 c.connect_terminal('B', gnd_net) 

142 c.set_parameter('R', 0) 

143 

144 c: kdb.Device = top_circuit.create_device(res, f"Rext_VSUBS_GND") 

145 c.connect_terminal('A', vsubs_net) 

146 c.connect_terminal('B', gnd_net) 

147 c.set_parameter('R', 0) 

148 

149 return expanded_netlist