Coverage for klayout_pex/magic/magic_ext_file_parser.py: 70%

57 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-17 19:08 +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# 

24 

25from typing import * 

26from pathlib import Path 

27import re 

28 

29from .magic_ext_data_structures import ( 

30 MagicPEXRun, 

31 CellName, 

32 CellExtData, 

33 ExtData, 

34 ResExtData, 

35 Port, 

36 Device, 

37 DeviceType, 

38 Node, 

39 ResNode, 

40 Resistor 

41) 

42 

43 

44def parse_magic_pex_run(run_dir: Path) -> MagicPEXRun: 

45 # search for <cell>.ext files (C related) 

46 # search for <cell>.res.ext files (R related) 

47 # ext_files = [f.resolve() for f in self.magic_log_dir_path.glob('*.ext')] 

48 ext_files = run_dir.glob('*.ext') 

49 

50 main_paths_by_cell_name: Dict[CellName, Path] = dict() 

51 res_paths_by_cell_name: Dict[CellName, Path] = dict() 

52 

53 regex = r'(?P<cell>.*?)(?P<res>\.res)?\.ext' 

54 for ef in ext_files: 

55 m = re.match(regex, ef.name) 

56 if not m: 

57 continue 

58 

59 cell = m.group('cell') 

60 res = m.group('res') 

61 

62 if res: 

63 res_paths_by_cell_name[cell] = ef 

64 else: 

65 main_paths_by_cell_name[cell] = ef 

66 

67 if not main_paths_by_cell_name: 

68 raise Exception(f"Could not find any *.ext files to analyze in {run_dir}") 

69 

70 cells: Dict[CellName, CellExtData] = {} 

71 for cell, ext_path in main_paths_by_cell_name.items(): 

72 ext_data = parse_magic_ext_file(ext_path) 

73 res_ext_path = res_paths_by_cell_name.get(cell, None) 

74 res_ext_data = None if res_ext_path is None else parse_magic_res_ext_file(res_ext_path) 

75 cells[cell] = CellExtData(ext_data=ext_data, res_ext_data=res_ext_data) 

76 

77 return MagicPEXRun( 

78 run_dir=run_dir, 

79 cells=cells 

80 ) 

81 

82 

83def parse_magic_ext_file(path: Path) -> ExtData: 

84 ports: List[Port] = [] 

85 nodes: List[Node] = [] 

86 devices: List[Device] = [] 

87 

88 with open(path, 'r') as f: 

89 for line in f.readlines(): 

90 # port "VDD" 2 -600 800 500 1000 m1 

91 # port "VSS" 3 -600 500 500 700 m1 

92 m = re.match( 

93 r'^port "(?P<net>\w+)" (?P<nr>\d+) (?P<x_bot>-?\d+) (?P<y_bot>-?\d+) (?P<x_top>-?\d+) (?P<y_top>-?\d+) (?P<layer>\w+)$', 

94 line.strip()) 

95 if m: 

96 ports.append(Port(net=m.group('net'), 

97 x_bot=int(m.group('x_bot')), 

98 y_bot=int(m.group('y_bot')), 

99 x_top=int(m.group('x_top')), 

100 y_top=int(m.group('y_top')), 

101 layer=m.group('layer'))) 

102 

103 # NOTE: the node's resistance and capacitance are not integers, 

104 # MAGIC writes e.g. 'node "B" 939 968.643 1970 -15 li …' 

105 m = re.match( 

106 r'^(node|substrate) "(?P<net>\w+)" (?P<int_r>-?[\d.]+) (?P<fin_c>-?[\d.]+) (?P<x_bot>-?\d+) (?P<y_bot>-?\d+) (?P<layer>\w+) .*$', 

107 line.strip()) 

108 if m: 

109 nodes.append(Node(net=m.group('net'), 

110 int_r=float(m.group('int_r')), 

111 fin_c=float(m.group('fin_c')), 

112 x_bot=int(m.group('x_bot')), 

113 y_bot=int(m.group('y_bot')), 

114 layer=m.group('layer'))) 

115 

116 m = re.match( 

117 r'^device (?P<type>\w+) (?P<model>\w+) (?P<x_bot>-?\d+) (?P<y_bot>-?\d+) (?P<x_top>-?\d+) (?P<y_top>-?\d+) .*$', 

118 line.strip()) 

119 if m: 

120 t = m.group('type') 

121 device_type = DeviceType(t) 

122 devices.append(Device(device_type=device_type, 

123 model=m.group('model'), 

124 x_bot=int(m.group('x_bot')), 

125 y_bot=int(m.group('y_bot')), 

126 x_top=int(m.group('x_top')), 

127 y_top=int(m.group('y_top')))) 

128 return ExtData(path=path, ports=ports, nodes=nodes, devices=devices) 

129 

130 

131def parse_magic_res_ext_file(path: Path) -> ResExtData: 

132 rnodes = [] 

133 resistors = [] 

134 

135 with open(path, 'r') as f: 

136 for line in f.readlines(): 

137 m = re.match( 

138 r'^rnode "(?P<name>[\w\\.]+)" (?P<int_r>-?\d+) (?P<fin_c>-?\d+) (?P<x_bot>-?\d+) (?P<y_bot>-?\d+) 0$', 

139 line.strip()) 

140 if m: 

141 rnodes.append(ResNode(name=m.group('name'), 

142 int_r=int(m.group('int_r')), # NOTE: R is always 0! 

143 fin_c=int(m.group('fin_c')), 

144 x_bot=int(m.group('x_bot')), 

145 y_bot=int(m.group('y_bot')))) 

146 

147 m = re.match( 

148 r'^resist "(?P<node1>[\w\\.]+)" "(?P<node2>[\w\\.]+)" (?P<value>-?[\d\\.]+)$', 

149 line.strip()) 

150 if m: 

151 resistors.append(Resistor(node1=m.group('node1'), 

152 node2=m.group('node2'), 

153 value_ohm=float(m.group('value')))) 

154 

155 return ResExtData(path=path, rnodes=rnodes, resistors=resistors)