From 5e3cfb37bd5ede464769479889bb6ad38e2509e6 Mon Sep 17 00:00:00 2001 From: Harrison Liew Date: Mon, 29 Apr 2024 22:07:21 -0700 Subject: [PATCH 1/2] Hier constraints from different files (#853) * restore Colin's code to allow vlsi.inputs.hierarchical.constraints to come from different files for different modules * vlsi.input.hierarchical.constraints for each module is now a dict, like in flat flow. Meta directives inside now work. Legacy list structure still supported. * extraneous prints * remove module-level constraints from -output-full.json so as not to pollute parent config * more robust, passes CI * method of restoring global configs was wrong, hierarchical placement constraints no longer need to specified separately * output compressed gds --------- Co-authored-by: dpgrubb13 --- doc/Hammer-Use/Hierarchical.rst | 53 +++------ e2e/configs-design/pass/mock_hier.yml | 37 ++++-- e2e/configs-design/pass/mock_hier_addl.yml | 6 + e2e/configs-design/pass/mock_hier_legacy.yml | 53 +++++++++ hammer/config/config_src.py | 4 +- hammer/config/defaults.yml | 14 +-- hammer/config/defaults_types.yml | 10 +- hammer/par/innovus/__init__.py | 2 +- hammer/vlsi/cli_driver.py | 72 ++++++------ hammer/vlsi/driver.py | 117 ++++++++++++------- 10 files changed, 234 insertions(+), 134 deletions(-) create mode 100644 e2e/configs-design/pass/mock_hier_addl.yml create mode 100644 e2e/configs-design/pass/mock_hier_legacy.yml diff --git a/doc/Hammer-Use/Hierarchical.rst b/doc/Hammer-Use/Hierarchical.rst index ae0263964..d81fe10df 100644 --- a/doc/Hammer-Use/Hierarchical.rst +++ b/doc/Hammer-Use/Hierarchical.rst @@ -11,7 +11,7 @@ Hierarchical Hammer Config The hierarchal flow is controlled in the ``vlsi.inputs.hierarchal`` namespace. To specify hierarchical mode, you must specify the following keys. In this example, we have our top module set as ``ChipTop``, with a submodule ``ModuleA`` and another submdule ``ModuleAA`` below that (these are names of Verilog modules). .. code-block:: yaml - + vlsi.inputs.hierarchical: mode: hierarchical top_module: ChipTop @@ -23,42 +23,31 @@ The hierarchal flow is controlled in the ``vlsi.inputs.hierarchal`` namespace. T - ModuleAA constraints: - ChipTop: - - vlsi.core... - - vlsi.inputs... + vlsi.core... + vlsi.inputs... - ModuleA: - - vlsi.core... - - vlsi.inputs... + vlsi.core... + vlsi.inputs... - ModuleAA: - - vlsi.core... - - vlsi.inputs... + vlsi.core... + vlsi.inputs... -Note how the configuration specific to each module in ``vlsi.inputs.hierarchical.constraints`` are list items, whereas in a flat flow, they would be at the root level. +Note how the configuration specific to each module in ``vlsi.inputs.hierarchical.constraints`` appears like they would in a flat flow. This means you can apply meta directives to them as normal, as long as the references are in the same parent dictionary (i.e., key = module name). +If you have constraints for the same module in multiple files, you can use ``vlsi.inputs.hierarchical.constraints_meta: append`` and the constraints will be combined properly. -Placement constraints for each module, however, are not specified here. Instead, they should be specified in ``vlsi.inputs.hierarchical.manual_placement_constraints``. The parameters such as ``x``, ``y``, ``width``, ``height``, etc. are omitted from each constraint for clarity. In the bottom-up hierarchal flow, instances of submodules are of ``type: hardmacro`` because they are hardened from below. +.. note:: + In a bottom-up hierarchical flow, submodule instances must have ``type: hardmacro`` in ``vlsi.inputs.placement_constraints`` because they are hardened from below. -.. code-block:: yaml +Special considerations for legacy support: - vlsi.inputs.hierarchical: - manual_placement_constraints_meta: append - manual_placement_constraints: - - ChipTop: - - path: "ChipTop" - type: toplevel - - path: "ChipTop/path/to/instance/of/ModuleA" - type: hardmacro - - ModuleA: - - path: "ModuleA" - type: toplevel - - path: "ModuleA/path/to/instance/of/ModuleAA" - type: hardmacro - - ModuleAA: - - path: "moduleAA" - type: toplevel +* If each module's ``constraints`` is a list of dicts with a single key/value pair, meta actions are not supported and the entire project configuration must be specified in a single file. + +* If placement constraints are specified with ``vlsi.inputs.hierarchical.manual_placement_constraints``, all of a given module's placement constraints must be specified in a single file. Flow Management and Actions --------------------------- -Based on the structure in ``vlsi.inputs.hierarchical.manual_modules``, Hammer constructs a hierarchical flow graph of dependencies. In this particular example, synthesis and place-and-route of ``ModuleAA`` will happen first. Synthesis of ``ModuleA`` will then depend on the place-and-route output of ``ModuleAA``, and so forth. +Based on the structure in ``vlsi.inputs.hierarchical.manual_modules``, Hammer constructs a hierarchical flow graph of dependencies. In this particular example, synthesis and place-and-route of ``ModuleAA`` will happen first. Synthesis of ``ModuleA`` will then depend on the place-and-route output of ``ModuleAA``, and so forth. These are enumerated in the auto-generated Makefile, ``hammer.d``, which is placed in the directory pointed to by the ``--obj_dir`` command line flag when the ``buildfile`` action is run. This action must be run BEFORE executing your flow. If you adjust the hierarchy, you must re-run this action. @@ -93,15 +82,7 @@ In a bottom-up hierarchical flow, is is important to remember that submodules do Special Notes & Limitations --------------------------- -#. Hammer IR keys propagate up through the hierarchical tree. For example, if ``vlsi.inputs.clocks`` was specified in the constraints for ``ModuleAA`` but not for ``ModuleA``, ``ModuleA`` will inherit ``ModuleAA``'s constraints. Take special care of where your constraints come from, especially for a parent module with more than one submodule. To avoid confusion, it is recommended to specify the same set of keys for every module. - -#. Hammer IR keys specified at the root level (i.e. outside of ``vlsi.inputs.hierarchical.constraints``) do not override the corresponding submodule constraints. However, if you add a Hammer IR file using ``-p`` on the command line (after the file containing ``vlsi.inputs.hierarchical.constraints``), those keys are global and override submodule constraints unless a meta action is specified. To avoid confusion, it is recommended to specify all constraints with ``vlsi.inputs.hierarchical.constraints``. - -#. Due to the structure of ``vlsi.inputs.hierarchical.constraints`` as a list structure, currently, there are the following limitations: - - * You must include all of the constraints in a single file. The config parser is unable to combine constraints from differnt files because most meta actions do not work on list items (advanced users will need to use ``deepsubst``). This will make it harder for collaboration, and unfortunately, changes to module constraints at a higher level of hierarchy after submodules are hardened will trigger the Make dependencies, so you will need to modify the generated Makefile or use redo-targets. - - * Other issues have been observed, such as the bump API failing (see `this issue `_ at the top module level. This is caused by similar mechanisms as above. The workaround is to ensure that bumps are specified at the root level for only the top module and the bumps step is removed from submodule par actions. +#. Hammer IR keys specified at the root level (i.e. outside of ``vlsi.inputs.hierarchical.constraints``) are overridden by the corresponding submodule constraints. Generally, to avoid confusion, it is recommended to specify all constraints module-by-module with ``vlsi.inputs.hierarchical.constraints``. #. Most Hammer APIs are not yet intelligent enough to constrain across hierarchical boundaries. For example: diff --git a/e2e/configs-design/pass/mock_hier.yml b/e2e/configs-design/pass/mock_hier.yml index 40ef02692..96e381842 100644 --- a/e2e/configs-design/pass/mock_hier.yml +++ b/e2e/configs-design/pass/mock_hier.yml @@ -21,13 +21,36 @@ vlsi.inputs.hierarchical: - SubModD - SubModB: - SubModE - manual_placement_constraints: - - ChipTop: [] - - SubModA: [] - - SubModB: [] - - SubModC: [] - - SubModD: [] - - SubModE: [] + constraints: + - ChipTop: + vlsi.inputs.placement_constraints: + - path: ChipTop + type: toplevel + x: 0 + y: 0 + width: 1000 + height: 1000 + margins: + left: 0 + right: 0 + top: 0 + bottom: 0 + - SubModC: + vlsi.inputs.placement_constraints: + - path: SubModC + type: toplevel + x: 0 + y: 0 + width: 100 + height: 100 + margins: + left: 0 + right: 0 + top: 0 + bottom: 0 + vlsi.inputs.power_spec_type: manual + vlsi.inputs.power_spec_contents: fake_command + par.generate_power_straps_options.by_tracks.strap_layers: [m3, m4, m5] vlsi.core.synthesis_tool: "hammer.synthesis.mocksynth" synthesis.mocksynth.temp_folder: "obj_dir" diff --git a/e2e/configs-design/pass/mock_hier_addl.yml b/e2e/configs-design/pass/mock_hier_addl.yml new file mode 100644 index 000000000..1a38c462f --- /dev/null +++ b/e2e/configs-design/pass/mock_hier_addl.yml @@ -0,0 +1,6 @@ +vlsi.inputs.hierarchical.constraints_meta: append +vlsi.inputs.hierarchical.constraints: + - SubModC: + vlsi.inputs.power_spec_type: upf + par.generate_power_straps_options.by_tracks.strap_layers: [m6] + par.generate_power_straps_options.by_tracks.strap_layers_meta: append \ No newline at end of file diff --git a/e2e/configs-design/pass/mock_hier_legacy.yml b/e2e/configs-design/pass/mock_hier_legacy.yml new file mode 100644 index 000000000..914fc9b7e --- /dev/null +++ b/e2e/configs-design/pass/mock_hier_legacy.yml @@ -0,0 +1,53 @@ +# Generate Make include to aid in flow +vlsi.core.build_system: make + +vlsi.inputs.power_spec_type: "cpf" +vlsi.inputs.power_spec_mode: "auto" + +synthesis.inputs: + top_module: "pass" + input_files: ["src/pass.v"] + +vlsi.inputs.hierarchical: + mode: hierarchical + top_module: ChipTop + config_source: manual + manual_modules: + - ChipTop: + - SubModA + - SubModB + - SubModA: + - SubModC + - SubModD + - SubModB: + - SubModE + manual_placement_constraints: + - ChipTop: [] + - SubModA: [] + - SubModB: [] + - SubModC: + - path: SubModC + type: toplevel + x: 0 + y: 0 + width: 100 + height: 100 + margins: + left: 0 + right: 0 + top: 0 + bottom: 0 + - SubModD: [] + - SubModE: [] + constraints: + - SubModC: + vlsi.inputs.power_spec_type: manual + vlsi.inputs.power_spec_contents: fake_command + par.generate_power_straps_options.by_tracks.strap_layers: [m3, m4, m5] + +vlsi.core.synthesis_tool: "hammer.synthesis.mocksynth" +synthesis.mocksynth.temp_folder: "obj_dir" +vlsi.core.par_tool: "hammer.par.mockpar" +vlsi.core.sim_tool: "hammer.sim.mocksim" +vlsi.core.drc_tool: "hammer.drc.mockdrc" +vlsi.core.lvs_tool: "hammer.lvs.mocklvs" diff --git a/hammer/config/config_src.py b/hammer/config/config_src.py index 80493940d..4d92a6b06 100644 --- a/hammer/config/config_src.py +++ b/hammer/config/config_src.py @@ -790,9 +790,9 @@ def runtime(self) -> List[dict]: return [self._runtime] @staticmethod - def internal_keys() -> Set[str]: + def internal_keys() -> List[str]: """Internal keys that shouldn't show up in any final config.""" - return {_CONFIG_PATH_KEY, _NEXT_FREE_INDEX_KEY} + return [_CONFIG_PATH_KEY, _NEXT_FREE_INDEX_KEY] def get_config(self) -> dict: """ diff --git a/hammer/config/defaults.yml b/hammer/config/defaults.yml index 1bacbe229..59f41acf7 100644 --- a/hammer/config/defaults.yml +++ b/hammer/config/defaults.yml @@ -60,13 +60,13 @@ vlsi.technology: bump_block_cut_layer: null # Top cut/via layer for blockage under bumps. (Optional[str]) # Only used if using vlsi.inputs.bumps # TODO: remove this after stackup supports vias ucb-bar/hammer#354 - + tap_cell_interval: 10.0 # Spacing between columns of tap cells (float) # Must be overridden by technology plugin defaults or else you will have DRC/latch-up errors. tap_cell_offset: 0.0 # Offset of the first column of tape cells from the edge of the floorplan (float) # Should be overridden by technology plugin defaults. - + routing_layers: null # If specified, set/override the [bottom, top] layers used for routing. (Optional[Tuple[int, int]]) # Both must match the index number (not name) of layers used in the stackup. @@ -103,7 +103,7 @@ vlsi.technology: timing_lib_pref: "NLDM" # Select a timing lib preference, available options include: - # NLDM, ECSM, and CCS (lower or upper case acceptable). + # NLDM, ECSM, and CCS (lower or upper case acceptable). # If no preference is specified, then the following preference order is followed: # NLDM -> ECSM -> CCS @@ -145,7 +145,7 @@ vlsi.inputs: manual_modules: [] # Manual hierarchical definitions used only if hierarchical_definition_source is set to manual mode. # Should be a list along the lines of [{"module1": "module1_sub1", "module1_sub2", "module2": "module2_sub"}]. - manual_placement_constraints: [] # Manual hierarchical placement constraints used only if hierarchical_definition_source is set to manual mode. + manual_placement_constraints: [] # (Deprecated) Manual hierarchical placement constraints used only if hierarchical_definition_source is set to manual mode. # Should be a list along the lines of [{"module1": }]. constraints: [] # Manual hierarchical constraints. Overrides generic constraints on a per module basis. @@ -212,7 +212,7 @@ vlsi.inputs: custom_sdc_constraints: [] # List of custom sdc constraints to use. (List[str]) # These are appended after all other generated constraints (clock, pin, delay, load, etc.). # IMPORTANT: Since SDC is unitless, you must manually verify that your time & cap units match the tech library's units. - + custom_sdc_files: [] # List of custom sdc files to append. (List[str]) # These are appended after all other generated constraints and custom_sdc_constraints. @@ -328,7 +328,7 @@ vlsi.inputs: # type: float pitch: 0.0 global_x_offset: 0.0 # offset the bump map in the x-axis (float) - global_y_offset: 0.0 # offset the bump map in the y-axis (float) + global_y_offset: 0.0 # offset the bump map in the y-axis (float) cell: "" # cell (str) - Name of the default bump cell assignments: [] # assignments - List of BumpAssignment structs. You must specify one of name or no_connect. # If both are specified the bump will be left unconnected @@ -901,7 +901,7 @@ power.inputs: # "write_profile" - profiles all power types on all categories for all the sub-hierarchies for a given design instance (*.fsdb) # "profile" - run plot_profile + dump_profile # "all" - generate all of the above report formats - + # examples: # report_configs: [{waveform_path: "/path/to/fsdb", module: "chiptop", levels:3, start_time: "0ns", end_time: "1ns", toggle_signal:"/ChipTop/clock", num_toggles:1, frame_count:1000, report_name: "my_fsdb_report"}] # report_configs: [{waveform_path: "/path/to/fsdb", inst: "ChipTop/system/tile_prci_domain/tile_reset_domain_tile", interval_size: "1ns", output_formats: ["plot_profile"]}] diff --git a/hammer/config/defaults_types.yml b/hammer/config/defaults_types.yml index 4bce0845a..f500f292c 100644 --- a/hammer/config/defaults_types.yml +++ b/hammer/config/defaults_types.yml @@ -58,7 +58,7 @@ vlsi.technology: # Offset of the first column of tape cells from the edge of the floorplan # type: float - tap_cell_offset: float + tap_cell_offset: float # Set the [bottom, top] layer used for routing. (Optional[Tuple[int, int]]) routing_layers: Optional[list[int]] @@ -105,7 +105,7 @@ vlsi.inputs: manual_placement_constraints: list[dict[str, list]] # Manual hierarchical constraints. Overrides generic constraints on a per module basis. - constraints: list[dict[str, list]] + constraints: list[dict[str, Any]] # ILMs for hierarchical mode. ilms: list[dict[str, str]] @@ -160,9 +160,9 @@ vlsi.inputs: y: int # pitch (float) - pitch of bumps in microns pitch: float - # global_x_offset (float) - offset the bump map in the x-axis + # global_x_offset (float) - offset the bump map in the x-axis global_x_offset: float - # global_y_offset (float) - offset the bump map in the y-axis + # global_y_offset (float) - offset the bump map in the y-axis global_y_offset: float # cell (str) - Name of the default bump cell cell: str @@ -283,7 +283,7 @@ par: # Spacing around blocks and hardmacros in microns blockage_spacing: float - # Ratio between the power blockage + place halo relative to route blockage. Route blockage should be smaller. + # Ratio between the power blockage + place halo relative to route blockage. Route blockage should be smaller. power_to_route_blockage_ratio: float # Top metal layer that is pulled back around blocks and hardmacros diff --git a/hammer/par/innovus/__init__.py b/hammer/par/innovus/__init__.py index 0c1b95593..da50ef507 100644 --- a/hammer/par/innovus/__init__.py +++ b/hammer/par/innovus/__init__.py @@ -108,7 +108,7 @@ def fill_outputs(self) -> bool: @property def output_gds_filename(self) -> str: - return os.path.join(self.run_dir, "{top}.gds".format(top=self.top_module)) + return os.path.join(self.run_dir, "{top}.gds.gz".format(top=self.top_module)) @property def output_netlist_filename(self) -> str: diff --git a/hammer/vlsi/cli_driver.py b/hammer/vlsi/cli_driver.py index 61430267d..574914862 100644 --- a/hammer/vlsi/cli_driver.py +++ b/hammer/vlsi/cli_driver.py @@ -456,31 +456,6 @@ def create_par_action(self, custom_hooks: List[HammerToolHookAction], return self.create_action("par", hooks if len(hooks) > 0 else None, pre_action_func, post_load_func, post_run_func) - @staticmethod - def get_full_config(driver: HammerDriver, output: dict) -> dict: - """ - Get the full configuration by combining the project config from the - driver with the given output dict (i.e. it contains only - "synthesis.output.blah") that we want to combine with the project - config. - :param driver: HammerDriver that has the full project config. - :param output: Output dict containing specific settings we want to add - to the full project config. - :return: Full project config combined with the output dict - """ - if "vlsi.builtins.is_complete" in output: - if bool(output["vlsi.builtins.is_complete"]): - raise ValueError("Output-only config claims it is complete") - else: - raise ValueError("Output-only config does not appear to be output only") - - output_full = deepdict(driver.project_config) - output_full.update(deepdict(output)) - # Merged configs are always complete - if "vlsi.builtins.is_complete" in output_full: - del output_full["vlsi.builtins.is_complete"] - return output_full - def create_drc_action(self, custom_hooks: List[HammerToolHookAction], pre_action_func: Optional[Callable[[HammerDriver], None]] = None, post_load_func: Optional[Callable[[HammerDriver], None]] = None, @@ -597,13 +572,13 @@ def action(driver: HammerDriver, append_error_func: Callable[[str], None]) -> Op if not success: driver.log.error("Synthesis tool did not succeed") return None + post_run_func_checked(driver) dump_config_to_json_file(os.path.join(driver.syn_tool.run_dir, "syn-output.json"), output) dump_config_to_json_file(os.path.join(driver.syn_tool.run_dir, "syn-output-full.json"), self.get_full_config(driver, output)) if driver.dump_history: dump_config_to_yaml_file(os.path.join(driver.syn_tool.run_dir, "syn-output-history.yml"), add_key_history(self.get_full_config(driver, output), key_history)) - post_run_func_checked(driver) elif action_type == "par": if not driver.load_par_tool(get_or_else(self.par_rundir, "")): return None @@ -617,13 +592,13 @@ def action(driver: HammerDriver, append_error_func: Callable[[str], None]) -> Op if not success: driver.log.error("Place-and-route tool did not succeed") return None + post_run_func_checked(driver) dump_config_to_json_file(os.path.join(driver.par_tool.run_dir, "par-output.json"), output) dump_config_to_json_file(os.path.join(driver.par_tool.run_dir, "par-output-full.json"), self.get_full_config(driver, output)) if driver.dump_history: dump_config_to_yaml_file(os.path.join(driver.par_tool.run_dir, "par-output-history.yml"), add_key_history(self.get_full_config(driver, output), key_history)) - post_run_func_checked(driver) elif action_type == "drc": if not driver.load_drc_tool(get_or_else(self.drc_rundir, "")): return None @@ -637,13 +612,13 @@ def action(driver: HammerDriver, append_error_func: Callable[[str], None]) -> Op if not success: driver.log.error("DRC tool did not succeed") return None + post_run_func_checked(driver) dump_config_to_json_file(os.path.join(driver.drc_tool.run_dir, "drc-output.json"), output) dump_config_to_json_file(os.path.join(driver.drc_tool.run_dir, "drc-output-full.json"), self.get_full_config(driver, output)) if driver.dump_history: dump_config_to_yaml_file(os.path.join(driver.drc_tool.run_dir, "drc-output-history.yml"), add_key_history(self.get_full_config(driver, output), key_history)) - post_run_func_checked(driver) elif action_type == "lvs": if not driver.load_lvs_tool(get_or_else(self.lvs_rundir, "")): return None @@ -657,13 +632,13 @@ def action(driver: HammerDriver, append_error_func: Callable[[str], None]) -> Op if not success: driver.log.error("LVS tool did not succeed") return None + post_run_func_checked(driver) dump_config_to_json_file(os.path.join(driver.lvs_tool.run_dir, "lvs-output.json"), output) dump_config_to_json_file(os.path.join(driver.lvs_tool.run_dir, "lvs-output-full.json"), self.get_full_config(driver, output)) if driver.dump_history: dump_config_to_yaml_file(os.path.join(driver.lvs_tool.run_dir, "lvs-output-history.yml"), add_key_history(self.get_full_config(driver, output), key_history)) - post_run_func_checked(driver) elif action_type == "sram_generator": if not driver.load_sram_generator_tool(get_or_else(self.sram_generator_rundir, "")): return None @@ -691,13 +666,13 @@ def action(driver: HammerDriver, append_error_func: Callable[[str], None]) -> Op if not success: driver.log.error("Sim tool did not succeed") return None + post_run_func_checked(driver) dump_config_to_json_file(os.path.join(driver.sim_tool.run_dir, "sim-output.json"), output) dump_config_to_json_file(os.path.join(driver.sim_tool.run_dir, "sim-output-full.json"), self.get_full_config(driver, output)) if driver.dump_history: dump_config_to_yaml_file(os.path.join(driver.sim_tool.run_dir, "sim-output-history.yml"), add_key_history(self.get_full_config(driver, output), key_history)) - post_run_func_checked(driver) elif action_type == "power": if not driver.load_power_tool(get_or_else(self.power_rundir, "")): return None @@ -711,13 +686,13 @@ def action(driver: HammerDriver, append_error_func: Callable[[str], None]) -> Op if not success: driver.log.error("Power tool did not succeed") return None + post_run_func_checked(driver) dump_config_to_json_file(os.path.join(driver.power_tool.run_dir, "power-output.json"), output) dump_config_to_json_file(os.path.join(driver.power_tool.run_dir, "power-output-full.json"), self.get_full_config(driver, output)) if driver.dump_history: dump_config_to_yaml_file(os.path.join(driver.power_tool.run_dir, "power-output-history.yml"), add_key_history(self.get_full_config(driver, output), key_history)) - post_run_func_checked(driver) elif action_type == "formal": if not driver.load_formal_tool(get_or_else(self.formal_rundir, "")): return None @@ -731,13 +706,13 @@ def action(driver: HammerDriver, append_error_func: Callable[[str], None]) -> Op if not success: driver.log.error("Formal tool did not succeed") return None + post_run_func_checked(driver) dump_config_to_json_file(os.path.join(driver.formal_tool.run_dir, "formal-output.json"), output) dump_config_to_json_file(os.path.join(driver.formal_tool.run_dir, "formal-output-full.json"), self.get_full_config(driver, output)) if driver.dump_history: dump_config_to_yaml_file(os.path.join(driver.formal_tool.run_dir, "formal-output-history.yml"), add_key_history(self.get_full_config(driver, output), key_history)) - post_run_func_checked(driver) elif action_type == "timing": if not driver.load_timing_tool(get_or_else(self.timing_rundir, "")): return None @@ -751,10 +726,10 @@ def action(driver: HammerDriver, append_error_func: Callable[[str], None]) -> Op if not success: driver.log.error("Timing tool did not succeed") return None + post_run_func_checked(driver) dump_config_to_json_file(os.path.join(driver.timing_tool.run_dir, "timing-output.json"), output) dump_config_to_json_file(os.path.join(driver.timing_tool.run_dir, "timing-output-full.json"), self.get_full_config(driver, output)) - post_run_func_checked(driver) elif action_type == "pcb": if not driver.load_pcb_tool(get_or_else(self.pcb_rundir, "")): return None @@ -768,13 +743,13 @@ def action(driver: HammerDriver, append_error_func: Callable[[str], None]) -> Op if not success: driver.log.error("PCB deliverable tool did not succeed") return None + post_run_func_checked(driver) dump_config_to_json_file(os.path.join(driver.pcb_tool.run_dir, "pcb-output.json"), output) dump_config_to_json_file(os.path.join(driver.pcb_tool.run_dir, "pcb-output-full.json"), self.get_full_config(driver, output)) if driver.dump_history: dump_config_to_yaml_file(os.path.join(driver.pcb_tool.run_dir, "pcb-output-history.yml"), add_key_history(self.get_full_config(driver, output), key_history)) - post_run_func_checked(driver) else: raise ValueError("Invalid action_type = " + str(action_type)) # TODO: detect errors @@ -1263,6 +1238,31 @@ def valid_actions(self) -> List[str]: """Get the list of valid actions for the command-line driver.""" return list(self.action_map().keys()) + @staticmethod + def get_full_config(driver: HammerDriver, output: dict) -> dict: + """ + Get the full configuration by combining the project config from the + driver with the given output dict (i.e. it contains only + "synthesis.output.blah") that we want to combine with the project + config. + :param driver: HammerDriver that has the full project config. + :param output: Output dict containing specific settings we want to add + to the full project config. + :return: Full project config combined with the output dict + """ + if "vlsi.builtins.is_complete" in output: + if bool(output["vlsi.builtins.is_complete"]): + raise ValueError("Output-only config claims it is complete") + else: + raise ValueError("Output-only config does not appear to be output only") + + output_full = deepdict(driver.project_config) + output_full.update(deepdict(output)) + # Merged configs are always complete + if "vlsi.builtins.is_complete" in output_full: + del output_full["vlsi.builtins.is_complete"] + return output_full + def args_to_driver(self, args: dict, default_options: Optional[HammerDriverOptions] = None) -> \ Tuple[HammerDriver, List[str]]: @@ -1424,6 +1424,8 @@ def syn_pre_func(d: HammerDriver) -> None: module=module)) # TODO(edwardw): fix this ugly os.path.join; it doesn't belong here. # TODO(edwardw): remove ugly hack to store stuff in parent context base_project_config[0] = deeplist(driver.project_configs) + print(base_project_config[0]) + print(config) d.update_project_configs(deeplist(base_project_config[0]) + [config]) def par_pre_func(d: HammerDriver) -> None: @@ -1483,7 +1485,7 @@ def post_run(d: HammerDriver, rundir: str) -> None: with open(os.path.join(rundir, "module_config.json"), "w") as f: new_output_json = json.dumps(config, cls=HammerJSONEncoder, indent=4) f.write(new_output_json) - + # Restore the original project config to remove hierarchical settings. d.update_project_configs(deeplist(base_project_config[0])) def syn_post_run(d: HammerDriver) -> None: diff --git a/hammer/vlsi/driver.py b/hammer/vlsi/driver.py index 8b2862bdd..eedc9af9d 100644 --- a/hammer/vlsi/driver.py +++ b/hammer/vlsi/driver.py @@ -65,7 +65,7 @@ def __init__(self, options: HammerDriverOptions, extra_project_config: dict = {} file_logger = HammerVLSIFileLogger(options.log_file) HammerVLSILogging.add_callback(file_logger.callback) self.log = HammerVLSILogging.context() # type: HammerVLSILoggingContext - + # Create a new hammer database. self.database = hammer_config.HammerDatabase() # type: hammer_config.HammerDatabase @@ -1670,8 +1670,8 @@ def _hierarchical_helper(self) -> Tuple[List[Tuple[str, dict]], Dict[str, Tuple[ hier_source_key = "vlsi.inputs.hierarchical.config_source" hier_source = str(self.database.get_setting(hier_source_key)) hier_modules = {} # type: Dict[str, List[str]] + hier_constraints = {} # type: Dict[str, Any] hier_placement_constraints = {} # type: Dict[str, List[PlacementConstraint]] - hier_constraints = {} # type: Dict[str, List[Dict]] # This is retrieving the list of hard macro sizes to be used when creating PlacementConstraint tuples later list_of_hard_macros = self.database.get_setting("vlsi.technology.extra_macro_sizes") # type: List[Dict] @@ -1686,56 +1686,83 @@ def _hierarchical_helper(self) -> Tuple[List[Tuple[str, dict]], Dict[str, Tuple[ if len(list_of_hier_modules) == 0: raise ValueError("No hierarchical modules defined manually in manual hierarchical mode") hier_modules = reduce(add_dicts, list_of_hier_modules) + all_modules = list(hier_modules.keys()) + reduce_list_str(add_lists, hier_modules.values()) + in_place_unique(all_modules) - list_of_placement_constraints = self.database.get_setting( - "vlsi.inputs.hierarchical.manual_placement_constraints") # type: List[Dict] - assert isinstance(list_of_placement_constraints, list) - combined_raw_placement_dict = reduce(add_dicts, list_of_placement_constraints, {}) # type: Dict[str, List[Dict[str, Any]]] - - # This helper function filters only the dict containing the toplevel placement constraint, if any, from the provided list of dicts. - # If the list does not contain a toplevel constraint, it returns None. - def get_toplevel(d: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]: - results = list(filter(lambda x: x["type"] == "toplevel", d)) - if len(results) == 0: - return None - else: - return results[-1] - - # Use the above helper method to filter down the combined raw placement dict into a dict: - # - keys are hierarchical module name - # - values are dicts containing toplevel constraints or None - toplevels_opt = {k: get_toplevel(v) for k, v in combined_raw_placement_dict.items()} # type: Dict[str, Optional[Dict[str, Any]]] - # This filters out all of the Nones to get only hierarchical modules with toplevel placement constraints - toplevels = {k: v for k, v in toplevels_opt.items() if v is not None} # type: Dict[str, Dict[str, Any]] - # This converts each dict entry into a MacroSize tuple, which should now represent all hierarchical modules - hier_macros = [MacroSize(library="", name=x[0], width=x[1]["width"], height=x[1]["height"]) for x in toplevels.items()] - masters = hard_macros + hier_macros - - hier_placement_constraints = {key: list(map(partial(PlacementConstraint.from_masters_and_dict, masters), lst)) - for key, lst in combined_raw_placement_dict.items()} # Iterate over project configs to find which ones contain hierarchical constraints # For each file that does append its path to the special key in the extracted # hierarchical constraints section. - """ hier_constraint_source = "" # type: str + [config_path_key, _] = self.database.internal_keys() for project_conf in self.project_configs: - if "vlsi.inputs.hierarchical.constraints" in project_conf: - hier_constraint_source = project_conf[hammer_config._CONFIG_PATH_KEY] + try: + hier_constraint_source = project_conf[config_path_key] pc = project_conf["vlsi.inputs.hierarchical.constraints"] # type: List[Dict] # Add CONFIG_PATH_KEY to actual project configs for each project config's hierarchical constraint # keys then update project configs at the end for md in pc: - md # one entry dict with a list - for m in md.keys(): - if hammer_config._CONFIG_PATH_KEY in md[m][-1]: + md # one entry dict with a list or dict + for mc in md.values(): + if config_path_key in mc: pass + elif isinstance(mc, list): # legacy (see below) + mc.append({config_path_key: hier_constraint_source}) + elif isinstance(mc, dict): # new (see below) + mc[config_path_key] = hier_constraint_source else: - md[m].append({hammer_config._CONFIG_PATH_KEY: hier_constraint_source}) - """ + raise TypeError("Invalid type for hierarchical constraints") + except: + # No _config_path in the config (from tests) + # Or no vlsi.inputs.hierarchical.constraints in project_conf + continue self.update_project_configs(self.project_configs) list_of_hier_constraints = self.database.get_setting( "vlsi.inputs.hierarchical.constraints") # type: List[Dict] - hier_constraints = reduce(add_dicts, list_of_hier_constraints, {}) + if len(list_of_hier_constraints) > 0: + # Check if the hierarchical constraints are a list of dicts or a dict + # list of dicts is for legacy compatibility. Meta directives don't work. + if isinstance(next(iter(list_of_hier_constraints[0].values())), list): + self.log.warning("In vlsi.inputs.hierarchical.constraints, specifying constraints for each module " + + "as a list of single key/value pair dicts is deprecated but still functional. " + + "Recommend using a full dict (like in a flat flow) instead.") + hier_constraints = reduce(add_dicts, list_of_hier_constraints, {}) + else: + # run combine_configs per module to resolve meta directives + for module in all_modules: + mod_configs = list(map(lambda x: x.get(module, {}), list_of_hier_constraints)) + hier_constraints[module] = hammer_config.combine_configs(mod_configs) + + # Process manual placement constraints + list_of_placement_constraints = self.database.get_setting( + "vlsi.inputs.hierarchical.manual_placement_constraints") # type: List[Dict] + assert isinstance(list_of_placement_constraints, list) + # Legacy method: derived from module's vlsi.inputs.placement_constraints + if len(list_of_placement_constraints) > 0: + self.log.warning("Use of vlsi.inputs.hierarchical.manual_placement_constraints is legacy. " + + "Place them constraints inside vlsi.inputs.placement_constraints for each module instead.") + combined_raw_placement_dict = reduce(add_dicts, list_of_placement_constraints, {}) # type: Dict[str, List[Dict[str, Any]]] + + # This helper function filters only the dict containing the toplevel placement constraint, if any, from the provided list of dicts. + # If the list does not contain a toplevel constraint, it returns None. + def get_toplevel(d: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]: + results = list(filter(lambda x: x["type"] == "toplevel", d)) + if len(results) == 0: + return None + else: + return results[-1] + + # Use the above helper method to filter down the combined raw placement dict into a dict: + # - keys are hierarchical module name + # - values are dicts containing toplevel constraints or None + toplevels_opt = {k: get_toplevel(v) for k, v in combined_raw_placement_dict.items()} # type: Dict[str, Optional[Dict[str, Any]]] + # This filters out all of the Nones to get only hierarchical modules with toplevel placement constraints + toplevels = {k: v for k, v in toplevels_opt.items() if v is not None} # type: Dict[str, Dict[str, Any]] + # This converts each dict entry into a MacroSize tuple, which should now represent all hierarchical modules + hier_macros = [MacroSize(library="", name=x[0], width=x[1]["width"], height=x[1]["height"]) for x in toplevels.items()] + masters = hard_macros + hier_macros + + hier_placement_constraints = {key: list(map(partial(PlacementConstraint.from_masters_and_dict, masters), lst)) + for key, lst in combined_raw_placement_dict.items()} elif hier_source == "from_placement": raise NotImplementedError("Generation from placement not implemented yet") else: @@ -1795,11 +1822,19 @@ def visit_module(mod: str) -> None: constraint_dict = { "vlsi.inputs.hierarchical.mode": str(mode), - "synthesis.inputs.top_module": module, - "vlsi.inputs.placement_constraints": list( - map(PlacementConstraint.to_dict, hier_placement_constraints.get(module, []))) + "synthesis.inputs.top_module": module } - constraint_dict = reduce(add_dicts, hier_constraints.get(module, []), constraint_dict) + # manual_placement_constraints specified (legacy) + if len(hier_placement_constraints) > 0: + constraint_dict = add_dicts(constraint_dict, { + "vlsi.inputs.placement_constraints": list( + map(PlacementConstraint.to_dict, hier_placement_constraints.get(module, []))) + }) + if len(hier_constraints) > 0: + if isinstance(next(iter(hier_constraints.values())), dict): # new + constraint_dict = add_dicts(hier_constraints.get(module, {}), constraint_dict) + else: # legacy + constraint_dict = reduce(add_dicts, hier_constraints.get(module, []), constraint_dict) output.append((module, constraint_dict)) return (output, dependency_graph) From 5277152e35f664a23e816c26fcc66e605173b19a Mon Sep 17 00:00:00 2001 From: cadeberkeley <74031820+cadeberkeley@users.noreply.github.com> Date: Mon, 13 May 2024 09:09:35 -0700 Subject: [PATCH 2/2] Added simple PLE flow and progress on iSpatial flow. (#829) * Added simple PLE flow and progress on iSpatial flow. * Removed hard-coded floorplan generation, now reads in def. * Updated comments describing physical flow effort levels. * Moved syn_opt step into syn_map because it is entirely conditional. * ispatial and ple * removed qrc bug fix --------- Co-authored-by: Cade Richard Co-authored-by: Class Account Co-authored-by: Evan Li <53130369+GooglyCoffee@users.noreply.github.com> --- hammer/synthesis/genus/__init__.py | 57 +++++++++++++++++++++++++++- hammer/synthesis/genus/defaults.yml | 7 ++++ hammer/tech/__init__.py | 1 + hammer/technology/sky130/__init__.py | 4 +- hammer/vlsi/cli_driver.py | 1 - 5 files changed, 65 insertions(+), 5 deletions(-) diff --git a/hammer/synthesis/genus/__init__.py b/hammer/synthesis/genus/__init__.py index a167f08b7..0133db8b6 100644 --- a/hammer/synthesis/genus/__init__.py +++ b/hammer/synthesis/genus/__init__.py @@ -85,6 +85,7 @@ def get_tool_hooks(self) -> List[HammerToolHookAction]: def steps(self) -> List[HammerToolStep]: steps_methods = [ self.init_environment, + self.predict_floorplan, self.syn_generic, self.syn_map, self.add_tieoffs, @@ -223,6 +224,19 @@ def init_environment(self) -> bool: files=" ".join(lef_files) )) + # Read qrc tech files for physical synthesis. + qrc_files = self.technology.read_libs([ + hammer_tech.filters.qrc_tech_filter + ], hammer_tech.HammerTechnologyUtils.to_plain_item) + verbose_append("set_db qrc_tech_file {{ {files} }}".format( + files=" ".join(qrc_files) + )) + + # Quit when ispatial is used with sky130 + if(not qrc_files and self.get_setting("synthesis.genus.phys_flow_effort").lower() == "high"): + self.logger.warning("Sky130 does not support ISpatial due to missing of qrc tech files.") + verbose_append("quit") + # Load input files and check that they are all Verilog. if not self.check_input_files([".v", ".sv", "vh"]): return False @@ -264,6 +278,12 @@ def init_environment(self) -> bool: # TODO: is there a way to track instance paths through the synthesis process? verbose_append("set_db root: .auto_ungroup none") + # Set design effort. + verbose_append(f"set_db phys_flow_effort {self.get_setting('synthesis.genus.phys_flow_effort')}") + + if self.get_setting("synthesis.genus.phys_flow_effort").lower() == "high": + self.verbose_append("set_db opt_spatial_effort extreme") + # Set "don't use" cells. for l in self.generate_dont_use_commands(): self.append(l) @@ -325,14 +345,28 @@ def syn_generic(self) -> bool: if len(inverter_cells) > 0 and len(logic_cells) > 0: # Clock mapping needs at least the attributes cts_inverter_cells and cts_logic_cells to be set self.append("set_db map_clock_tree true") - self.verbose_append("syn_generic") + + if self.get_setting("synthesis.genus.phys_flow_effort").lower() == "none": + self.verbose_append("syn_generic") + else: + self.verbose_append("syn_generic -physical") self.dedup_ilms() return True def syn_map(self) -> bool: - self.verbose_append("syn_map") + if self.get_setting("synthesis.genus.phys_flow_effort").lower() == "none": + self.verbose_append("syn_map") + else: + self.verbose_append("syn_map -physical") + + # High QoR optimization. + if self.get_setting("synthesis.genus.phys_flow_effort").lower() == "medium": + self.verbose_append("syn_opt") + elif self.get_setting("synthesis.genus.phys_flow_effort").lower() == "high": + self.verbose_append("syn_opt -spatial") + # Need to suffix modules for hierarchical simulation if not top if self.hierarchical_mode not in [HierarchicalMode.Flat, HierarchicalMode.Top]: self.verbose_append("update_names -module -log hier_updated_names.log -suffix _{MODULE}".format(MODULE=self.top_module)) @@ -340,6 +374,19 @@ def syn_map(self) -> bool: self.dedup_ilms() return True + + def predict_floorplan(self) -> bool: + if self.get_setting("synthesis.genus.phys_flow_effort").lower() == "none": + self.verbose_append("set_db predict_floorplan_enable_during_generic false") + self.verbose_append("set_db physical_force_predict_floorplan false") + else: + self.verbose_append("set_db invs_temp_dir temp_invs") + self.verbose_append(f"set_db innovus_executable {self.get_setting('par.innovus.innovus_bin')}") + self.verbose_append("set_db predict_floorplan_enable_during_generic true") + self.verbose_append("set_db physical_force_predict_floorplan true") + self.verbose_append(f"set_db predict_floorplan_use_innovus true") + self.verbose_append("predict_floorplan") + return True def add_tieoffs(self) -> bool: tie_hi_cells = self.technology.get_special_cell_by_type(CellType.TieHiCell) @@ -376,8 +423,13 @@ def generate_reports(self) -> bool: """Generate reports.""" # TODO: extend report generation capabilities self.verbose_append("write_reports -directory reports -tag final") + if self.get_setting("synthesis.genus.phys_flow_effort").lower() != "none": + self.verbose_append("report_ple > reports/final_ple.rpt") + #qor done by write_reports + # Write reports does not normally report unconstrained paths self.verbose_append("report_timing -unconstrained -max_paths 50 > reports/final_unconstrained.rpt") + return True def write_regs(self) -> bool: @@ -420,6 +472,7 @@ def write_outputs(self) -> bool: verbose_append("write_design -innovus {hier_flag} -gzip_files {top}".format( hier_flag="-hierarchical" if is_hier else "", top=top)) + verbose_append("write_db -common") self.ran_write_outputs = True return True diff --git a/hammer/synthesis/genus/defaults.yml b/hammer/synthesis/genus/defaults.yml index fee8294b9..d1ae43d6f 100644 --- a/hammer/synthesis/genus/defaults.yml +++ b/hammer/synthesis/genus/defaults.yml @@ -8,5 +8,12 @@ synthesis.genus: # Used to locate the binary - e.g. the '221' in ${cadence.cadence_home}/DDI/DDI221/GENUS221/bin/genus version: "231" + # Physical flow effort. + # Valid options: high, medium, and none. Requires Genus Physical Option (GEN40) license and access to the Innovus System. + # High effort: Provides the best QoR at the cost of runtime. Requires Genus_Physical_Opt license. + # Medium effort: Offers the best trade-off between the runtime and QoR and turns legalization off by default. + # None: Will result in the best runtime. Works with all base Genus products. + phys_flow_effort: "medium" + # Generate the TCL file but do not run it yet. generate_only: false diff --git a/hammer/tech/__init__.py b/hammer/tech/__init__.py index 24482b6d0..b4e844252 100644 --- a/hammer/tech/__init__.py +++ b/hammer/tech/__init__.py @@ -127,6 +127,7 @@ class Library(BaseModel): spice_model_file: Optional[SpiceModelFile] = None power_grid_library: Optional[str] = None extra_prefixes: Optional[List[PathPrefix]] = None + def_file: Optional[str] = None PathsFunctionType = Callable[[Library], List[str]] diff --git a/hammer/technology/sky130/__init__.py b/hammer/technology/sky130/__init__.py index 5eda84b08..a814688f1 100644 --- a/hammer/technology/sky130/__init__.py +++ b/hammer/technology/sky130/__init__.py @@ -29,9 +29,9 @@ def post_install_script(self) -> None: # check whether variables were overriden to point to a valid path self.use_sram22 = os.path.exists(self.get_setting("technology.sky130.sram22_sky130_macros")) self.setup_cdl() - self.setup_verilog() + # self.setup_verilog() self.setup_techlef() - self.setup_io_lefs() + # self.setup_io_lefs() self.logger.info('Loaded Sky130 Tech') diff --git a/hammer/vlsi/cli_driver.py b/hammer/vlsi/cli_driver.py index 574914862..b50aeee3c 100644 --- a/hammer/vlsi/cli_driver.py +++ b/hammer/vlsi/cli_driver.py @@ -156,7 +156,6 @@ def __init__(self) -> None: self.formal_rundir = "" # type: Optional[str] self.timing_rundir = "" # type: Optional[str] self.pcb_rundir = "" # type: Optional[str] - self.synthesis_action: CLIActionConfigType # If a subclass has defined these, don't clobber them in init # since the subclass still uses this init function.