Coverage for klayout_pex/magic/magic_ext_file_parser.py: 12%
57 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-03-31 19:36 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-03-31 19:36 +0000
1#
2# --------------------------------------------------------------------------------
3# SPDX-FileCopyrightText: 2024 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/martinjankoehler/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 m = re.match(
104 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+) .*$',
105 line.strip())
106 if m:
107 nodes.append(Node(net=m.group('net'),
108 int_r=int(m.group('int_r')),
109 fin_c=int(m.group('fin_c')),
110 x_bot=int(m.group('x_bot')),
111 y_bot=int(m.group('y_bot')),
112 layer=m.group('layer')))
114 m = re.match(
115 r'^device (?P<type>\w+) (?P<model>\w+) (?P<x_bot>-?\d+) (?P<y_bot>-?\d+) (?P<x_top>-?\d+) (?P<y_top>-?\d+) .*$',
116 line.strip())
117 if m:
118 t = m.group('type')
119 device_type = DeviceType(t)
120 devices.append(Device(device_type=device_type,
121 model=m.group('model'),
122 x_bot=int(m.group('x_bot')),
123 y_bot=int(m.group('y_bot')),
124 x_top=int(m.group('x_top')),
125 y_top=int(m.group('y_top'))))
126 return ExtData(path=path, ports=ports, nodes=nodes, devices=devices)
129def parse_magic_res_ext_file(path: Path) -> ResExtData:
130 rnodes = []
131 resistors = []
133 with open(path, 'r') as f:
134 for line in f.readlines():
135 m = re.match(
136 r'^rnode "(?P<name>[\w\\.]+)" (?P<int_r>-?\d+) (?P<fin_c>-?\d+) (?P<x_bot>-?\d+) (?P<y_bot>-?\d+) 0$',
137 line.strip())
138 if m:
139 rnodes.append(ResNode(name=m.group('name'),
140 int_r=int(m.group('int_r')), # NOTE: R is always 0!
141 fin_c=int(m.group('fin_c')),
142 x_bot=int(m.group('x_bot')),
143 y_bot=int(m.group('y_bot'))))
145 m = re.match(
146 r'^resist "(?P<node1>[\w\\.]+)" "(?P<node2>[\w\\.]+)" (?P<value>-?[\d\\.]+)$',
147 line.strip())
148 if m:
149 resistors.append(Resistor(node1=m.group('node1'),
150 node2=m.group('node2'),
151 value_ohm=float(m.group('value'))))
153 return ResExtData(path=path, rnodes=rnodes, resistors=resistors)