Coverage for klayout_pex/openrcx/pex25d_importer.py: 100%
51 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-06 13:14 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-06 13:14 +0000
1#
2# --------------------------------------------------------------------------------
3# SPDX-FileCopyrightText: 2024-2026 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#
25"""
26OpenRCX Universal Pattern Format (UF) → unresolved PEX25DFile,
27the built-in importer ``klayout-pex:openrcx-uf``.
29A stub: :meth:`OpenRCXUFImporter.import_file` raises ``NotImplementedError``, which
30``pex25d import`` reports with exit code 3. :meth:`OpenRCXUFImporter.placeholder_file`
31is the template for the implementation. It fills every part of a PEX25DFile with
32placeholder values, and each ``TODO(UF)`` names what the Universal Pattern Format
33input has to provide instead.
35All coordinates are integers in grid units (``units``); the template's grid is 1 nm.
36"""
38from __future__ import annotations
40from typing import TYPE_CHECKING, Any, Mapping, Optional
42from .. import pex25d
43from ..plugin_api.v1 import PEX25DImporter
44from ..version import __version__
46if TYPE_CHECKING:
47 from klayout_pex_protobuf.kpex.pex25d.pex25d_file_pb2 import PEX25DFile
50class OpenRCXUFImporter(PEX25DImporter):
51 """Translate an OpenRCX Universal Pattern Format file into an unresolved PEX25DFile.
53 A stub: imports raise NotImplementedError until the parsing is implemented.
54 """
56 name = 'openrcx-uf'
58 def import_file(self,
59 input_file_path: str,
60 *,
61 supporting_files: Optional[Mapping[str, str]] = None,
62 options: Optional[Mapping[str, Any]] = None,
63 report: Optional[pex25d.DiagnosticsReport] = None) -> PEX25DFile:
64 # TODO(UF): parse input_file_path (and any supporting_files, e.g. a process
65 # stack), then build the file like self.placeholder_file() does.
66 # Report unsupported constructs as pex25d.ImporterError, never guess
67 # values.
68 raise NotImplementedError(
69 f"The '{self.name}' importer is a stub; the template to implement it is "
70 f"klayout_pex/openrcx/pex25d_importer.py")
72 def placeholder_file(self) -> PEX25DFile:
73 """
74 A minimal, valid PEX25DFile.
76 Every value is a placeholder for what the Universal Pattern Format input provides.
77 """
78 pex25d_file = pex25d.proto.PEX25DFile()
80 # ---------------------------------------------------------------- header
81 # The PEX25D version this implementation writes;
82 # not taken from the Universal Pattern Format input.
83 pex25d_file.format_version_major = pex25d.FORMAT_VERSION_MAJOR
84 pex25d_file.format_version_minor = pex25d.FORMAT_VERSION_MINOR
85 pex25d_file.format_version_suffix = pex25d.FORMAT_VERSION_SUFFIX
87 # ----------------------------------------------------------------- units
88 # TODO(UF): the length unit and a grid fine enough for every coordinate and
89 # z value of the pattern. source_dbu_* is the database unit of the
90 # Universal Pattern Format input, if it has one.
91 units = pex25d_file.units
92 units.length = units.LENGTH_UNIT_UM
93 units.grid_numerator, units.grid_denominator = 1, 1000 # 1 nm
94 units.source_dbu_numerator, units.source_dbu_denominator = 1, 1000 # 1 nm
96 # -------------------------------------------------------------- metadata
97 # TODO(UF): technology, corner and the pattern / cell name, as far as the
98 # Universal Pattern Format input names them.
99 for key, value in (('source_format', 'openrcx-uf'),
100 ('generator', f'kpex {__version__}'),
101 ('technology', 'placeholder')):
102 meta = pex25d_file.metadata.add()
103 meta.key, meta.value = key, value
105 # --------------------------------------------------------- process stack
106 # TODO(UF): ground plane, metals and vias with absolute z ranges (zlow < zhigh).
107 ground_plane = pex25d_file.ground_plane
108 ground_plane.name = 'substrate'
109 ground_plane.zlow, ground_plane.zhigh = -100, 0
111 metal = pex25d_file.metals.add()
112 metal.name = 'metal1'
113 metal.zlow, metal.zhigh = 1000, 1400
115 # A via connects two metals; patterns with a single metal have none:
116 # via = pex25d_file.vias.add()
117 # via.name, via.connects_below, via.connects_above = 'via1', 'metal1', 'metal2'
119 # ----------------------------------------------------------- dielectrics
120 # TODO(UF): the background and every dielectric with its permittivity, from the
121 # innermost outwards. A SIMPLE band lies between two profiles; a
122 # CONFORMAL film wraps one (dielectric.conformal.thickness_*).
123 background = pex25d_file.background
124 background.name = 'air'
125 background.permittivity = 1.0
127 dielectric = pex25d_file.dielectrics.add()
128 dielectric.name = 'ild'
129 dielectric.kind = pex25d.proto.dielectric.DIELECTRIC_KIND_SIMPLE
130 dielectric.permittivity = 3.9
131 dielectric.wraps = ground_plane.name
132 dielectric.simple.between_below = ground_plane.name
133 dielectric.simple.between_above = metal.name
135 # ------------------------------------------------------ conductors, shapes
136 # TODO(UF): one conductor per wire / net of the pattern, and its shapes per
137 # layer. Floating conductors (e.g. fill) get their own net name.
138 conductor = pex25d_file.conductors.add()
139 conductor.name, conductor.net = 'w0', 'net0'
141 shape = pex25d_file.shapes.add()
142 shape.conductor, shape.layer = conductor.name, metal.name
143 shape.kind = shape.SHAPE_KIND_BOX
144 shape.box.lower_left.x, shape.box.lower_left.y = 0, 0
145 shape.box.upper_right.x, shape.box.upper_right.y = 1000, 200
146 # A polygon instead: shape.kind = shape.SHAPE_KIND_POLYGON, then
147 # for x, y in points:
148 # point = shape.polygon.outer.points.add()
149 # point.x, point.y = x, y
151 # TODO(UF): source references, so that diagnostics point into the
152 # Universal Pattern Format input:
153 # shape.source.file, shape.source.line = input_file_path, line_number
155 # -------------------------------------------------------------- terminals
156 # TODO(UF): ports, if the Universal Pattern Format input names them; otherwise leave
157 # terminals empty.
158 terminal = pex25d_file.terminals.add()
159 terminal.name, terminal.conductor, terminal.layer = 't0', conductor.name, metal.name
160 terminal.kind = pex25d.proto.terminal.TERMINAL_KIND_PIN
161 terminal.region.lower_left.x, terminal.region.lower_left.y = 0, 0
162 terminal.region.upper_right.x, terminal.region.upper_right.y = 200, 200
164 # ----------------------------------------------------------------- domain
165 # TODO(UF): the simulation window. Either a margin around the geometry, as here,
166 # or an explicit pex25d_file.domain_box.
167 margin = pex25d_file.domain_margin
168 margin.x, margin.y, margin.z = 4000, 4000, 2000
170 # ------------------------------------------------------------- resistance
171 # Optional; only if the Universal Pattern Format input carries sheet resistances:
172 # pex25d_file.resistance_temperature.celsius = 25.0
173 # sheet = pex25d_file.metal_resistances.add()
174 # sheet.metal, sheet.sheet = 'metal1', 0.125
176 return pex25d_file