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

61 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-02 17:12 +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 with open(log_path, 'w', encoding='utf-8') as f: 

86 while True: 

87 line = proc.stdout.readline() 

88 if not line: 

89 break 

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

91 f.writelines([line]) 

92 proc.wait() 

93 

94 duration = time.time() - start 

95 

96 rule() 

97 

98 if proc.returncode == 0: 

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

100 else: 

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

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

103 

104 

105# CAPACITANCE MATRIX, picofarads 

106# 1 2 3 4 

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

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

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

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

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

112 with open(log_path, 'r') as f: 

113 rlines = f.readlines() 

114 rlines.reverse() 

115 

116 # multiple iterations possible, find the last matrix 

117 for idx, line in enumerate(rlines): 

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

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

120 if not section_m: 

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

122 unit_str = section_m.group(1) 

123 

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

125 dimensions = dimension_line.split() # remove whitespace 

126 dim = len(dimensions) 

127 conductor_names: List[str] = [] 

128 rows: List[List[float]] = [] 

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

130 line = rlines[i].strip() 

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

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

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

134 cells.pop(1) 

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

136 conductor_names.append(cells[0]) 

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

138 rows.append(row) 

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

140 return cm 

141 

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