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

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# 

24 

25""" 

26OpenRCX Universal Pattern Format (UF) → unresolved PEX25DFile, 

27the built-in importer ``klayout-pex:openrcx-uf``. 

28 

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. 

34 

35All coordinates are integers in grid units (``units``); the template's grid is 1 nm. 

36""" 

37 

38from __future__ import annotations 

39 

40from typing import TYPE_CHECKING, Any, Mapping, Optional 

41 

42from .. import pex25d 

43from ..plugin_api.v1 import PEX25DImporter 

44from ..version import __version__ 

45 

46if TYPE_CHECKING: 

47 from klayout_pex_protobuf.kpex.pex25d.pex25d_file_pb2 import PEX25DFile 

48 

49 

50class OpenRCXUFImporter(PEX25DImporter): 

51 """Translate an OpenRCX Universal Pattern Format file into an unresolved PEX25DFile. 

52 

53 A stub: imports raise NotImplementedError until the parsing is implemented. 

54 """ 

55 

56 name = 'openrcx-uf' 

57 

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") 

71 

72 def placeholder_file(self) -> PEX25DFile: 

73 """ 

74 A minimal, valid PEX25DFile. 

75 

76 Every value is a placeholder for what the Universal Pattern Format input provides. 

77 """ 

78 pex25d_file = pex25d.proto.PEX25DFile() 

79 

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 

86 

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 

95 

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 

104 

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 

110 

111 metal = pex25d_file.metals.add() 

112 metal.name = 'metal1' 

113 metal.zlow, metal.zhigh = 1000, 1400 

114 

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' 

118 

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 

126 

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 

134 

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' 

140 

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 

150 

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 

154 

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 

163 

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 

169 

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 

175 

176 return pex25d_file