From aa42d9f05f9b81f1b80d7ab848168b373632fa15 Mon Sep 17 00:00:00 2001 From: Harrison Liew Date: Thu, 26 Oct 2023 20:45:21 -0700 Subject: [PATCH] untested Innovus implementationn, runs properly thru unit tests & mock_hier e2e test --- e2e/configs-design/pass/mock_hier.yml | 9 +- hammer/config/defaults.yml | 12 +- hammer/config/defaults_types.yml | 6 + hammer/generate_properties.py | 6 +- hammer/lvs/mocklvs/__init__.py | 2 +- hammer/par/innovus/__init__.py | 293 +++++++++++++++++--------- hammer/par/mockpar/__init__.py | 28 ++- hammer/synthesis/genus/__init__.py | 5 +- hammer/technology/sky130/__init__.py | 2 +- hammer/vlsi/cli_driver.py | 16 +- hammer/vlsi/driver.py | 28 ++- hammer/vlsi/hammer_build_systems.py | 26 ++- hammer/vlsi/hammer_vlsi_impl.py | 54 ++++- tests/test_build_systems.py | 12 +- 14 files changed, 363 insertions(+), 136 deletions(-) diff --git a/e2e/configs-design/pass/mock_hier.yml b/e2e/configs-design/pass/mock_hier.yml index 40ef02692..05e36e5a7 100644 --- a/e2e/configs-design/pass/mock_hier.yml +++ b/e2e/configs-design/pass/mock_hier.yml @@ -9,11 +9,12 @@ synthesis.inputs: input_files: ["src/pass.v"] vlsi.inputs.hierarchical: - mode: hierarchical - top_module: ChipTop + #mode: hierarchical + mode: top-down + top_module: pass config_source: manual manual_modules: - - ChipTop: + - pass: - SubModA - SubModB - SubModA: @@ -22,7 +23,7 @@ vlsi.inputs.hierarchical: - SubModB: - SubModE manual_placement_constraints: - - ChipTop: [] + - pass: [] - SubModA: [] - SubModB: [] - SubModC: [] diff --git a/hammer/config/defaults.yml b/hammer/config/defaults.yml index 8368ac893..ac0f6d48f 100644 --- a/hammer/config/defaults.yml +++ b/hammer/config/defaults.yml @@ -133,7 +133,11 @@ vlsi.inputs: # hierarchical - Same as bottom-up (legacy). module_mode: flat # Hierarchical par mode for the current module. (str) - # This is not user-set. It is dynamically set as you traverse through the hierarchical dependency graph. + # Generally not user-set. It is dynamically set as you traverse through the hierarchical dependency graph. + + partitioning: true # Whether the current module in the top-down flow is undergoing partitioning. (bool) + # If false, it will be undergoing assembly. + # Generally not user-set. It is dynamically set as you traverse through the hierarchical dependency graph. top_module: "null" # Top RTL module in the hierarchical flow. (str) # Not to be confused with synthesis.inputs.top_module which specifies the synthesis module for this run. @@ -481,6 +485,12 @@ par.inputs: # Typically a list of Verilog/VHDL files, depending on the tool. # For place and route, these should typically be post-synthesis netlists. + input_dbs: [] # Input databases. + # Input databases used in top-down hierarchical flow. + # Partitioning should be triggered if the post-partitioning database of the current module is not found in this list. + # Otherwise, assembly should be triggered. + # Generally set by linking steps, but can be manually overridden. + top_module: null # Top RTL module. post_synth_sdc: null # Optional: SDC input file from post-synthesis. diff --git a/hammer/config/defaults_types.yml b/hammer/config/defaults_types.yml index d52609b3a..981c86cbb 100644 --- a/hammer/config/defaults_types.yml +++ b/hammer/config/defaults_types.yml @@ -94,6 +94,9 @@ vlsi.inputs: # Hierarchical par mode of the current module. (str) module_mode: str + # Partitioning status of the current module. (bool) + partitioning: bool + # Top RTL module in the hierarchical flow. (str) top_module: str @@ -229,6 +232,9 @@ par.inputs: # Input post-synthesis netlist files. input_files: list[str] + # Input databases. + input_dbs: list[str] + # Top RTL module. top_module: Optional[str] diff --git a/hammer/generate_properties.py b/hammer/generate_properties.py index df8f29907..982ed513e 100755 --- a/hammer/generate_properties.py +++ b/hammer/generate_properties.py @@ -133,6 +133,8 @@ def main(args) -> int: inputs=[ InterfaceVar("input_files", "List[str]", "input post-synthesis netlist files"), + InterfaceVar("input_dbs", "List[str]", + "(optional) input database files/dirs for top-down hierarchical mode"), InterfaceVar("post_synth_sdc", "Optional[str]", "(optional) input post-synthesis SDC constraint file"), ], @@ -141,7 +143,9 @@ def main(args) -> int: # e.g. par-rundir/TopModuleILMDir/mmmc/ilm_data/TopModule. Has a bunch of files TopModule_postRoute* InterfaceVar("output_ilms", "List[ILMStruct]", - "(optional) output ILM information for hierarchical mode"), + "(optional) output ILM information for bottom-up hierarchical mode"), + InterfaceVar("output_dbs", "List[str]", + "(optional) output database files/dirs for each partition in top-down hierarchical mode"), InterfaceVar("output_gds", "str", "path to the output GDS file"), InterfaceVar("output_netlist", "str", "path to the output netlist file"), InterfaceVar("output_sim_netlist", "str", "path to the output simulation netlist file"), diff --git a/hammer/lvs/mocklvs/__init__.py b/hammer/lvs/mocklvs/__init__.py index a5ec02763..29c006e66 100644 --- a/hammer/lvs/mocklvs/__init__.py +++ b/hammer/lvs/mocklvs/__init__.py @@ -29,7 +29,7 @@ def lvs_results(self) -> List[str]: return ["VDD is connected to VSS"] def get_ilms(self) -> bool: - if self.hierarchical_mode in [HierarchicalMode.Hierarchical, HierarchicalMode.Top]: + if self.hierarchical_mode.is_nonleaf_hierarchical(): with open(os.path.join(self.run_dir, "input_ilms.json"), "w") as f: f.write(json.dumps(list(map(lambda s: s.to_setting(), self.get_input_ilms())))) return True diff --git a/hammer/par/innovus/__init__.py b/hammer/par/innovus/__init__.py index 0746d15d1..fe3b994ba 100644 --- a/hammer/par/innovus/__init__.py +++ b/hammer/par/innovus/__init__.py @@ -32,6 +32,7 @@ def export_config_outputs(self) -> Dict[str, Any]: outputs["par.outputs.all_regs"] = self.output_all_regs outputs["par.outputs.sdf_file"] = self.output_sdf_path outputs["par.outputs.spefs"] = self.output_spef_paths + outputs["vlsi.inputs.hierarchical.partitioning"] = self.partitioning return outputs def fill_outputs(self) -> bool: @@ -61,6 +62,11 @@ def fill_outputs(self) -> bool: else: self.output_ilms = [] + if self.ran_partition_design: + self.output_dbs = self.partition_dirs + else: + self.output_dbs = [self.output_innovus_lib_name] + # Check that the regs paths were written properly if the write_regs step was run self.output_seq_cells = self.all_cells_path self.output_all_regs = self.all_regs_path @@ -214,40 +220,43 @@ def get_tool_hooks(self) -> List[HammerToolHookAction]: @property def steps(self) -> List[HammerToolStep]: - steps = [ + steps = [] # type: List[Callable[[], bool]] + pre_partition = [ self.init_design, self.floorplan_design, self.place_bumps, self.place_tap_cells, self.power_straps, - self.place_pins, + self.place_pins + ] # type: List[Callable[[], bool]] + post_partition = [ self.place_opt_design, self.clock_tree, self.add_fillers, self.route_design, self.opt_design - ] + ] # type: List[Callable[[], bool]] write_design_step = [ self.write_regs, self.write_design ] # type: List[Callable[[], bool]] - if self.hierarchical_mode == HierarchicalMode.Flat: - # Nothing to do - pass - elif self.hierarchical_mode == HierarchicalMode.Leaf: - # All modules in hierarchical must write an ILM - write_design_step += [self.write_ilm] - elif self.hierarchical_mode == HierarchicalMode.Hierarchical: - # All modules in hierarchical must write an ILM - write_design_step += [self.write_ilm] - elif self.hierarchical_mode == HierarchicalMode.Top: - # No need to write ILM at the top. - # Top needs assemble_design instead. - steps += [self.assemble_design] - pass + + if self.hierarchical_mode in {HierarchicalMode.Flat, HierarchicalMode.BUTop, HierarchicalMode.TDLeaf}: + # Default + steps = pre_partition + post_partition + write_design_step + elif self.hierarchical_mode in {HierarchicalMode.BULeaf, HierarchicalMode.BUHierarchical}: + # All submodules in bottom-up hierarchical must write an ILM + steps = pre_partition + post_partition + write_design_step + [self.write_ilm] + elif self.hierarchical_mode in {HierarchicalMode.TDTop, HierarchicalMode.TDHierarchical}: + input_dbs = self.get_setting("par.inputs.input_dbs", []) + # Run partition_design + if self.partitioning: + steps = pre_partition + [self.partition_design] + else: + steps = [self.assemble_design] + post_partition + write_design_step else: raise NotImplementedError("HierarchicalMode not implemented: " + str(self.hierarchical_mode)) - return self.make_steps_from_methods(steps + write_design_step) + return self.make_steps_from_methods(steps) def tool_config_prefix(self) -> str: return "par.innovus" @@ -256,86 +265,103 @@ def init_design(self) -> bool: """Initialize the design.""" verbose_append = self.verbose_append - # Perform common path pessimism removal in setup and hold mode - verbose_append("set_db timing_analysis_cppr both") - # Use OCV mode for timing analysis by default - verbose_append("set_db timing_analysis_type ocv") - - # Read LEF layouts. - lef_files = self.technology.read_libs([ - hammer_tech.filters.lef_filter - ], hammer_tech.HammerTechnologyUtils.to_plain_item) - if self.hierarchical_mode.is_nonleaf_hierarchical(): - ilm_lefs = list(map(lambda ilm: ilm.lef, self.get_input_ilms())) - lef_files.extend(ilm_lefs) - verbose_append("read_physical -lef {{ {files} }}".format( - files=" ".join(lef_files) - )) + # Check for input databases + input_dbs = self.get_setting("par.inputs.input_dbs", []) + + # Initialize differently depending on hierarchical mode + if ((self.hierarchical_mode in {HierarchicalMode.Flat, HierarchicalMode.BUTop, HierarchicalMode.BULeaf, HierarchicalMode.BUHierarchical}) + or (self.hierarchical_mode == HierarchicalMode.TDTop and self.partitioning)): + + # Perform common path pessimism removal in setup and hold mode + verbose_append("set_db timing_analysis_cppr both") + # Use OCV mode for timing analysis by default + verbose_append("set_db timing_analysis_type ocv") + + # Read LEF layouts. + lef_files = self.technology.read_libs([ + hammer_tech.filters.lef_filter + ], hammer_tech.HammerTechnologyUtils.to_plain_item) + if self.hierarchical_mode.is_nonleaf_hierarchical(): + ilm_lefs = list(map(lambda ilm: ilm.lef, self.get_input_ilms())) + lef_files.extend(ilm_lefs) + verbose_append("read_physical -lef {{ {files} }}".format( + files=" ".join(lef_files) + )) + + # Read timing libraries. + mmmc_path = os.path.join(self.run_dir, "mmmc.tcl") + self.write_contents_to_path(self.generate_mmmc_script(), mmmc_path) + verbose_append("read_mmmc {mmmc_path}".format(mmmc_path=mmmc_path)) + + # Read netlist. + # Innovus only supports structural Verilog for the netlist; the Verilog can be optionally compressed. + if not self.check_input_files([".v", ".v.gz"]): + return False - # Read timing libraries. - mmmc_path = os.path.join(self.run_dir, "mmmc.tcl") - self.write_contents_to_path(self.generate_mmmc_script(), mmmc_path) - verbose_append("read_mmmc {mmmc_path}".format(mmmc_path=mmmc_path)) + # We are switching working directories and we still need to find paths. + abspath_input_files = list(map(lambda name: os.path.join(os.getcwd(), name), self.input_files)) + verbose_append("read_netlist {{ {files} }} -top {top}".format( + files=" ".join(abspath_input_files), + top=self.top_module + )) + + if self.hierarchical_mode.is_nonleaf_hierarchical(): + # Read ILMs. + for ilm in self.get_input_ilms(): + # Assumes that the ILM was created by Innovus (or at least the file/folder structure). + verbose_append("read_ilm -cell {module} -directory {dir}".format(dir=ilm.dir, module=ilm.module)) + + # Emit init_power_nets and init_ground_nets in case CPF/UPF is not used + # commit_power_intent does not override power nets defined in "init_power_nets" + spec_mode = self.get_setting("vlsi.inputs.power_spec_mode") # type: str + if spec_mode == "empty": + power_supplies = self.get_independent_power_nets() # type: List[Supply] + power_nets = " ".join(map(lambda s: s.name, power_supplies)) + ground_supplies = self.get_independent_ground_nets() # type: List[Supply] + ground_nets = " ".join(map(lambda s: s.name, ground_supplies)) + verbose_append("set_db init_power_nets {{{n}}}".format(n=power_nets)) + verbose_append("set_db init_ground_nets {{{n}}}".format(n=ground_nets)) + + # Run init_design to validate data and start the Cadence place-and-route workflow. + verbose_append("init_design") + + # Setup power settings from cpf/upf + for l in self.generate_power_spec_commands(): + verbose_append(l) + + # Set the top and bottom global/detail routing layers. + # This must happen after the tech LEF is loaded + layers = self.get_setting("vlsi.technology.routing_layers") + if layers is not None: + if self.version() >= self.version_number("201"): + verbose_append(f"set_db design_bottom_routing_layer {layers[0]}") + verbose_append(f"set_db design_top_routing_layer {layers[1]}") + else: + verbose_append(f"set_db route_early_global_bottom_layer {layers[0]}") + verbose_append(f"set_db route_early_global_top_layer {layers[1]}") + verbose_append(f"set_db route_design_bottom_layer {layers[0]}") + verbose_append(f"set_db route_design_top_layer {layers[1]}") + + # Set design effort. + verbose_append(f"set_db design_flow_effort {self.get_setting('par.innovus.design_flow_effort')}") + verbose_append(f"set_db design_power_effort {self.get_setting('par.innovus.design_power_effort')}") + + # Set "don't use" cells. + for l in self.generate_dont_use_commands(): + self.append(l) + return True - # Read netlist. - # Innovus only supports structural Verilog for the netlist; the Verilog can be optionally compressed. - if not self.check_input_files([".v", ".v.gz"]): + elif ((self.hierarchical_mode == HierarchicalMode.TDHierarchical and self.partitioning) + or self.hierarchical_mode == HierarchicalMode.TDLeaf): + # Read DB from parent partitioning + input_dbs = self.get_setting("par.inputs.input_dbs") + this_db = next(filter(lambda db: db.endswith(self.top_module), input_dbs)) + verbose_append(f"read_db {this_db}") + return True + else: + # Should not get here return False - # We are switching working directories and we still need to find paths. - abspath_input_files = list(map(lambda name: os.path.join(os.getcwd(), name), self.input_files)) - verbose_append("read_netlist {{ {files} }} -top {top}".format( - files=" ".join(abspath_input_files), - top=self.top_module - )) - - if self.hierarchical_mode.is_nonleaf_hierarchical(): - # Read ILMs. - for ilm in self.get_input_ilms(): - # Assumes that the ILM was created by Innovus (or at least the file/folder structure). - verbose_append("read_ilm -cell {module} -directory {dir}".format(dir=ilm.dir, module=ilm.module)) - - # Emit init_power_nets and init_ground_nets in case CPF/UPF is not used - # commit_power_intent does not override power nets defined in "init_power_nets" - spec_mode = self.get_setting("vlsi.inputs.power_spec_mode") # type: str - if spec_mode == "empty": - power_supplies = self.get_independent_power_nets() # type: List[Supply] - power_nets = " ".join(map(lambda s: s.name, power_supplies)) - ground_supplies = self.get_independent_ground_nets() # type: List[Supply] - ground_nets = " ".join(map(lambda s: s.name, ground_supplies)) - verbose_append("set_db init_power_nets {{{n}}}".format(n=power_nets)) - verbose_append("set_db init_ground_nets {{{n}}}".format(n=ground_nets)) - - # Run init_design to validate data and start the Cadence place-and-route workflow. - verbose_append("init_design") - - # Setup power settings from cpf/upf - for l in self.generate_power_spec_commands(): - verbose_append(l) - - # Set the top and bottom global/detail routing layers. - # This must happen after the tech LEF is loaded - layers = self.get_setting("vlsi.technology.routing_layers") - if layers is not None: - if self.version() >= self.version_number("201"): - verbose_append(f"set_db design_bottom_routing_layer {layers[0]}") - verbose_append(f"set_db design_top_routing_layer {layers[1]}") - else: - verbose_append(f"set_db route_early_global_bottom_layer {layers[0]}") - verbose_append(f"set_db route_early_global_top_layer {layers[1]}") - verbose_append(f"set_db route_design_bottom_layer {layers[0]}") - verbose_append(f"set_db route_design_top_layer {layers[1]}") - - # Set design effort. - verbose_append(f"set_db design_flow_effort {self.get_setting('par.innovus.design_flow_effort')}") - verbose_append(f"set_db design_power_effort {self.get_setting('par.innovus.design_power_effort')}") - - # Set "don't use" cells. - for l in self.generate_dont_use_commands(): - self.append(l) - - return True - def floorplan_design(self) -> bool: floorplan_tcl = os.path.join(self.run_dir, "floorplan.tcl") self.write_contents_to_path("\n".join(self.create_floorplan_tcl()), floorplan_tcl) @@ -636,7 +662,12 @@ def opt_design(self) -> bool: return True def assemble_design(self) -> bool: - # TODO: implement the assemble_design step. + """ + Assemble top and block directories + """ + input_dbs = self.get_setting("par.inputs.input_dbs") + block_dir_text = " ".join(f"-block_dir {d}" for d in input_dbs) + self.verbose_append(f"assemble_design -top_dir {self.post_partition_db} {block_dir_text}") return True def write_netlist(self) -> bool: @@ -858,6 +889,53 @@ def write_ilm(self) -> bool: self.ran_write_ilm = True return True + @property + def ran_partition_design(self) -> bool: + """The write_partitions stage sets this to True if it was run.""" + return self.attr_getter("_ran_write_partitions", False) + + @ran_partition_design.setter + def ran_partition_design(self, val: bool) -> None: + self.attr_setter("_ran_partition_design", val) + + @property + def partitioning(self) -> bool: + status_in = self.get_setting("vlsi.inputs.hierarchical.partitioning") + status_out = False if self.hierarchical_mode == HierarchicalMode.TDLeaf else status_in + return status_out + + @property + def partitions(self) -> List[str]: + partitions = list(filter(lambda x: x.type == PlacementConstraintType.Hierarchical, self.get_placement_constraints())) + return list(map(lambda x: get_or_else(x.master, ""), partitions)) + + @property + def partition_dirs(self) -> List[str]: + return list(map(lambda x: os.path.join(self.run_dir, x), self.partitions)) + + @property + def post_partition_db(self) -> str: + return f"{self.top_module}_post_partition" + + def partition_design(self) -> bool: + """Partitioning in top-down hierarchical.""" + self.verbose_append(f"set_db route_early_global_honor_partition_fence {{{' '.join(self.partitions)}}}") + self.verbose_append(f"set_db route_early_global_honor_partition_pin {{{' '.join(self.partitions)}}}") + self.verbose_append(f"set_db route_early_global_honor_power_domain true") + self.verbose_append("route_early_global") + self.verbose_append("check_pin_assignment -pre_check") + self.verbose_append("assign_partition_pins") + self.verbose_append("check_pin_assignment -report_violating_pin") + self.verbose_append("create_timing_budget") + for (partition, dir) in zip(self.partitions, self.partition_dirs): + self.verbose_append(f"commit_partitions -partition {partition} -pg_pushdown_honor_1801") + self.verbose_append(f"write_partitions {partition} -dir {dir} -def -verilog") + self.verbose_append(f"write_partition_pins -partition {partition} {partition}_pin_info") + # Write a post-partitioning database to continue from later + self.verbose_append(f"write_db {self.post_partition_db}") + self.ran_write_partitions = True + return True + def run_innovus(self) -> bool: # Quit Innovus. self.verbose_append("exit") @@ -931,7 +1009,7 @@ def create_floorplan_tcl(self) -> List[str]: else: if floorplan_mode != "blank": self.logger.error("Invalid floorplan_mode {mode}. Using blank floorplan.".format(mode=floorplan_mode)) - # Write blank floorplan + # Write blank floorpla/generaten output.append("# Blank floorplan specified from HAMMER") return output @@ -981,6 +1059,9 @@ def generate_floorplan_tcl(self) -> List[str]: floorplan_constraints = self.get_placement_constraints() global_top_layer = self.get_setting("par.blockage_spacing_top_layer") # type: Optional[str] + # Generate floorplans differently if top-down hierarchical + td_hier = self.get_setting("vlsi.inputs.hierarchical.mode") in {"top_down", "top-down"} + ############## Actually generate the constraints ################ for constraint in floorplan_constraints: # Floorplan names/insts need to not include the top-level module, @@ -1030,7 +1111,8 @@ def generate_floorplan_tcl(self) -> List[str]: orientation=orientation, fixed=" -fixed" if constraint.create_physical else "" )) - elif constraint.type in [PlacementConstraintType.HardMacro, PlacementConstraintType.Hierarchical]: + elif constraint.type == PlacementConstraintType.HardMacro or \ + (constraint.type == PlacementConstraintType.Hierarchical and not td_hier): output.append("place_inst {inst} {x} {y} {orientation}{fixed}".format( inst=new_path, x=constraint.x, @@ -1052,9 +1134,26 @@ def generate_floorplan_tcl(self) -> List[str]: inst=new_path, s=spacing)) output.append("create_route_halo -bottom_layer {b} -space {s} -top_layer {t} -inst {inst}".format( inst=new_path, b=bot_layer, t=current_top_layer, s=spacing)) +<<<<<<< HEAD output.append("create_route_blockage -pg_nets -inst {inst} -layers {{{layers}}} -cover".format( inst=new_path, layers=" ".join(cover_layers))) +======= + elif constraint.type == PlacementConstraintType.Hierarchical and td_hier: + output.append("create_boundary_constraint -type fence -hinst {inst} -rects {{{x1} {y1} {x2} {y2}}}".format( + inst=new_path, + x1=constraint.x, + x2=constraint.x + constraint.width, + y1=constraint.y, + y2=constraint.y + constraint.height + )) + halo_top_layer = get_or_else(constraint.top_layer, global_top_layer) + output.append("create_partition -hinst {inst} -place_halo {{{s} {s} {s} {s}}} -route_halo {s} {halo_top_layer_str}".format( + inst=new_path, + s=self.get_setting("par.blockage_spacing"), + halo_top_layer_str = f"-route_halo_top_layer {halo_top_layer}" if halo_top_layer is not None else "" + )) +>>>>>>> ea4acf1 (suntestedInnovus implementationn, runs properly thru unit tests & mock_hier e2e test) elif constraint.type == PlacementConstraintType.Obstruction: obs_types = get_or_else(constraint.obs_types, []) # type: List[ObstructionType] if ObstructionType.Place in obs_types: diff --git a/hammer/par/mockpar/__init__.py b/hammer/par/mockpar/__init__.py index 16419a512..9607b8299 100644 --- a/hammer/par/mockpar/__init__.py +++ b/hammer/par/mockpar/__init__.py @@ -33,7 +33,8 @@ def temp_file(self, filename: str) -> str: def steps(self) -> List[HammerToolStep]: return self.make_steps_from_methods([ self.power_straps, - self.get_ilms + self.get_ilms, + self.partition ]) def power_straps(self) -> bool: @@ -79,18 +80,29 @@ def specify_std_cell_power_straps(self, blockage_spacing: Decimal, bbox: Optiona return [json.dumps(output_dict, cls=HammerJSONEncoder)] def get_ilms(self) -> bool: - if self.hierarchical_mode in [HierarchicalMode.Hierarchical, HierarchicalMode.Top]: + if self.hierarchical_mode.is_nonleaf_hierarchical(): with open(os.path.join(self.run_dir, "input_ilms.json"), "w") as f: f.write(json.dumps(list(map(lambda s: s.to_setting(), self.get_input_ilms())))) return True + def partition(self) -> bool: + partitioning = self.get_setting("vlsi.inputs.hierarchical.partitioning") + self.logger.info(f"Partitioning status: {partitioning}") + return True + + def export_config_outputs(self) -> Dict[str, Any]: + outputs = dict(super().export_config_outputs()) + if self.hierarchical_mode == HierarchicalMode.TDLeaf: + outputs["vlsi.inputs.hierarchical.partitioning"] = False + return outputs + def fill_outputs(self) -> bool: self.output_gds = "/dev/null" self.output_netlist = "/dev/null" self.output_sim_netlist = "/dev/null" self.output_ilm_sdcs = ["/dev/null"] self.hcells_list = [] - if self.hierarchical_mode in [HierarchicalMode.Leaf, HierarchicalMode.Hierarchical]: + if self.hierarchical_mode in [HierarchicalMode.BULeaf, HierarchicalMode.BUHierarchical]: self.output_ilms = [ ILMStruct(dir="/dev/null", data_dir="/dev/null", module=self.top_module, lef="/dev/null", gds=self.output_gds, netlist=self.output_netlist, @@ -98,6 +110,16 @@ def fill_outputs(self) -> bool: ] else: self.output_ilms = [] + if self.hierarchical_mode in [HierarchicalMode.TDTop, HierarchicalMode.TDHierarchical] and self.get_setting("vlsi.inputs.hierarchical.partitioning"): + man_mods = self.get_setting("vlsi.inputs.hierarchical.manual_modules") + child_mods = [] # type: List[str] + for mod_dict in man_mods: + if self.top_module in mod_dict: + child_mods = mod_dict[self.top_module] + break + self.output_dbs = list(map(lambda m: os.path.join("/dev/null", m), child_mods)) + else: + self.output_dbs = [os.path.join("/dev/null", self.top_module)] return True diff --git a/hammer/synthesis/genus/__init__.py b/hammer/synthesis/genus/__init__.py index deeec5359..566fb6665 100644 --- a/hammer/synthesis/genus/__init__.py +++ b/hammer/synthesis/genus/__init__.py @@ -320,7 +320,7 @@ def syn_generic(self) -> bool: def syn_map(self) -> bool: self.verbose_append("syn_map") # Need to suffix modules for hierarchical simulation if not top - if self.hierarchical_mode not in [HierarchicalMode.Flat, HierarchicalMode.Top]: + if self.hierarchical_mode not in [HierarchicalMode.Flat, HierarchicalMode.BUTop, HierarchicalMode.TDTop]: self.verbose_append("update_names -module -log hier_updated_names.log -suffix _{MODULE}".format(MODULE=self.top_module)) return True @@ -398,9 +398,8 @@ def write_outputs(self) -> bool: else: # We just get "Cannot trace ILM directory. Data corrupted." # -hierarchical needs to be used for non-leaf modules - is_hier = self.hierarchical_mode != HierarchicalMode.Leaf # self.hierarchical_mode != HierarchicalMode.Flat verbose_append("write_design -innovus {hier_flag} -gzip_files {top}".format( - hier_flag="-hierarchical" if is_hier else "", top=top)) + hier_flag="-hierarchical" if self.hierarchical_mode.is_nonleaf_hierarchical() else "", top=top)) self.ran_write_outputs = True diff --git a/hammer/technology/sky130/__init__.py b/hammer/technology/sky130/__init__.py index 751525b22..64d46038e 100644 --- a/hammer/technology/sky130/__init__.py +++ b/hammer/technology/sky130/__init__.py @@ -349,7 +349,7 @@ def sky130_innovus_settings(ht: HammerTool) -> bool: set_db route_design_detail_use_multi_cut_via_effort medium ''' ) - if ht.hierarchical_mode in {HierarchicalMode.Top, HierarchicalMode.Flat}: + if ht.hierarchical_mode in {HierarchicalMode.BUTop, HierarchicalMode.TDTop, HierarchicalMode.Flat}: ht.append( ''' # For top module: snap die to manufacturing grid, not placement grid diff --git a/hammer/vlsi/cli_driver.py b/hammer/vlsi/cli_driver.py index dbf2ef7d0..2d6242e05 100644 --- a/hammer/vlsi/cli_driver.py +++ b/hammer/vlsi/cli_driver.py @@ -252,6 +252,14 @@ def action_map(self) -> Dict[str, CLIActionType]: "syn-par": self.synthesis_par_action, "hier_par_to_syn": self.hier_par_to_syn_action, "hier-par-to-syn": self.hier_par_to_syn_action, + "par_partition": self.par_action, + "par-partition": self.par_action, + "par_assemble": self.par_action, + "par-assemble": self.par_action, + "hier_par_partition_to_par": self.hier_par_to_par_action, + "hier-par-partition-to-par": self.hier_par_to_par_action, + "hier_par_to_par_assemble": self.hier_par_to_par_action, + "hier-par-to-par-assemble": self.hier_par_to_par_action, "par_to_drc": self.par_to_drc_action, "par-to-drc": self.par_to_drc_action, "par_to_lvs": self.par_to_lvs_action, @@ -817,10 +825,14 @@ def hier_par_to_syn_action(self, driver: HammerDriver, append_error_func: Callab else: return self.get_full_config(driver, syn_input_only) - #modified def hier_par_to_par_action(self, driver: HammerDriver, append_error_func: Callable[[str], None]) -> Optional[dict]: """ Create a full config to run the output. """ - #par to par - 1 and par to par - 2 + par_input_only = HammerDriver.par_output_to_par_input(driver.project_config) + if par_input_only is None: + driver.log.error("Input config does not appear to contain valid par outputs") + return None + else: + return self.get_full_config(driver, par_input_only) def par_to_drc_action(self, driver: HammerDriver, append_error_func: Callable[[str], None]) -> Optional[dict]: """ Create a full config to run the output. """ diff --git a/hammer/vlsi/driver.py b/hammer/vlsi/driver.py index 19a342d33..a74283ce3 100644 --- a/hammer/vlsi/driver.py +++ b/hammer/vlsi/driver.py @@ -293,6 +293,7 @@ def set_up_par_tool(self, par_tool: HammerPlaceAndRouteTool, # TODO: automate this based on the definitions par_tool.input_files = list(self.database.get_setting("par.inputs.input_files")) + par_tool.input_dbs = list(self.database.get_setting("par.inputs.input_dbs")) par_tool.top_module = self.database.get_setting("par.inputs.top_module", nullvalue="") par_tool.post_synth_sdc = self.database.get_setting("par.inputs.post_synth_sdc", nullvalue="") par_tool.output_all_regs = "" @@ -1229,12 +1230,14 @@ def run_par(self, hook_actions: Optional[List[HammerToolHookAction]] = None, for def par_output_to_syn_input(output_dict: dict) -> Optional[dict]: """ Generate the appropriate inputs for running the next level of synthesis from the - outputs of par run in a hierarchical flow. + outputs of par run in a bottom-up hierarchical flow. Does not merge the results with any project dictionaries. :param output_dict: Dict containing par.outputs.* :return: vlsi.inputs.* settings generated from output_dict, or None if output_dict was invalid """ + assert output_dict["vlsi.inputs.hierarchical.mode"] in {"hierarchical", "bottom-up", "bottom_up"},\ + "par_output_to_syn_input should only be called in bottom-up hierarchical mode" try: result = { "vlsi.inputs.ilms": output_dict["vlsi.inputs.ilms"] + output_dict["par.outputs.output_ilms"], @@ -1245,6 +1248,28 @@ def par_output_to_syn_input(output_dict: dict) -> Optional[dict]: # KeyError means that the given dictionary is missing output keys. return None + @staticmethod + def par_output_to_par_input(output_dict: dict) -> Optional[dict]: + """ + Generate the appropriate inputs for running the next level of par from the + outputs of par run in a top-down hierarchical flow. + Does not merge the results with any project dictionaries. + :param output_dict: Dict containing par.outputs.* + :return: vlsi.inputs.* settings generated from output_dict, + or None if output_dict was invalid + """ + assert output_dict["vlsi.inputs.hierarchical.mode"] in {"top-down", "top_down"},\ + "par_output_to_par_input should only be called in top-down hierarchical mode" + try: + result = { + "par.inputs.input_dbs": output_dict["par.outputs.output_dbs"], + "vlsi.builtins.is_complete": False + } + return result + except KeyError: + # KeyError means that the given dictionary is missing output keys. + return None + @staticmethod def par_output_to_drc_input(output_dict: dict) -> Optional[dict]: """ @@ -1830,6 +1855,7 @@ def visit_module(mod: str) -> None: constraint_dict = { "vlsi.inputs.hierarchical.module_mode": str(mode), "synthesis.inputs.top_module": module, + "par.inputs.top_module": module, "vlsi.inputs.placement_constraints": list( map(PlacementConstraint.to_dict, hier_placement_constraints.get(module, []))) } diff --git a/hammer/vlsi/hammer_build_systems.py b/hammer/vlsi/hammer_build_systems.py index 2aeced488..fc459b6d7 100644 --- a/hammer/vlsi/hammer_build_systems.py +++ b/hammer/vlsi/hammer_build_systems.py @@ -26,12 +26,16 @@ def __init__(self, self.base = action.split("-")[0] rd_suffix = "{suffix}" if hier else "-rundir" self.rundir = os.path.join("{obj_dir}", f"{action}{rd_suffix}") - if self.base == "par": # par partition & assemble should be run in the same dir + if self.base == "par": # just make the rundir par-... self.rundir = os.path.join("{obj_dir}", f"par{rd_suffix}") self.out_file = os.path.join(self.rundir, f"{self.base}-output-full.json") - if self.action == "par-partition": # par-partition has a different output file - self.out_file = os.path.join(self.rundir, f"{self.base}-partition-output-full.json") - default_pconf = [os.path.join("{obj_dir}", f"{action}-input.json")] + self.copy_text = "" + default_pconf = [os.path.join("{obj_dir}", f"{action}{{suffix}}-input.json")] + if self.action == "par-partition": # copy json to different file + default_out_file = self.out_file + self.out_file = os.path.join(self.rundir, f"{action}-output-full.json") + self.copy_text = f"\tcp {default_out_file} {self.out_file}" + default_pconf = [os.path.join("{obj_dir}", "par{suffix}-input.json")] if self.base == "power": # power actions require at least 1 input file lvl = action.split("-")[-1] in_json = os.path.join("{obj_dir}", f"power-sim-{lvl}-input.json") @@ -43,18 +47,20 @@ def __init__(self, self.deps = get_or_else(deps_ovrd, " ".join(default_pconf)) def phony_target(self) -> str: - return f"{self.action}{{suffix}}: {self.out_file}" + return f"{self.action}{{suffix}}: {self.out_file}\n" def recipe(self) -> str: return textwrap.dedent(f""" {self.out_file}: {self.deps} $(HAMMER_{self.action.upper().replace('-','_')}_DEPENDENCIES) \t$(HAMMER_EXEC) {{env_confs}} {self.pconf_str} $(HAMMER_EXTRA_ARGS) --{self.base}_rundir {self.rundir} --obj_dir {{obj_dir}} {self.base}{{suffix}} + {self.copy_text} """) def redo_recipe(self) -> str: return textwrap.dedent(f""" redo-{self.action}{{suffix}}: \t$(HAMMER_EXEC) {{env_confs}} {self.pconf_str} $(HAMMER_EXTRA_ARGS) --{self.base}_rundir {self.rundir} --obj_dir {{obj_dir}} {self.base}{{suffix}} + {self.copy_text} """) class MakeLinkRecipe: @@ -77,7 +83,7 @@ def __init__(self, self.y_in = os.path.join("{obj_dir}", f"{y}{y_in_suffix}.json") def phony_target(self) -> str: - return f"{self.action}{{suffix}}: {self.y_in}" + return f"{self.action}{{suffix}}: {self.y_in}\n" def recipe(self) -> str: return textwrap.dedent(f""" @@ -259,7 +265,8 @@ def gen_actions(hier_mode: str) -> List[Union[MakeActionRecipe, MakeLinkRecipe]] flow_mode = driver.get_hierarchical_flow_mode() dependency_graph = driver.get_hierarchical_dependency_graph() makefile = os.path.join(driver.obj_dir, "hammer.mk") - os.symlink(makefile, os.path.join(driver.obj_dir, "hammer.d")) + if not os.path.exists(os.path.join(driver.obj_dir, "hammer.d")): + os.symlink(makefile, os.path.join(driver.obj_dir, "hammer.d")) default_dependencies = driver.options.project_configs + driver.options.environment_configs default_dependencies.extend(list(driver.database.get_setting("synthesis.inputs.input_files", []))) # Resolve the canonical path for each dependency @@ -322,10 +329,7 @@ def gen_actions(hier_mode: str) -> List[Union[MakeActionRecipe, MakeLinkRecipe]] partition_confs = [os.path.join(obj_dir, "par-" + x, "par-partition-output-full.json") for x in parent_edges] pstring=" ".join(["-p " + x for x in partition_confs]) par_out_confs=" ".join(partition_confs) - if len(out_edges) == 0: # leaf node - par_deps = os.path.join(obj_dir, f"par-{node}-input.json") - else: - par_deps = os.path.join(obj_dir, f"par-partition-{node}-input.json") + par_deps = os.path.join(obj_dir, f"par-{node}-input.json") par_partition_to_par = textwrap.dedent(f""" .PHONY: hier-par-partition-to-par-{node} hier-par-partition-to-par-{node}: {par_deps} diff --git a/hammer/vlsi/hammer_vlsi_impl.py b/hammer/vlsi/hammer_vlsi_impl.py index 1a7fb3273..02be05a0e 100644 --- a/hammer/vlsi/hammer_vlsi_impl.py +++ b/hammer/vlsi/hammer_vlsi_impl.py @@ -54,9 +54,9 @@ def __str__(self) -> str: def is_nonleaf_hierarchical(self) -> bool: """ Helper function that returns True if this mode is a non-leaf hierarchical mode (i.e. any block with - hierarchical sub-blocks). + hierarchical sub-blocks). Used in bottom-up flows only. """ - return self == HierarchicalMode.Hierarchical or self == HierarchicalMode.BUTop or self == HierarchicalMode.TDTop + return self in {HierarchicalMode.BUHierarchical, HierarchicalMode.BUTop} class FlowLevel(Enum): RTL = 1 @@ -401,6 +401,8 @@ def export_config_outputs(self) -> Dict[str, Any]: outputs["par.outputs.output_ilms_meta"] = "append" # to coalesce ILMs for current level of hierarchy outputs["vlsi.inputs.ilms"] = list(map(lambda s: s.to_setting(), self.get_input_ilms(full_tree=True))) outputs["vlsi.inputs.ilms_meta"] = "append" # to coalesce ILMs for entire hierarchical tree + outputs["par.outputs.output_dbs"] = self.output_dbs + outputs["par.outputs.output_dbs_meta"] = "append" # to coalesce dbs for current level of hierarchy outputs["par.outputs.output_gds"] = str(self.output_gds) outputs["par.outputs.output_netlist"] = str(self.output_netlist) outputs["par.outputs.output_sim_netlist"] = str(self.output_sim_netlist) @@ -435,6 +437,26 @@ def input_files(self, value: List[str]) -> None: self.attr_setter("_input_files", value) + @property + def input_dbs(self) -> List[str]: + """ + Get the (optional) input database files/dirs for top-down hierarchical mode. + + :return: The (optional) input database files/dirs for top-down hierarchical mode. + """ + try: + return self.attr_getter("_input_dbs", None) + except AttributeError: + raise ValueError("Nothing set for the (optional) input database files/dirs for top-down hierarchical mode yet") + + @input_dbs.setter + def input_dbs(self, value: List[str]) -> None: + """Set the (optional) input database files/dirs for top-down hierarchical mode.""" + if not (isinstance(value, List)): + raise TypeError("input_dbs must be a List[str]") + self.attr_setter("_input_dbs", value) + + @property def post_synth_sdc(self) -> Optional[str]: """ @@ -460,23 +482,43 @@ def post_synth_sdc(self, value: Optional[str]) -> None: @property def output_ilms(self) -> List[ILMStruct]: """ - Get the (optional) output ILM information for hierarchical mode. + Get the (optional) output ILM information for bottom-up hierarchical mode. - :return: The (optional) output ILM information for hierarchical mode. + :return: The (optional) output ILM information for bottom-up hierarchical mode. """ try: return self.attr_getter("_output_ilms", None) except AttributeError: - raise ValueError("Nothing set for the (optional) output ILM information for hierarchical mode yet") + raise ValueError("Nothing set for the (optional) output ILM information for bottom-up hierarchical mode yet") @output_ilms.setter def output_ilms(self, value: List[ILMStruct]) -> None: - """Set the (optional) output ILM information for hierarchical mode.""" + """Set the (optional) output ILM information for bottom-up hierarchical mode.""" if not (isinstance(value, List)): raise TypeError("output_ilms must be a List[ILMStruct]") self.attr_setter("_output_ilms", value) + @property + def output_dbs(self) -> List[str]: + """ + Get the (optional) output database files/dirs for each partition in top-down hierarchical mode. + + :return: The (optional) output database files/dirs for each partition in top-down hierarchical mode. + """ + try: + return self.attr_getter("_output_dbs", None) + except AttributeError: + raise ValueError("Nothing set for the (optional) output database files/dirs for each partition in top-down hierarchical mode yet") + + @output_dbs.setter + def output_dbs(self, value: List[str]) -> None: + """Set the (optional) output database files/dirs for each partition in top-down hierarchical mode.""" + if not (isinstance(value, List)): + raise TypeError("output_dbs must be a List[str]") + self.attr_setter("_output_dbs", value) + + @property def output_gds(self) -> str: """ diff --git a/tests/test_build_systems.py b/tests/test_build_systems.py index 10f0e68cb..d912b4d3e 100644 --- a/tests/test_build_systems.py +++ b/tests/test_build_systems.py @@ -211,9 +211,9 @@ def test_top_down_makefile(self, tmpdir) -> None: expected_targets.add(task + "-TopMod") expected_targets.add("redo-" + task + "-TopMod") if task == "par-partition": - expected_targets.add(os.path.join(tmpdir, "par-TopMod", task + "-output-full.json")) + expected_targets.add(os.path.join(tmpdir, "par-TopMod", "par-partition-output-full.json")) elif task == "par-assemble": - expected_targets.add(os.path.join(tmpdir, "par-TopMod", task.split("-")[0] + "-output-full.json")) + expected_targets.add(os.path.join(tmpdir, "par-TopMod", "par-output-full.json")) else: expected_targets.add(os.path.join(tmpdir, task + "-TopMod", task.split("-")[0] + "-output-full.json")) if task == "par-partition": @@ -225,12 +225,14 @@ def test_top_down_makefile(self, tmpdir) -> None: expected_targets.add(task + "-HierMod") expected_targets.add("redo-" + task + "-HierMod") if task == "par-partition": - expected_targets.add(os.path.join(tmpdir, "par-HierMod", task + "-output-full.json")) + expected_targets.add(os.path.join(tmpdir, "par-HierMod", "par-partition-output-full.json")) elif task == "par-assemble": - expected_targets.add(os.path.join(tmpdir, "par-HierMod", task.split("-")[0] + "-output-full.json")) + expected_targets.add(os.path.join(tmpdir, "par-HierMod", "par-output-full.json")) else: expected_targets.add(os.path.join(tmpdir, task + "-HierMod", task.split("-")[0] + "-output-full.json")) - if "rtl" not in task: + if task == "par-partition": + expected_targets.add(os.path.join(tmpdir, "par-HierMod-input.json")) + elif "rtl" not in task: expected_targets.add(os.path.join(tmpdir, task + "-HierMod-input.json")) leaf_tasks = {"sim-rtl", "power-rtl", "par", "sim-par", "power-par", "drc", "lvs", "formal-par", "timing-par"} for task in leaf_tasks: