Coverage for klayout_pex/fastcap/fastcap_runner.py: 56%

61 statements  

« prev     ^ index     » next       coverage.py v7.17.2, created at 2026-11-06 13: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# 

24import re 

25import time 

26from typing import * 

27 

28import os 

29import subprocess 

30 

31from ..log import ( 

32 debug, 

33 info, 

34 warning, 

35 error, 

36 rule, 

37 subproc, 

38) 

39from ..common.capacitance_matrix import CapacitanceMatrix 

40 

41 

42def run_fastcap(exe_path: str, 

43 lst_file_path: str, 

44 log_path: str, 

45 expansion_order: float = 2, 

46 partitioning_depth: str = 'auto', 

47 permittivity_factor: float = 1.0, 

48 iterative_tolerance: float = 0.01): 

49 work_dir = os.path.dirname(lst_file_path) 

50 

51 # we have to chdir into the directory containing the lst file, 

52 # so make all paths absolute, and the lst_file relative 

53 log_path = os.path.abspath(log_path) 

54 lst_file_path = os.path.basename(lst_file_path) 

55 

56 args = [ 

57 exe_path, 

58 f"-o{expansion_order}", 

59 ] 

60 

61 if partitioning_depth != 'auto': 

62 args += [ 

63 f"-d{partitioning_depth}", 

64 ] 

65 

66 args += [ 

67 f"-p{permittivity_factor}", 

68 f"-t{iterative_tolerance}", 

69 f"-l{lst_file_path}", 

70 ] 

71 

72 info(f"Calling FastCap2 in {work_dir}") 

73 subproc(f"{' '.join(args)}, output file: {log_path}") 

74 

75 rule('FastCap Output') 

76 start = time.time() 

77 

78 proc = subprocess.Popen(args, 

79 cwd=work_dir, 

80 stdin=subprocess.DEVNULL, 

81 stdout=subprocess.PIPE, 

82 stderr=subprocess.STDOUT, 

83 universal_newlines=True, 

84 text=True, 

85 encoding='utf-8', 

86 errors='replace') 

87 with open(log_path, 'w', encoding='utf-8') as f: 

88 while True: 

89 line = proc.stdout.readline() 

90 if not line: 

91 break 

92 subproc(line[:-1]) # remove newline 

93 f.writelines([line]) 

94 proc.wait() 

95 

96 duration = time.time() - start 

97 

98 rule() 

99 

100 if proc.returncode == 0: 

101 info(f"FastCap2 succeeded after {'%.4g' % duration}s") 

102 else: 

103 raise Exception(f"FastCap2 failed with status code {proc.returncode} after {'%.4g' % duration}s, " 

104 f"see log file: {log_path}") 

105 

106 

107# CAPACITANCE MATRIX, picofarads 

108# 1 2 3 4 

109# $1%GROUP2 1 7850 -7277 -2115 54.97 

110# $1%GROUP2 2 -7277 3.778e+05 130.9 -3.682e+05 

111# $2%GROUP3 3 -2115 130.9 6792 -5388 

112# $2%GROUP3 4 54.97 -3.682e+05 -5388 3.753e+05 

113def fastcap_parse_capacitance_matrix(log_path: str) -> CapacitanceMatrix: 

114 with open(log_path, 'r', encoding='utf-8') as f: 

115 rlines = f.readlines() 

116 rlines.reverse() 

117 

118 # multiple iterations possible, find the last matrix 

119 for idx, line in enumerate(rlines): 

120 if line.startswith('CAPACITANCE MATRIX, '): 

121 section_m = re.match(r'CAPACITANCE MATRIX, (\w+)', line) 

122 if not section_m: 

123 raise Exception(f"Could not parse capacitor unit") 

124 unit_str = section_m.group(1) 

125 

126 dimension_line = rlines[idx-1].strip() 

127 dimensions = dimension_line.split() # remove whitespace 

128 dim = len(dimensions) 

129 conductor_names: List[str] = [] 

130 rows: List[List[float]] = [] 

131 for i in reversed(range(idx-1-dim, idx-1)): 

132 line = rlines[i].strip() 

133 cells = [cell.strip() for cell in line.split(' ')] 

134 if cells[1] != str(i): 

135 warning(f"Expected capacitor matrix row to have index {i}, but obtained {cells[1]}") 

136 cells.pop(1) 

137 cells = list(filter(lambda c: len(c) >= 1, cells)) 

138 conductor_names.append(cells[0]) 

139 row = [float(cell)/1e6 for cell in cells[1:]] 

140 rows.append(row) 

141 cm = CapacitanceMatrix(conductor_names=conductor_names, rows=rows) 

142 return cm 

143 

144 raise Exception(f"Could not extract capacitance matrix from FasterCap log file {log_path}")