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
« 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#
25from typing import *
26from pathlib import Path
27import re
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)
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')
50 main_paths_by_cell_name: Dict[CellName, Path] = dict()
51 res_paths_by_cell_name: Dict[CellName, Path] = dict()
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
59 cell = m.group('cell')
60 res = m.group('res')
62 if res:
63 res_paths_by_cell_name[cell] = ef
64 else:
65 main_paths_by_cell_name[cell] = ef
67 if not main_paths_by_cell_name:
68 raise Exception(f"Could not find any *.ext files to analyze in {run_dir}")
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)
77 return MagicPEXRun(
78 run_dir=run_dir,
79 cells=cells
80 )
83def parse_magic_ext_file(path: Path) -> ExtData:
84 ports: List[Port] = []
85 nodes: List[Node] = []
86 devices: List[Device] = []
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')))
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')))
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)
131def parse_magic_res_ext_file(path: Path) -> ResExtData:
132 rnodes = []
133 resistors = []
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'))))
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'))))
155 return ResExtData(path=path, rnodes=rnodes, resistors=resistors)