siliconcompiler 0.35.4__py3-none-any.whl → 0.36.0__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.
Files changed (59) hide show
  1. siliconcompiler/_metadata.py +1 -1
  2. siliconcompiler/constraints/__init__.py +4 -1
  3. siliconcompiler/constraints/asic_timing.py +230 -38
  4. siliconcompiler/constraints/fpga_timing.py +209 -14
  5. siliconcompiler/constraints/timing_mode.py +82 -0
  6. siliconcompiler/data/templates/tcl/manifest.tcl.j2 +0 -6
  7. siliconcompiler/flowgraph.py +95 -42
  8. siliconcompiler/flows/generate_openroad_rcx.py +2 -2
  9. siliconcompiler/flows/highresscreenshotflow.py +37 -0
  10. siliconcompiler/library.py +2 -1
  11. siliconcompiler/package/__init__.py +39 -45
  12. siliconcompiler/project.py +4 -1
  13. siliconcompiler/scheduler/scheduler.py +64 -35
  14. siliconcompiler/scheduler/schedulernode.py +5 -2
  15. siliconcompiler/scheduler/slurm.py +7 -6
  16. siliconcompiler/scheduler/taskscheduler.py +19 -16
  17. siliconcompiler/schema/_metadata.py +1 -1
  18. siliconcompiler/schema/namedschema.py +2 -4
  19. siliconcompiler/schema_support/cmdlineschema.py +0 -3
  20. siliconcompiler/schema_support/dependencyschema.py +0 -6
  21. siliconcompiler/schema_support/record.py +4 -3
  22. siliconcompiler/tool.py +58 -27
  23. siliconcompiler/tools/_common/tcl/sc_schema_access.tcl +0 -6
  24. siliconcompiler/tools/keplerformal/__init__.py +7 -0
  25. siliconcompiler/tools/keplerformal/lec.py +112 -0
  26. siliconcompiler/tools/klayout/screenshot.py +66 -1
  27. siliconcompiler/tools/klayout/scripts/klayout_export.py +10 -40
  28. siliconcompiler/tools/klayout/scripts/klayout_show.py +4 -4
  29. siliconcompiler/tools/klayout/scripts/klayout_utils.py +13 -1
  30. siliconcompiler/tools/montage/tile.py +26 -12
  31. siliconcompiler/tools/openroad/_apr.py +26 -10
  32. siliconcompiler/tools/openroad/init_floorplan.py +5 -2
  33. siliconcompiler/tools/openroad/power_grid_analysis.py +1 -1
  34. siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +49 -14
  35. siliconcompiler/tools/openroad/scripts/apr/sc_irdrop.tcl +6 -4
  36. siliconcompiler/tools/openroad/scripts/apr/sc_write_data.tcl +4 -4
  37. siliconcompiler/tools/openroad/scripts/common/procs.tcl +14 -5
  38. siliconcompiler/tools/openroad/scripts/common/read_liberty.tcl +2 -2
  39. siliconcompiler/tools/openroad/scripts/common/reports.tcl +6 -3
  40. siliconcompiler/tools/openroad/scripts/common/screenshot.tcl +1 -1
  41. siliconcompiler/tools/openroad/scripts/common/write_data_physical.tcl +8 -0
  42. siliconcompiler/tools/openroad/scripts/common/write_images.tcl +16 -12
  43. siliconcompiler/tools/openroad/scripts/sc_rdlroute.tcl +3 -1
  44. siliconcompiler/tools/openroad/write_data.py +2 -2
  45. siliconcompiler/tools/opensta/scripts/sc_check_library.tcl +2 -2
  46. siliconcompiler/tools/opensta/scripts/sc_report_libraries.tcl +2 -2
  47. siliconcompiler/tools/opensta/scripts/sc_timing.tcl +12 -14
  48. siliconcompiler/tools/opensta/timing.py +6 -2
  49. siliconcompiler/toolscripts/_tools.json +9 -4
  50. siliconcompiler/toolscripts/ubuntu22/install-keplerformal.sh +72 -0
  51. siliconcompiler/toolscripts/ubuntu24/install-keplerformal.sh +72 -0
  52. siliconcompiler/utils/multiprocessing.py +11 -0
  53. siliconcompiler/utils/settings.py +70 -49
  54. {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.0.dist-info}/METADATA +3 -3
  55. {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.0.dist-info}/RECORD +59 -53
  56. {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.0.dist-info}/WHEEL +0 -0
  57. {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.0.dist-info}/entry_points.txt +0 -0
  58. {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.0.dist-info}/licenses/LICENSE +0 -0
  59. {siliconcompiler-0.35.4.dist-info → siliconcompiler-0.36.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,112 @@
1
+ import os.path
2
+
3
+ from siliconcompiler import Task
4
+ from siliconcompiler.utils import sc_open
5
+
6
+
7
+ class LECTask(Task):
8
+ """LEC task using Kepler-formal for logical equivalence checking."""
9
+ def tool(self) -> str:
10
+ return "kepler-formal"
11
+
12
+ def task(self) -> str:
13
+ return "lec"
14
+
15
+ def setup(self) -> None:
16
+ super().setup()
17
+
18
+ self.set_exe("kepler-formal")
19
+
20
+ # Log file handled by kepler-formal config
21
+ self.set_logdestination("stdout", "none")
22
+ self.set_logdestination("stderr", "none")
23
+
24
+ if f"{self.design_topmodule}.lec.vg" in self.get_files_from_input_nodes():
25
+ inputnodes = self.get_files_from_input_nodes()[f"{self.design_topmodule}.lec.vg"]
26
+ if len(inputnodes) != 2:
27
+ raise ValueError("LEC requires exactly two input netlists for comparison.")
28
+ for node in inputnodes:
29
+ self.add_input_file(
30
+ self.compute_input_file_node_name(f"{self.design_topmodule}.lec.vg", *node))
31
+
32
+ scenarios = self.project.constraint.timing.get_scenario()
33
+ if not scenarios:
34
+ raise ValueError("LEC requires at least one timing scenario to determine "
35
+ "library corners.")
36
+ scenario = list(scenarios.values())[0]
37
+ libcorners = scenario.get_libcorner(self.step, self.index)
38
+ delay_model = self.project.get("asic", "delaymodel")
39
+ for asiclib in self.project.get("asic", "asiclib"):
40
+ lib = self.project.get("library", asiclib, field="schema")
41
+ for corner in libcorners:
42
+ if not lib.valid("asic", "libcornerfileset", corner, delay_model):
43
+ continue
44
+ self.add_required_key(lib, "asic", "libcornerfileset", corner, delay_model)
45
+ for fileset in lib.get("asic", "libcornerfileset", corner, delay_model):
46
+ self.add_required_key(lib, "fileset", fileset, "file", "liberty")
47
+
48
+ def __config_file(self) -> str:
49
+ return "lec.yaml"
50
+
51
+ def pre_process(self):
52
+ super().pre_process()
53
+
54
+ with open(self.__config_file(), "w") as f:
55
+ f.write("format: verilog\n")
56
+ f.write("input_paths:\n")
57
+ for node in self.get_files_from_input_nodes()[f"{self.design_topmodule}.lec.vg"]:
58
+ filename = self.compute_input_file_node_name(f"{self.design_topmodule}.lec.vg",
59
+ *node)
60
+ f.write(f" - inputs/{filename}\n")
61
+ f.write("liberty_files:\n")
62
+
63
+ scenario = list(self.project.constraint.timing.get_scenario().values())[0]
64
+ libcorners = scenario.get_libcorner(self.step, self.index)
65
+ delay_model = self.project.get("asic", "delaymodel")
66
+ for asiclib in self.project.get("asic", "asiclib"):
67
+ lib = self.project.get("library", asiclib, field="schema")
68
+ for corner in libcorners:
69
+ if not lib.valid("asic", "libcornerfileset", corner, delay_model):
70
+ continue
71
+ for fileset in lib.get("asic", "libcornerfileset", corner, delay_model):
72
+ for file in lib.get_file(fileset=fileset, filetype="liberty"):
73
+ f.write(f" - {file}\n")
74
+ f.write(f"log_file: {self.get_logpath('exe')}\n")
75
+
76
+ def runtime_options(self):
77
+ options = super().runtime_options()
78
+ options.append("--config")
79
+ options.append(self.__config_file())
80
+ return options
81
+
82
+ def post_process(self):
83
+ super().post_process()
84
+ # Kepler-formal writes its own log file; nothing to do here.
85
+ errors = 0
86
+ log = self.get_logpath('exe')
87
+ if os.path.exists(log):
88
+ with sc_open(log, 'r') as f:
89
+ for logline in f:
90
+ if "Found difference " in logline:
91
+ errors += 1
92
+
93
+ self.record_metric("drvs", errors, source_file=log)
94
+
95
+ @classmethod
96
+ def make_docs(cls):
97
+ from siliconcompiler import Flowgraph, Design, ASIC
98
+ from siliconcompiler.scheduler import SchedulerNode
99
+ from siliconcompiler.targets import freepdk45_demo
100
+ design = Design("<design>")
101
+ with design.active_fileset("docs"):
102
+ design.set_topmodule("top")
103
+ proj = ASIC(design)
104
+ proj.add_fileset("docs")
105
+ freepdk45_demo(proj)
106
+ flow = Flowgraph("docsflow")
107
+ flow.node("<step>", cls(), index="<index>")
108
+ proj.set_flow(flow)
109
+
110
+ node = SchedulerNode(proj, "<step>", "<index>")
111
+ node.setup()
112
+ return node.task
@@ -1,3 +1,5 @@
1
+ from typing import Optional, Union
2
+
1
3
  from siliconcompiler import ScreenshotTask, Task
2
4
  from siliconcompiler.tools.klayout.show import ShowTask
3
5
 
@@ -20,7 +22,70 @@ class ScreenshotParams(Task):
20
22
  self.add_parameter("show_linewidth", "int",
21
23
  "Width of lines in detailed screenshots", defvalue=0, unit="px")
22
24
  self.add_parameter("show_oversampling", "int",
23
- "Image oversampling used in detailed screenshots'", defvalue=2)
25
+ "Image oversampling used in detailed screenshots", defvalue=2)
26
+
27
+ def set_klayout_bins(self, xbins: int, ybins: int,
28
+ step: Optional[str] = None, index: Optional[Union[str, int]] = None):
29
+ """
30
+ Set the number of bins for KLayout screenshotting.
31
+
32
+ Args:
33
+ xbins (int): Number of bins along the x-axis.
34
+ ybins (int): Number of bins along the y-axis.
35
+ step (Optional[str]): Flow step to set the parameter for. Defaults to None.
36
+ index (Optional[Union[str, int]]): Index to set the parameter for. Defaults to None.
37
+ """
38
+ self.set("var", "show_bins", (xbins, ybins), step=step, index=index)
39
+
40
+ def set_klayout_margin(self, margin: float,
41
+ step: Optional[str] = None, index: Optional[Union[str, int]] = None):
42
+ """
43
+ Set the margin for KLayout screenshotting.
44
+
45
+ Args:
46
+ margin (float): Margin around the design in microns.
47
+ step (Optional[str]): Flow step to set the parameter for. Defaults to None.
48
+ index (Optional[Union[str, int]]): Index to set the parameter for. Defaults to None.
49
+ """
50
+ self.set("var", "show_margin", margin, step=step, index=index)
51
+
52
+ def set_klayout_resolution(self, xres: int, yres: int,
53
+ step: Optional[str] = None, index: Optional[Union[str, int]] = None):
54
+ """
55
+ Set the resolution for KLayout screenshotting.
56
+
57
+ Args:
58
+ xres (int): Horizontal resolution in pixels.
59
+ yres (int): Vertical resolution in pixels.
60
+ step (Optional[str]): Flow step to set the parameter for. Defaults to None.
61
+ index (Optional[Union[str, int]]): Index to set the parameter for. Defaults to None.
62
+ """
63
+ self.set("var", "show_resolution", (xres, yres), step=step, index=index)
64
+
65
+ def set_klayout_linewidth(self, linewidth: int,
66
+ step: Optional[str] = None, index: Optional[Union[str, int]] = None):
67
+ """
68
+ Set the linewidth for KLayout screenshotting.
69
+
70
+ Args:
71
+ linewidth (int): Width of lines in detailed screenshots.
72
+ step (Optional[str]): Flow step to set the parameter for. Defaults to None.
73
+ index (Optional[Union[str, int]]): Index to set the parameter for. Defaults to None.
74
+ """
75
+ self.set("var", "show_linewidth", linewidth, step=step, index=index)
76
+
77
+ def set_klayout_oversampling(self, oversampling: int,
78
+ step: Optional[str] = None,
79
+ index: Optional[Union[str, int]] = None):
80
+ """
81
+ Set the oversampling for KLayout screenshotting.
82
+
83
+ Args:
84
+ oversampling (int): Image oversampling used in detailed screenshots.
85
+ step (Optional[str]): Flow step to set the parameter for. Defaults to None.
86
+ index (Optional[Union[str, int]]): Index to set the parameter for. Defaults to None.
87
+ """
88
+ self.set("var", "show_oversampling", oversampling, step=step, index=index)
24
89
 
25
90
  def setup(self):
26
91
  super().setup()
@@ -39,24 +39,23 @@ import sys
39
39
  import fnmatch
40
40
 
41
41
 
42
- def gds_export(design_name, in_def, in_files, out_file, tech, allow_missing, config_file='',
43
- seal_file='',
44
- timestamps=True):
42
+ def gds_export(design_name, in_def, out_file, tech, allow_missing, timestamps=True):
45
43
  from klayout_utils import get_write_options # noqa E402
46
44
 
47
45
  # Load def file
48
46
  main_layout = pya.Layout()
49
47
  main_layout.technology_name = tech.name
50
- main_layout.read(in_def, tech.load_layout_options)
48
+ layout_options = tech.load_layout_options
49
+ main_layout.read(in_def, layout_options)
51
50
 
52
51
  # List cells
53
52
  def_cells = []
54
- for def_cell in main_layout.each_cell():
55
- def_cells.append(def_cell.name)
53
+ for cell_idx in main_layout.cell(design_name).each_child_cell():
54
+ def_cells.append(main_layout.cell_name(cell_idx))
56
55
 
57
- def_cells.remove(design_name)
58
56
  # Remove vias
59
- def_cells = sorted([cell for cell in def_cells if not cell.startswith("VIA_")])
57
+ via_prefix = layout_options.lefdef_config.via_cellname_prefix
58
+ def_cells = sorted([cell for cell in def_cells if not cell.startswith(via_prefix)])
60
59
  print(f"[INFO] Read in {len(def_cells)} cells from DEF file")
61
60
  for cell in def_cells:
62
61
  print(f" [INFO] DEF cell: {cell}")
@@ -65,16 +64,11 @@ def gds_export(design_name, in_def, in_files, out_file, tech, allow_missing, con
65
64
  def_cells.remove(f"{design_name}_DEF_FILL")
66
65
 
67
66
  # Load in the gds to merge
68
- print("[INFO] Merging GDS/OAS files...")
69
- for fil in in_files:
67
+ for fil in layout_options.lefdef_config.macro_layout_files:
70
68
  macro_layout = pya.Layout()
71
69
  macro_layout.read(fil)
72
- print(f"[INFO] Read in {fil}")
73
70
  for cell in list(def_cells):
74
71
  if macro_layout.has_cell(cell):
75
- subcell = main_layout.cell(cell)
76
- print(f" [INFO] Merging in {cell}")
77
- subcell.copy_tree(macro_layout.cell(cell))
78
72
  def_cells.remove(cell)
79
73
 
80
74
  # Copy the top level only to a new layout
@@ -100,18 +94,6 @@ def gds_export(design_name, in_def, in_files, out_file, tech, allow_missing, con
100
94
  if i.name != design_name and i.parent_cells() == 0:
101
95
  print("[ERROR] Found orphan cell '{0}'".format(i.name))
102
96
 
103
- if seal_file:
104
- top_cell = top_only_layout.top_cell()
105
-
106
- print("[INFO] Reading seal GDS/OAS file...")
107
- print("\t{0}".format(seal_file))
108
- top_only_layout.read(seal_file)
109
-
110
- for cell in top_only_layout.top_cells():
111
- if cell != top_cell:
112
- print("[INFO] Merging '{0}' as child of '{1}'".format(cell.name, top_cell.name))
113
- top.insert(pya.CellInstArray(cell.cell_index(), pya.Trans()))
114
-
115
97
  # Write out the GDS
116
98
  print("[INFO] Writing out GDS/OAS '{0}'".format(out_file))
117
99
  top_only_layout.write(out_file, get_write_options(out_file, timestamps))
@@ -125,7 +107,6 @@ def main():
125
107
 
126
108
  from klayout_utils import (
127
109
  technology,
128
- get_streams,
129
110
  save_technology,
130
111
  get_schema,
131
112
  generate_metrics
@@ -155,18 +136,8 @@ def main():
155
136
 
156
137
  out_file = os.path.join('outputs', f'{design}.{sc_stream}')
157
138
 
158
- in_files = []
159
- libs = schema.get("asic", "asiclib")
160
- for lib in libs:
161
- libobj = schema.get("library", lib, field="schema")
162
- for s in get_streams(schema):
163
- for fileset in libobj.get("asic", "aprfileset"):
164
- if libobj.valid("fileset", fileset, "file", s):
165
- in_files.extend(libobj.get("fileset", fileset, "file", s))
166
- break
167
-
168
139
  allow_missing = []
169
- for lib in libs:
140
+ for lib in schema.get("asic", "asiclib"):
170
141
  if schema.valid('library', lib, 'tool', 'klayout', 'allow_missing_cell'):
171
142
  patterns = [pattern for pattern in schema.get('library', lib, 'tool', 'klayout',
172
143
  'allow_missing_cell') if pattern]
@@ -179,8 +150,7 @@ def main():
179
150
 
180
151
  sc_tech = technology(design, schema)
181
152
 
182
- gds_export(design, in_def, in_files, out_file, sc_tech, allow_missing,
183
- config_file='', seal_file='', timestamps=sc_timestamps)
153
+ gds_export(design, in_def, out_file, sc_tech, allow_missing, timestamps=sc_timestamps)
184
154
 
185
155
  if sc_screenshot:
186
156
  show(schema, sc_tech, out_file, f'outputs/{design}.png', screenshot=True)
@@ -25,7 +25,7 @@ def show(schema, tech, input_path, output_path, screenshot=False, report=None):
25
25
  layout_options.lefdef_config.produce_obstructions = True
26
26
 
27
27
  # Always use LEF geometry even when LEF file contains FOREIGN statement.
28
- layout_options.lefdef_config.macro_resolution_mode = 1
28
+ layout_options.lefdef_config.macro_resolution_mode = 0
29
29
 
30
30
  tech.load_layout_options = layout_options
31
31
 
@@ -118,9 +118,9 @@ def __screenshot_montage(schema, view, xbins, ybins):
118
118
 
119
119
  app.set_config("background-color", "#000000") # Black
120
120
 
121
- design = schema.get('option', 'entrypoint')
122
- if not design:
123
- design = schema.get('design')
121
+ design_name = schema.get('option', 'design')
122
+ fileset = schema.get("option", "fileset")[0]
123
+ design = schema.get("library", design_name, "fileset", fileset, "topmodule")
124
124
 
125
125
  horizontal_resolution, vertical_resolution = schema.get(
126
126
  'tool', 'klayout', 'task', task, 'var', 'show_resolution', step=step, index=index)
@@ -76,7 +76,7 @@ def technology(design, schema):
76
76
 
77
77
  layout_options = tech.load_layout_options
78
78
 
79
- layout_options.lefdef_config.macro_resolution_mode = 1
79
+ layout_options.lefdef_config.macro_resolution_mode = 0
80
80
  layout_options.lefdef_config.via_cellname_prefix = "VIA_"
81
81
  pathed_files = set()
82
82
  for lef_file in layout_options.lefdef_config.lef_files:
@@ -98,6 +98,18 @@ def technology(design, schema):
98
98
  for lef_file in layout_options.lefdef_config.lef_files:
99
99
  print(f"[INFO] LEF file: {lef_file}")
100
100
 
101
+ in_files = layout_options.lefdef_config.macro_layout_files
102
+ for lib in schema.get("asic", "asiclib"):
103
+ libobj = schema.get("library", lib, field="schema")
104
+ for s in get_streams(schema):
105
+ for fileset in libobj.get("asic", "aprfileset"):
106
+ if libobj.valid("fileset", fileset, "file", s):
107
+ in_files.extend(libobj.get("fileset", fileset, "file", s))
108
+ break
109
+ layout_options.lefdef_config.macro_layout_files = in_files
110
+ for lef_file in layout_options.lefdef_config.macro_layout_files:
111
+ print(f"[INFO] Layout file: {lef_file}")
112
+
101
113
  # Set layer properties
102
114
  layer_properties = tech.layer_properties_file
103
115
  if layer_properties:
@@ -1,20 +1,11 @@
1
- from siliconcompiler import Task
1
+ from typing import Optional, Union
2
+
3
+ from siliconcompiler import Task, TaskSkip
2
4
 
3
5
 
4
6
  class TileTask(Task):
5
7
  '''
6
8
  Tiles input images into a single output image.
7
-
8
- Notes:
9
- Need to make ensure that /etc/ImageMagick-6/policy.xml
10
- <policy domain="resource" name="memory" value="8GiB"/>
11
- <policy domain="resource" name="map" value="8GiB"/>
12
- <policy domain="resource" name="width" value="32KP"/>
13
- <policy domain="resource" name="height" value="32KP"/>
14
- <policy domain="resource" name="area" value="1GP"/>
15
- <policy domain="resource" name="disk" value="8GiB"/>
16
- This ensures there are enough resources available to generate
17
- the final image.
18
9
  '''
19
10
 
20
11
  def __init__(self):
@@ -23,6 +14,19 @@ class TileTask(Task):
23
14
  self.add_parameter("bins", "(int,int)", "Number of bins along the (x, y)-axis",
24
15
  defvalue=(2, 2))
25
16
 
17
+ def set_montage_bins(self, xbins: int, ybins: int,
18
+ step: Optional[str] = None, index: Optional[Union[str, int]] = None):
19
+ """
20
+ Set the number of bins for Montage tiling.
21
+
22
+ Args:
23
+ xbins (int): Number of bins along the x-axis.
24
+ ybins (int): Number of bins along the y-axis.
25
+ step (Optional[str]): Flow step to set the parameter for. Defaults to None.
26
+ index (Optional[Union[str, int]]): Index to set the parameter for. Defaults to None.
27
+ """
28
+ self.set("var", "bins", (xbins, ybins), step=step, index=index)
29
+
26
30
  def tool(self):
27
31
  return "montage"
28
32
 
@@ -41,12 +45,22 @@ class TileTask(Task):
41
45
 
42
46
  xbins, ybins = self.get("var", "bins")
43
47
 
48
+ if f"{self.design_topmodule}.png" in self.get_files_from_input_nodes():
49
+ raise TaskSkip("Input provides a single image; skipping tiling.")
50
+
44
51
  for x in range(xbins):
45
52
  for y in range(ybins):
46
53
  self.add_input_file(f'{self.design_topmodule}_X{x}_Y{y}.png')
47
54
 
48
55
  self.add_output_file(ext="png")
49
56
 
57
+ self.add_commandline_option(['-limit', 'memory', '8GiB'])
58
+ self.add_commandline_option(['-limit', 'map', '8GiB'])
59
+ self.add_commandline_option(['-limit', 'disk', '8GiB'])
60
+ self.add_commandline_option(['-limit', 'width', '32KP'])
61
+ self.add_commandline_option(['-limit', 'height', '32KP'])
62
+ self.add_commandline_option(['-limit', 'area', '1GP'])
63
+
50
64
  def runtime_options(self):
51
65
  options = super().runtime_options()
52
66
 
@@ -374,9 +374,12 @@ class OpenROADDRTParameter(_OpenROADDRTCommonParameter):
374
374
  self.add_parameter("drt_disable_via_gen", "bool",
375
375
  "true/false, when true turns off via generation in detailed router "
376
376
  "and only uses the specified tech vias", defvalue=False)
377
- self.add_parameter("drt_via_in_pin_bottom_layer", "str", "TODO")
378
- self.add_parameter("drt_via_in_pin_top_layer", "str", "TODO")
379
- self.add_parameter("drt_repair_pdn_vias", "str", "TODO")
377
+ self.add_parameter("drt_via_in_pin_bottom_layer", "str",
378
+ "bottom layer to allow vias inside pins")
379
+ self.add_parameter("drt_via_in_pin_top_layer", "str",
380
+ "top layer to allow vias inside pins")
381
+ self.add_parameter("drt_repair_pdn_vias", "str",
382
+ "layer to repair PDN vias on")
380
383
 
381
384
  self.add_parameter("drt_report_interval", "int",
382
385
  "reporting interval in steps for generating a DRC report.", defvalue=5)
@@ -635,13 +638,22 @@ class APRTask(OpenROADTask):
635
638
  def _add_pnr_outputs(self):
636
639
  self.add_output_file(ext="sdc")
637
640
  self.add_output_file(ext="vg")
641
+ self.add_output_file(ext="lec.vg")
638
642
  self.add_output_file(ext="def")
639
643
  self.add_output_file(ext="odb")
640
644
 
645
+ for lib in self.project.get("asic", "asiclib"):
646
+ libobj = self.project.get("library", lib, field="schema")
647
+ for celltype in ["decap", "tie", "filler", "tap", "endcap", "antenna", "physicalonly"]:
648
+ if libobj.valid("asic", "cells", celltype) and \
649
+ libobj.get("asic", "cells", celltype):
650
+ self.add_required_key(libobj, "asic", "cells", celltype)
651
+
641
652
  def _get_pex_mapping(self):
642
653
  corners = {}
643
- for constraint in self.project.getkeys('constraint', 'timing'):
644
- pexcorner = self.project.get('constraint', 'timing', constraint, 'pexcorner',
654
+ for constraint in self.project.getkeys('constraint', 'timing', 'scenario'):
655
+ pexcorner = self.project.get('constraint', 'timing', 'scenario',
656
+ constraint, 'pexcorner',
645
657
  step=self.step, index=self.index)
646
658
  if pexcorner:
647
659
  corners[constraint] = pexcorner
@@ -649,13 +661,17 @@ class APRTask(OpenROADTask):
649
661
  return corners
650
662
 
651
663
  def _get_constraint_by_check(self, check: str) -> str:
652
- for constraint in self.project.getkeys('constraint', 'timing'):
653
- if check in self.project.get('constraint', 'timing', constraint, 'check',
664
+ for constraint in self.project.getkeys('constraint', 'timing', 'scenario'):
665
+ if check in self.project.get('constraint', 'timing', 'scenario',
666
+ constraint, 'check',
654
667
  step=self.step, index=self.index):
655
668
  return constraint
656
669
 
657
670
  # if not specified, just pick the first constraint available
658
- return self.project.getkeys('constraint', 'timing')[0]
671
+ scenarios = self.project.getkeys('constraint', 'timing', 'scenario')
672
+ if not scenarios:
673
+ raise ValueError("No timing scenarios defined in project constraints.")
674
+ return scenarios[0]
659
675
 
660
676
  def _build_pex_estimation_file(self):
661
677
  corners = self._get_pex_mapping()
@@ -761,9 +777,9 @@ class APRTask(OpenROADTask):
761
777
  ],
762
778
  "peakpower": [
763
779
  *[f"power/{corner}.rpt"
764
- for corner in self.project.getkeys('constraint', 'timing')],
780
+ for corner in self.project.getkeys('constraint', 'timing', 'scenario')],
765
781
  *[f"images/heatmap/power_density/{corner}.png"
766
- for corner in self.project.getkeys('constraint', 'timing')]
782
+ for corner in self.project.getkeys('constraint', 'timing', 'scenario')]
767
783
  ],
768
784
  "drvs": [
769
785
  "timing/drv_violators.rpt",
@@ -149,8 +149,11 @@ class InitFloorplanTask(APRTask,
149
149
 
150
150
  # Mark requires for components, pin, and floorplan placements
151
151
  for component in self.project.constraint.component.get_component().values():
152
- self.add_required_key(component, "placement")
153
- self.add_required_key(component, "rotation")
152
+ if component.get_placement(step=self.step, index=self.index) is not None:
153
+ self.add_required_key(component, "placement")
154
+ self.add_required_key(component, "rotation")
155
+ if component.get_halo(step=self.step, index=self.index) is not None:
156
+ self.add_required_key(component, "halo")
154
157
  if component.get_partname(step=self.step, index=self.index):
155
158
  self.add_required_key(component, "partname")
156
159
 
@@ -142,7 +142,7 @@ class PowerGridAnalysisTask(APRTask, OpenROADPSMParameter, OpenROADSTAParameter)
142
142
  super().setup()
143
143
  self.set_script("apr/sc_irdrop.tcl")
144
144
 
145
- self.add_version(">=v2.0-26750", clobber=True)
145
+ self.add_version(">=v2.0-26866", clobber=True)
146
146
 
147
147
  # Output is not a standard design file, unset default expectation
148
148
  self.unset("output")
@@ -299,6 +299,8 @@ if { [sc_cfg_exists constraint component] } {
299
299
  set x_grid [ord::dbu_to_microns $x_grid]
300
300
  set y_grid [ord::dbu_to_microns $y_grid]
301
301
 
302
+ set sc_placed_insts []
303
+
302
304
  dict for {name params} [sc_cfg_get constraint component] {
303
305
  set location [dict get $params placement]
304
306
  set rotation [sc_convert_rotation [dict get $params rotation]]
@@ -308,11 +310,27 @@ if { [sc_cfg_exists constraint component] } {
308
310
  } else {
309
311
  set cell ""
310
312
  }
313
+ set halo {}
311
314
  if { [llength [dict get $params halo]] != 0 } {
312
- utl::warn FLW 1 "Halo is not supported in OpenROAD"
315
+ if { [llength [dict get $params halo]] == 2 } {
316
+ set halo [dict get $params halo]
317
+ } else {
318
+ utl::warn FLW 1 "Halo must be a list of 2 elements"
319
+ }
313
320
  }
314
321
 
315
- set inst [[ord::get_db_block] findInst $name]
322
+ set stainst [get_cells -quiet $name]
323
+ if { $stainst != {} } {
324
+ if { [llength $stainst] > 1 } {
325
+ utl::error FLW 1 "Multiple cells found for instance $name"
326
+ }
327
+ set inst [sta::sta_to_db_inst $stainst]
328
+ } else {
329
+ set inst "NULL"
330
+ }
331
+ if { $inst == "NULL" } {
332
+ set inst [[ord::get_db_block] findInst $name]
333
+ }
316
334
  if { $inst == "NULL" } {
317
335
  utl::warn FLW 1 "Could not find instance: $name"
318
336
 
@@ -324,23 +342,40 @@ if { [sc_cfg_exists constraint component] } {
324
342
  set cell ""
325
343
  }
326
344
 
327
- set x_loc [expr { round([lindex $location 0] / $x_grid) * $x_grid }]
328
- set y_loc [expr { round([lindex $location 1] / $y_grid) * $y_grid }]
345
+ if { $inst != "NULL" } {
346
+ set name [$inst getName]
347
+ }
348
+
349
+ if { [llength $location] == 2 } {
350
+ # Only place if location is specified
351
+ set x_loc [expr { round([lindex $location 0] / $x_grid) * $x_grid }]
352
+ set y_loc [expr { round([lindex $location 1] / $y_grid) * $y_grid }]
353
+
354
+ set place_inst_args []
355
+ if { $cell != "" } {
356
+ lappend place_inst_args -cell $cell
357
+ }
329
358
 
330
- set place_inst_args []
331
- if { $cell != "" } {
332
- lappend place_inst_args -cell $cell
359
+ place_inst \
360
+ -name $name \
361
+ -origin "$x_loc $y_loc" \
362
+ -orient $rotation \
363
+ -status FIRM \
364
+ {*}$place_inst_args
333
365
  }
334
366
 
335
- place_inst \
336
- -name $name \
337
- -location "$x_loc $y_loc" \
338
- -orient $rotation \
339
- -status FIRM \
340
- {*}$place_inst_args
367
+ if { $halo != {} } {
368
+ set inst [[ord::get_db_block] findInst $name]
369
+ odb::dbBox_create $inst \
370
+ [ord::microns_to_dbu [lindex $halo 0]] \
371
+ [ord::microns_to_dbu [lindex $halo 1]] \
372
+ [ord::microns_to_dbu [lindex $halo 0]] \
373
+ [ord::microns_to_dbu [lindex $halo 1]]
374
+ }
375
+ lappend sc_placed_insts $name
341
376
  }
342
377
 
343
- sc_print_macro_information
378
+ sc_print_macro_information $sc_placed_insts
344
379
  }
345
380
 
346
381
  if { [sc_check_version 23008] } {
@@ -89,7 +89,8 @@ foreach net $nets {
89
89
  foreach corner $sc_scenarios {
90
90
  analyze_power_grid -net $net -corner $corner -allow_reuse
91
91
 
92
- save_animated_gif -start "reports/${net}/${corner}.gif"
92
+ set gif [save_animated_gif -start "reports/${net}/${corner}.gif"]
93
+ set gif_log [save_animated_gif -start "reports/${net}/${corner}_log.gif"]
93
94
 
94
95
  foreach layer [[ord::get_db_tech] getLayers] {
95
96
  if { [$layer getRoutingLevel] == 0 } {
@@ -123,7 +124,7 @@ foreach net $nets {
123
124
  sc_save_image \
124
125
  "IR drop for $net on $layer_name for $corner heatmap" \
125
126
  reports/${net}/${corner}.${layer_name}.png \
126
- true
127
+ $gif
127
128
 
128
129
  gui::set_heatmap IRDrop LogScale 1
129
130
  gui::set_heatmap IRDrop rebuild
@@ -131,7 +132,7 @@ foreach net $nets {
131
132
  sc_save_image \
132
133
  "IR drop for $net on $layer_name for $corner heatmap" \
133
134
  reports/${net}/${corner}.${layer_name}_log.png \
134
- false
135
+ $gif_log
135
136
 
136
137
  gui::set_display_controls "Heat Maps/IR Drop" visible false
137
138
 
@@ -139,7 +140,8 @@ foreach net $nets {
139
140
  gui::delete_label $label
140
141
  }
141
142
  }
142
- save_animated_gif -end
143
+ save_animated_gif -end -key $gif
144
+ save_animated_gif -end -key $gif_log
143
145
  }
144
146
  }
145
147