librelane 2.4.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of librelane might be problematic. Click here for more details.

Files changed (166) hide show
  1. librelane/__init__.py +38 -0
  2. librelane/__main__.py +470 -0
  3. librelane/__version__.py +43 -0
  4. librelane/common/__init__.py +61 -0
  5. librelane/common/cli.py +75 -0
  6. librelane/common/drc.py +245 -0
  7. librelane/common/generic_dict.py +319 -0
  8. librelane/common/metrics/__init__.py +35 -0
  9. librelane/common/metrics/__main__.py +413 -0
  10. librelane/common/metrics/library.py +354 -0
  11. librelane/common/metrics/metric.py +186 -0
  12. librelane/common/metrics/util.py +279 -0
  13. librelane/common/misc.py +402 -0
  14. librelane/common/ring_buffer.py +63 -0
  15. librelane/common/tcl.py +80 -0
  16. librelane/common/toolbox.py +549 -0
  17. librelane/common/tpe.py +41 -0
  18. librelane/common/types.py +117 -0
  19. librelane/config/__init__.py +32 -0
  20. librelane/config/__main__.py +158 -0
  21. librelane/config/config.py +1025 -0
  22. librelane/config/flow.py +490 -0
  23. librelane/config/pdk_compat.py +255 -0
  24. librelane/config/preprocessor.py +464 -0
  25. librelane/config/removals.py +45 -0
  26. librelane/config/variable.py +722 -0
  27. librelane/container.py +264 -0
  28. librelane/env_info.py +306 -0
  29. librelane/examples/spm/config.yaml +33 -0
  30. librelane/examples/spm/pin_order.cfg +14 -0
  31. librelane/examples/spm/src/impl.sdc +73 -0
  32. librelane/examples/spm/src/signoff.sdc +68 -0
  33. librelane/examples/spm/src/spm.v +73 -0
  34. librelane/examples/spm/verify/spm_tb.v +106 -0
  35. librelane/examples/spm-user_project_wrapper/SPM_example.v +286 -0
  36. librelane/examples/spm-user_project_wrapper/base_sdc_file.sdc +145 -0
  37. librelane/examples/spm-user_project_wrapper/config-tut.json +12 -0
  38. librelane/examples/spm-user_project_wrapper/config.json +13 -0
  39. librelane/examples/spm-user_project_wrapper/defines.v +66 -0
  40. librelane/examples/spm-user_project_wrapper/template.def +7656 -0
  41. librelane/examples/spm-user_project_wrapper/user_project_wrapper.v +123 -0
  42. librelane/flows/__init__.py +24 -0
  43. librelane/flows/builtins.py +18 -0
  44. librelane/flows/classic.py +330 -0
  45. librelane/flows/cli.py +463 -0
  46. librelane/flows/flow.py +985 -0
  47. librelane/flows/misc.py +71 -0
  48. librelane/flows/optimizing.py +179 -0
  49. librelane/flows/sequential.py +367 -0
  50. librelane/flows/synth_explore.py +173 -0
  51. librelane/logging/__init__.py +40 -0
  52. librelane/logging/logger.py +323 -0
  53. librelane/open_pdks_rev +1 -0
  54. librelane/plugins.py +21 -0
  55. librelane/py.typed +0 -0
  56. librelane/scripts/base.sdc +80 -0
  57. librelane/scripts/klayout/Readme.md +2 -0
  58. librelane/scripts/klayout/open_design.py +63 -0
  59. librelane/scripts/klayout/render.py +121 -0
  60. librelane/scripts/klayout/stream_out.py +176 -0
  61. librelane/scripts/klayout/xml_drc_report_to_json.py +45 -0
  62. librelane/scripts/klayout/xor.drc +120 -0
  63. librelane/scripts/magic/Readme.md +1 -0
  64. librelane/scripts/magic/common/read.tcl +114 -0
  65. librelane/scripts/magic/def/antenna_check.tcl +35 -0
  66. librelane/scripts/magic/def/mag.tcl +19 -0
  67. librelane/scripts/magic/def/mag_gds.tcl +81 -0
  68. librelane/scripts/magic/drc.tcl +79 -0
  69. librelane/scripts/magic/extract_spice.tcl +98 -0
  70. librelane/scripts/magic/gds/drc_batch.tcl +74 -0
  71. librelane/scripts/magic/gds/erase_box.tcl +32 -0
  72. librelane/scripts/magic/gds/extras_mag.tcl +47 -0
  73. librelane/scripts/magic/gds/mag_with_pointers.tcl +32 -0
  74. librelane/scripts/magic/get_bbox.tcl +11 -0
  75. librelane/scripts/magic/lef/extras_maglef.tcl +63 -0
  76. librelane/scripts/magic/lef/maglef.tcl +27 -0
  77. librelane/scripts/magic/lef.tcl +57 -0
  78. librelane/scripts/magic/open.tcl +28 -0
  79. librelane/scripts/magic/wrapper.tcl +19 -0
  80. librelane/scripts/netgen/setup.tcl +28 -0
  81. librelane/scripts/odbpy/apply_def_template.py +49 -0
  82. librelane/scripts/odbpy/cell_frequency.py +107 -0
  83. librelane/scripts/odbpy/check_antenna_properties.py +116 -0
  84. librelane/scripts/odbpy/contextualize.py +109 -0
  85. librelane/scripts/odbpy/defutil.py +574 -0
  86. librelane/scripts/odbpy/diodes.py +373 -0
  87. librelane/scripts/odbpy/disconnected_pins.py +305 -0
  88. librelane/scripts/odbpy/exception_codes.py +17 -0
  89. librelane/scripts/odbpy/filter_unannotated.py +100 -0
  90. librelane/scripts/odbpy/io_place.py +482 -0
  91. librelane/scripts/odbpy/label_macro_pins.py +277 -0
  92. librelane/scripts/odbpy/lefutil.py +97 -0
  93. librelane/scripts/odbpy/placers.py +162 -0
  94. librelane/scripts/odbpy/power_utils.py +395 -0
  95. librelane/scripts/odbpy/random_place.py +57 -0
  96. librelane/scripts/odbpy/reader.py +246 -0
  97. librelane/scripts/odbpy/remove_buffers.py +173 -0
  98. librelane/scripts/odbpy/snap_to_grid.py +57 -0
  99. librelane/scripts/odbpy/wire_lengths.py +93 -0
  100. librelane/scripts/openroad/antenna_check.tcl +20 -0
  101. librelane/scripts/openroad/antenna_repair.tcl +31 -0
  102. librelane/scripts/openroad/basic_mp.tcl +24 -0
  103. librelane/scripts/openroad/buffer_list.tcl +10 -0
  104. librelane/scripts/openroad/common/dpl.tcl +24 -0
  105. librelane/scripts/openroad/common/dpl_cell_pad.tcl +26 -0
  106. librelane/scripts/openroad/common/grt.tcl +32 -0
  107. librelane/scripts/openroad/common/io.tcl +476 -0
  108. librelane/scripts/openroad/common/pdn_cfg.tcl +135 -0
  109. librelane/scripts/openroad/common/resizer.tcl +103 -0
  110. librelane/scripts/openroad/common/set_global_connections.tcl +78 -0
  111. librelane/scripts/openroad/common/set_layer_adjustments.tcl +31 -0
  112. librelane/scripts/openroad/common/set_power_nets.tcl +30 -0
  113. librelane/scripts/openroad/common/set_rc.tcl +75 -0
  114. librelane/scripts/openroad/common/set_routing_layers.tcl +30 -0
  115. librelane/scripts/openroad/cts.tcl +80 -0
  116. librelane/scripts/openroad/cut_rows.tcl +24 -0
  117. librelane/scripts/openroad/dpl.tcl +24 -0
  118. librelane/scripts/openroad/drt.tcl +37 -0
  119. librelane/scripts/openroad/fill.tcl +30 -0
  120. librelane/scripts/openroad/floorplan.tcl +145 -0
  121. librelane/scripts/openroad/gpl.tcl +88 -0
  122. librelane/scripts/openroad/grt.tcl +30 -0
  123. librelane/scripts/openroad/gui.tcl +15 -0
  124. librelane/scripts/openroad/insert_buffer.tcl +127 -0
  125. librelane/scripts/openroad/ioplacer.tcl +67 -0
  126. librelane/scripts/openroad/irdrop.tcl +51 -0
  127. librelane/scripts/openroad/pdn.tcl +52 -0
  128. librelane/scripts/openroad/rcx.tcl +32 -0
  129. librelane/scripts/openroad/repair_design.tcl +70 -0
  130. librelane/scripts/openroad/repair_design_postgrt.tcl +48 -0
  131. librelane/scripts/openroad/rsz_timing_postcts.tcl +68 -0
  132. librelane/scripts/openroad/rsz_timing_postgrt.tcl +70 -0
  133. librelane/scripts/openroad/sta/check_macro_instances.tcl +53 -0
  134. librelane/scripts/openroad/sta/corner.tcl +393 -0
  135. librelane/scripts/openroad/tapcell.tcl +25 -0
  136. librelane/scripts/openroad/write_views.tcl +27 -0
  137. librelane/scripts/pyosys/construct_abc_script.py +177 -0
  138. librelane/scripts/pyosys/json_header.py +84 -0
  139. librelane/scripts/pyosys/synthesize.py +493 -0
  140. librelane/scripts/pyosys/ys_common.py +153 -0
  141. librelane/scripts/tclsh/hello.tcl +1 -0
  142. librelane/state/__init__.py +24 -0
  143. librelane/state/__main__.py +61 -0
  144. librelane/state/design_format.py +180 -0
  145. librelane/state/state.py +351 -0
  146. librelane/steps/__init__.py +61 -0
  147. librelane/steps/__main__.py +511 -0
  148. librelane/steps/checker.py +637 -0
  149. librelane/steps/common_variables.py +340 -0
  150. librelane/steps/cvc_rv.py +169 -0
  151. librelane/steps/klayout.py +509 -0
  152. librelane/steps/magic.py +566 -0
  153. librelane/steps/misc.py +160 -0
  154. librelane/steps/netgen.py +253 -0
  155. librelane/steps/odb.py +955 -0
  156. librelane/steps/openroad.py +2433 -0
  157. librelane/steps/openroad_alerts.py +102 -0
  158. librelane/steps/pyosys.py +629 -0
  159. librelane/steps/step.py +1547 -0
  160. librelane/steps/tclstep.py +288 -0
  161. librelane/steps/verilator.py +222 -0
  162. librelane/steps/yosys.py +371 -0
  163. librelane-2.4.0.dev0.dist-info/METADATA +151 -0
  164. librelane-2.4.0.dev0.dist-info/RECORD +166 -0
  165. librelane-2.4.0.dev0.dist-info/WHEEL +4 -0
  166. librelane-2.4.0.dev0.dist-info/entry_points.txt +8 -0
@@ -0,0 +1,566 @@
1
+ # Copyright 2023 Efabless Corporation
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import os
15
+ import re
16
+ import shutil
17
+ import functools
18
+ import subprocess
19
+ from signal import SIGKILL
20
+ from decimal import Decimal
21
+ from abc import abstractmethod
22
+ from typing import Any, Literal, List, Optional, Tuple
23
+
24
+ from .step import (
25
+ DefaultOutputProcessor,
26
+ OutputProcessor,
27
+ StepError,
28
+ StepException,
29
+ ViewsUpdate,
30
+ MetricsUpdate,
31
+ Step,
32
+ )
33
+ from .tclstep import TclStep
34
+ from ..state import DesignFormat, State
35
+
36
+ from ..config import Variable
37
+ from ..common import get_script_dir, DRC as DRCObject, Path, mkdirp
38
+
39
+
40
+ class MagicOutputProcessor(OutputProcessor):
41
+ _error_patterns = [
42
+ re.compile(rx)
43
+ for rx in [
44
+ r"DEF read.*\(Error\).*",
45
+ r"LEF read.*\(Error\).*",
46
+ r"Error while reading cell(?!.*Warning:).*",
47
+ r".*Calma output error.*",
48
+ r".*is an abstract view.*",
49
+ ]
50
+ ]
51
+
52
+ key = "magic_output"
53
+
54
+ def __init__(self, step: Step, report_dir: str, silent: bool) -> None:
55
+ super().__init__(step, report_dir, silent)
56
+ self.fatal_error_count = 0
57
+
58
+ def process_line(self, line: str) -> bool:
59
+ for pattern in self._error_patterns:
60
+ if pattern.match(line):
61
+ self.fatal_error_count += 1
62
+ self.step.err(line)
63
+ return True
64
+ return False
65
+
66
+ def result(self) -> Any:
67
+ return {"fatal_error_count": self.fatal_error_count}
68
+
69
+
70
+ class MagicStep(TclStep):
71
+ inputs = [DesignFormat.GDS]
72
+ outputs = []
73
+
74
+ output_processors = [MagicOutputProcessor, DefaultOutputProcessor]
75
+
76
+ config_vars = [
77
+ Variable(
78
+ "MAGIC_DEF_LABELS",
79
+ bool,
80
+ "A flag to choose whether labels are read with DEF files or not. From magic docs: \"The '-labels' option to the 'def read' command causes each net in the NETS and SPECIALNETS sections of the DEF file to be annotated with a label having the net name as the label text.\" If LVS fails, try disabling this option.",
81
+ default=True,
82
+ ),
83
+ Variable(
84
+ "MAGIC_GDS_POLYGON_SUBCELLS",
85
+ bool,
86
+ 'A flag to enable polygon subcells in magic for gds read potentially speeding up magic. From magic docs: "Put non-Manhattan polygons. This prevents interations with other polygons on the same plane and so reduces tile splitting."',
87
+ default=False,
88
+ ),
89
+ Variable(
90
+ "MAGIC_DEF_NO_BLOCKAGES",
91
+ bool,
92
+ "If set to true, blockages in DEF files are ignored. Otherwise, they are read as sheets of metal by Magic.",
93
+ default=True,
94
+ ),
95
+ Variable(
96
+ "MAGIC_INCLUDE_GDS_POINTERS",
97
+ bool,
98
+ "A flag to choose whether to include GDS pointers in the generated mag files or not.",
99
+ default=False,
100
+ ),
101
+ Variable(
102
+ "MAGICRC",
103
+ Path,
104
+ "A path to the `.magicrc` file which is sourced before running magic in the flow.",
105
+ deprecated_names=["MAGIC_MAGICRC"],
106
+ pdk=True,
107
+ ),
108
+ Variable(
109
+ "MAGIC_TECH",
110
+ Path,
111
+ "A path to a Magic tech file which, mainly, has DRC rules.",
112
+ deprecated_names=["MAGIC_TECH_FILE"],
113
+ pdk=True,
114
+ ),
115
+ Variable(
116
+ "MAGIC_PDK_SETUP",
117
+ Path,
118
+ "A path to a PDK-specific setup file sourced by `.magicrc`.",
119
+ pdk=True,
120
+ ),
121
+ Variable(
122
+ "CELL_MAGS",
123
+ Optional[List[Path]],
124
+ "A list of pre-processed concrete views for cells. Read as a fallback for undefined cells.",
125
+ pdk=True,
126
+ ),
127
+ Variable(
128
+ "CELL_MAGLEFS",
129
+ Optional[List[Path]],
130
+ "A list of pre-processed abstract LEF views for cells. Read as a fallback for undefined cells in scripts where cells are black-boxed.",
131
+ pdk=True,
132
+ ),
133
+ Variable(
134
+ "MAGIC_CAPTURE_ERRORS",
135
+ bool,
136
+ "Capture errors print by Magic and quit when a fatal error is encountered."
137
+ + " Fatal errors are determined heuristically. It is not guaranteed that they are fatal errors."
138
+ + " Hence this is function is gated by a variable."
139
+ + " This function is needed because Magic does not throw errors.",
140
+ default=True,
141
+ ),
142
+ ]
143
+
144
+ @abstractmethod
145
+ def get_script_path(self) -> str:
146
+ pass
147
+
148
+ def get_command(self) -> List[str]:
149
+ return [
150
+ "magic",
151
+ "-dnull",
152
+ "-noconsole",
153
+ "-rcfile",
154
+ self.config["MAGICRC"],
155
+ os.path.join(get_script_dir(), "magic", "wrapper.tcl"),
156
+ ]
157
+
158
+ def prepare_env(self, env: dict, state: State) -> dict:
159
+ env = super().prepare_env(env, state)
160
+
161
+ env["_MAGIC_SCRIPT"] = self.get_script_path()
162
+ env["MACRO_GDS_FILES"] = ""
163
+ for gds in self.toolbox.get_macro_views(self.config, DesignFormat.GDS):
164
+ env["MACRO_GDS_FILES"] += f" {gds}"
165
+
166
+ return env
167
+
168
+ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
169
+ kwargs, env = self.extract_env(kwargs)
170
+ env = self.prepare_env(env, state_in)
171
+
172
+ check = False
173
+ if "check" in kwargs:
174
+ check = kwargs.pop("check")
175
+
176
+ command = self.get_command()
177
+
178
+ subprocess_result = self.run_subprocess(
179
+ command,
180
+ env=env,
181
+ check=check,
182
+ **kwargs,
183
+ )
184
+
185
+ if (
186
+ self.config["MAGIC_CAPTURE_ERRORS"]
187
+ and subprocess_result["magic_output"]["fatal_error_count"]
188
+ ):
189
+ raise StepError("Encountered one or more fatal errors while running Magic.")
190
+
191
+ generated_metrics = subprocess_result["generated_metrics"]
192
+
193
+ views_updates: ViewsUpdate = {}
194
+ for output in self.outputs:
195
+ if output.value.multiple:
196
+ # Too step-specific.
197
+ continue
198
+ path = Path(env[f"SAVE_{output.name}"])
199
+ if not path.exists():
200
+ continue
201
+ views_updates[output] = path
202
+
203
+ return views_updates, generated_metrics
204
+
205
+
206
+ @Step.factory.register()
207
+ class WriteLEF(MagicStep):
208
+ """
209
+ Writes a LEF view of the design using the GDS using Magic.
210
+ """
211
+
212
+ id = "Magic.WriteLEF"
213
+ name = "Write LEF (Magic)"
214
+
215
+ inputs = [DesignFormat.GDS, DesignFormat.DEF]
216
+ outputs = [DesignFormat.LEF]
217
+
218
+ config_vars = MagicStep.config_vars + [
219
+ Variable(
220
+ "MAGIC_LEF_WRITE_USE_GDS",
221
+ bool,
222
+ "A flag to choose whether to use GDS for LEF writing. If not, then the extraction will be done using abstract LEF views.",
223
+ default=False,
224
+ ),
225
+ Variable(
226
+ "MAGIC_WRITE_FULL_LEF",
227
+ bool,
228
+ "A flag to specify whether or not the output LEF should include all shapes inside the macro or an abstracted view of the macro LEF view via magic.",
229
+ default=False,
230
+ ),
231
+ Variable(
232
+ "MAGIC_WRITE_LEF_PINONLY",
233
+ bool,
234
+ "If true, the LEF write will mark only areas that are port labels as pins, while marking the rest of each related net as an obstruction. Otherwise, the labeled port and the any connected metal on the same layer are marked as a pin.",
235
+ default=False,
236
+ ),
237
+ ]
238
+
239
+ def get_script_path(self):
240
+ return os.path.join(get_script_dir(), "magic", "lef.tcl")
241
+
242
+ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
243
+ kwargs, env = self.extract_env(kwargs)
244
+ env["MAGTYPE"] = "mag"
245
+ return super().run(state_in, **kwargs)
246
+
247
+
248
+ @Step.factory.register()
249
+ class StreamOut(MagicStep):
250
+ """
251
+ Converts DEF views into GDSII streams using Magic.
252
+
253
+ If ``PRIMARY_GDSII_STREAMOUT_TOOL`` is set to ``"magic"``, both GDS and MAG_GDS
254
+ will be updated, and if set to another tool, only ``MAG_GDS`` will be
255
+ updated.
256
+ """
257
+
258
+ id = "Magic.StreamOut"
259
+ name = "GDSII Stream Out (Magic)"
260
+
261
+ inputs = [DesignFormat.DEF]
262
+ outputs = [DesignFormat.GDS, DesignFormat.MAG_GDS, DesignFormat.MAG]
263
+
264
+ config_vars = MagicStep.config_vars + [
265
+ Variable(
266
+ "DIE_AREA",
267
+ Optional[Tuple[Decimal, Decimal, Decimal, Decimal]],
268
+ 'Specific die area to be used in floorplanning when `FP_SIZING` is set to `absolute`. Specified as a 4-corner rectangle "x0 y0 x1 y1".',
269
+ units="µm",
270
+ ),
271
+ Variable(
272
+ "MAGIC_ZEROIZE_ORIGIN",
273
+ bool,
274
+ "A flag to move the layout such that it's origin in the lef generated by magic is 0,0.",
275
+ default=False,
276
+ ),
277
+ Variable(
278
+ "MAGIC_DISABLE_CIF_INFO",
279
+ bool,
280
+ "A flag to disable writing Caltech Intermediate Format (CIF) hierarchy and subcell array information to the GDSII file.",
281
+ default=True,
282
+ deprecated_names=["MAGIC_DISABLE_HIER_GDS"],
283
+ ),
284
+ Variable(
285
+ "MAGIC_MACRO_STD_CELL_SOURCE",
286
+ Literal["PDK", "macro"],
287
+ "If set to PDK, magic will use the PDK definition of the STD cells for macros inside the design."
288
+ + " Otherwise, the macro is completely treated as a blackbox and magic will use the existing cell definition inside"
289
+ + " the macro gds."
290
+ + " This mode is only supported for macros specified in MACROS variable",
291
+ default="macro",
292
+ ),
293
+ ]
294
+
295
+ def get_script_path(self):
296
+ return os.path.join(get_script_dir(), "magic", "def", "mag_gds.tcl")
297
+
298
+ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
299
+ kwargs, env = self.extract_env(kwargs)
300
+
301
+ env = self.prepare_env(env, state_in)
302
+ if die_area := state_in.metrics.get("design__die__bbox"):
303
+ env["DIE_AREA"] = die_area
304
+
305
+ env["MAGTYPE"] = "mag"
306
+
307
+ if (
308
+ self.config["MACROS"] is not None
309
+ and self.config["MAGIC_MACRO_STD_CELL_SOURCE"] == "macro"
310
+ ):
311
+ macro_gds = []
312
+ env_copy = env.copy()
313
+ for macro in self.config["MACROS"].keys():
314
+ macro_gdses = [str(path) for path in self.config["MACROS"][macro].gds]
315
+ if len(macro_gdses) > 1:
316
+ raise StepException(
317
+ "Multiple GDSII files in one Macro currently unsupported when MAGIC_MACRO_STD_CELL_SOURCE is set to 'macro'."
318
+ )
319
+ env_copy["_GDS_IN"] = macro_gdses[0]
320
+ env_copy["_MACRO_NAME_IN"] = macro
321
+ env_copy["_MAGIC_SCRIPT"] = os.path.join(
322
+ get_script_dir(), "magic", "get_bbox.tcl"
323
+ )
324
+
325
+ subprocess_result = super().run_subprocess(
326
+ self.get_command(),
327
+ env=env_copy,
328
+ log_to=os.path.join(self.step_dir, f"{macro}.get_bbox.log"),
329
+ )
330
+ generated_metrics = subprocess_result["generated_metrics"]
331
+
332
+ if generated_metrics == {}:
333
+ raise StepError(
334
+ f"Failed to extract PR boundary from GDSII view of macro '{macro}'. Ensure that the GDSII view has a PR boundary layer."
335
+ )
336
+ macro_gds.append([macro, macro_gdses, generated_metrics.values()])
337
+
338
+ env["__MACRO_GDS"] = TclStep.value_to_tcl(macro_gds)
339
+
340
+ views_updates, metrics_updates = super().run(
341
+ state_in,
342
+ env=env,
343
+ **kwargs,
344
+ )
345
+
346
+ if self.config["PRIMARY_GDSII_STREAMOUT_TOOL"] == "magic":
347
+ magic_gds_out = str(views_updates[DesignFormat.MAG_GDS])
348
+ gds_path = os.path.join(self.step_dir, f"{self.config['DESIGN_NAME']}.gds")
349
+ shutil.copy(magic_gds_out, gds_path)
350
+ views_updates[DesignFormat.GDS] = Path(gds_path)
351
+
352
+ views_updates[DesignFormat.MAG] = Path(
353
+ os.path.join(self.step_dir, f"{self.config['DESIGN_NAME']}.mag")
354
+ )
355
+
356
+ return views_updates, metrics_updates
357
+
358
+
359
+ @Step.factory.register()
360
+ class DRC(MagicStep):
361
+ """
362
+ Performs `design rule checking <https://en.wikipedia.org/wiki/Design_rule_checking>`_
363
+ on the GDSII stream using Magic.
364
+
365
+ This also converts the results to a KLayout database, which can be loaded.
366
+
367
+ The metrics will be updated with ``magic__drc_error__count``. You can use
368
+ `the relevant checker <#Checker.MagicDRC>`_ to quit if that number is
369
+ nonzero.
370
+ """
371
+
372
+ id = "Magic.DRC"
373
+ name = "DRC"
374
+ long_name = "Design Rule Checks"
375
+
376
+ inputs = [DesignFormat.DEF, DesignFormat.GDS]
377
+ outputs = []
378
+
379
+ config_vars = MagicStep.config_vars + [
380
+ Variable(
381
+ "MAGIC_DRC_USE_GDS",
382
+ bool,
383
+ "A flag to choose whether to run the Magic DRC checks on GDS or not. If not, then the checks will be done on the DEF view of the design, which is a bit faster, but may be less accurate as some DEF/LEF elements are abstract.",
384
+ default=True,
385
+ ),
386
+ ]
387
+
388
+ def get_script_path(self):
389
+ return os.path.join(get_script_dir(), "magic", "drc.tcl")
390
+
391
+ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
392
+ reports_dir = os.path.join(self.step_dir, "reports")
393
+ mkdirp(reports_dir)
394
+
395
+ views_updates, metrics_updates = super().run(state_in, **kwargs)
396
+
397
+ report_path = os.path.join(reports_dir, "drc_violations.magic.rpt")
398
+ klayout_db_path = os.path.join(reports_dir, "drc_violations.magic.xml")
399
+
400
+ # report_stats = os.stat(report_path)
401
+ # drc_db_file = None
402
+ # if report_stats.st_size >= 0: # 134217728:
403
+ # drc_db_file = os.path.join(reports_dir, "drc.db")
404
+
405
+ drc, bbox_count = DRCObject.from_magic(
406
+ open(report_path, encoding="utf8"),
407
+ # db_file=drc_db_file,
408
+ )
409
+
410
+ drc.to_klayout_xml(open(klayout_db_path, "wb"))
411
+
412
+ metrics_updates["magic__drc_error__count"] = bbox_count
413
+
414
+ return views_updates, metrics_updates
415
+
416
+
417
+ @Step.factory.register()
418
+ class SpiceExtraction(MagicStep):
419
+ """
420
+ Extracts a SPICE netlist from the GDSII stream. Used in Layout vs. Schematic
421
+ checks.
422
+
423
+ Also, the metrics will be updated with ``magic__illegal_overlap__count``. You can use
424
+ `the relevant checker <#Checker.IllegalOverlap>`_ to quit if that number is
425
+ nonzero.
426
+ """
427
+
428
+ id = "Magic.SpiceExtraction"
429
+ name = "SPICE Extraction"
430
+ long_name = "SPICE Model Extraction"
431
+
432
+ inputs = [DesignFormat.GDS, DesignFormat.DEF]
433
+ outputs = [DesignFormat.SPICE]
434
+
435
+ config_vars = MagicStep.config_vars + [
436
+ Variable(
437
+ "MAGIC_EXT_USE_GDS",
438
+ bool,
439
+ "A flag to choose whether to use GDS for spice extraction or not. If not, then the extraction will be done using the DEF/LEF, which is faster.",
440
+ default=False,
441
+ ),
442
+ Variable(
443
+ "MAGIC_EXT_ABSTRACT_CELLS",
444
+ Optional[List[str]],
445
+ "A list of regular experssions which are matched against the cells of a "
446
+ + "the design. Matches are abstracted (black-boxed) during SPICE extraction.",
447
+ ),
448
+ Variable(
449
+ "MAGIC_NO_EXT_UNIQUE",
450
+ bool,
451
+ "Enables connections by label in LVS by skipping `extract unique` in Magic extractions.",
452
+ default=False,
453
+ deprecated_names=["LVS_CONNECT_BY_LABEL"],
454
+ ),
455
+ Variable(
456
+ "MAGIC_EXT_SHORT_RESISTOR",
457
+ bool,
458
+ "Enables adding resistors to shorts- resolves LVS issues if more than one top-level pin is connected to the same net, but may increase runtime and break some designs. Proceed with caution.",
459
+ default=False,
460
+ ),
461
+ Variable(
462
+ "MAGIC_EXT_ABSTRACT",
463
+ bool,
464
+ "Extracts a SPICE netlist based on black-boxed standard cells and macros (basically, anything with a LEF) rather than transistors. An error will be thrown if both this and `MAGIC_EXT_USE_GDS` is set to ``True``.",
465
+ default=False,
466
+ ),
467
+ ]
468
+
469
+ def get_script_path(self):
470
+ return os.path.join(get_script_dir(), "magic", "extract_spice.tcl")
471
+
472
+ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
473
+ if self.config["MAGIC_EXT_USE_GDS"] and self.config["MAGIC_EXT_ABSTRACT"]:
474
+ raise StepException(
475
+ "'MAGIC_EXT_USE_GDS' and 'MAGIC_EXT_ABSTRACT' cannot be both set to 'True'. The step cannot run."
476
+ )
477
+
478
+ kwargs, env = self.extract_env(kwargs)
479
+
480
+ env["MAGTYPE"] = "maglef" if self.config["MAGIC_EXT_ABSTRACT"] else "mag"
481
+
482
+ views_updates, metrics_updates = super().run(state_in, env=env, **kwargs)
483
+
484
+ cif_scale = Decimal(open(os.path.join(self.step_dir, "cif_scale.txt")).read())
485
+ feedback_path = os.path.join(self.step_dir, "feedback.txt")
486
+ try:
487
+ se_feedback, _ = DRCObject.from_magic_feedback(
488
+ open(feedback_path, encoding="utf8"),
489
+ cif_scale,
490
+ self.config["DESIGN_NAME"],
491
+ )
492
+ illegal_overlap_count = functools.reduce(
493
+ lambda a, b: a + len(b.bounding_boxes),
494
+ [
495
+ v
496
+ for v in se_feedback.violations.values()
497
+ if "Illegal overlap" in v.description
498
+ ],
499
+ 0,
500
+ )
501
+ with open(os.path.join(self.step_dir, "feedback.xml"), "wb") as f:
502
+ se_feedback.to_klayout_xml(f)
503
+ metrics_updates["magic__illegal_overlap__count"] = illegal_overlap_count
504
+ except ValueError as e:
505
+ self.warn(
506
+ f"Failed to convert SPICE extraction feedback to KLayout database format: {e}"
507
+ )
508
+ metrics_updates["magic__illegal_overlap__count"] = (
509
+ open(feedback_path, encoding="utf8").read().count("Illegal overlap")
510
+ )
511
+ return views_updates, metrics_updates
512
+
513
+
514
+ @Step.factory.register()
515
+ class OpenGUI(MagicStep):
516
+ """
517
+ Opens the DEF view in the Magic GUI.
518
+ """
519
+
520
+ id = "Magic.OpenGUI"
521
+ name = "Open In GUI"
522
+
523
+ inputs = [DesignFormat.DEF]
524
+ outputs = []
525
+
526
+ config_vars = MagicStep.config_vars + [
527
+ Variable(
528
+ "MAGIC_GUI_USE_GDS",
529
+ bool,
530
+ "Whether to prioritize GDS (if found) when running this step.",
531
+ default=True,
532
+ ),
533
+ ]
534
+
535
+ def get_script_path(self):
536
+ return os.path.join(get_script_dir(), "magic", "open.tcl")
537
+
538
+ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
539
+ kwargs, env = self.extract_env(kwargs)
540
+
541
+ env = self.prepare_env(env, state_in)
542
+ env = self._reroute_env(env)
543
+
544
+ if DesignFormat.GDS in state_in:
545
+ env["CURRENT_GDS"] = self.value_to_tcl(state_in[DesignFormat.GDS])
546
+
547
+ cmd = [
548
+ "magic",
549
+ "-rcfile",
550
+ self.config["MAGICRC"],
551
+ self.get_script_path(),
552
+ ]
553
+
554
+ # Not run_subprocess- need stdin, stdout, stderr to be accessible to the
555
+ # user normally
556
+ magic = subprocess.Popen(
557
+ cmd,
558
+ env=env,
559
+ cwd=self.step_dir,
560
+ )
561
+ try:
562
+ magic.wait()
563
+ except KeyboardInterrupt:
564
+ magic.send_signal(SIGKILL)
565
+
566
+ return {}, {}