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 4fa1a0c89..ac0f6d48f 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 @@ -124,12 +124,20 @@ vlsi.inputs: GND: "0 V" # GND (str): The default voltage of the primary ground net hierarchical: - mode: flat # Hierarchical par mode. (str) + mode: flat # User-defined hierarchical flow mode. (str) + # This determines how the build system will handle hierarchical designs. # Valid options: - # flat - Perform a flat place and route run. - # leaf - Leaf module in a hierarchical run - same as flat except that an extra write_ilm step is added. - # hierarchical - Module which has sub-modules which are also hierarchical. - # top - Top module in a hierarchical run - runs an extra assemble_design step. + # flat - Perform a flat flow. + # top_down - Perform a top-down hierarchical flow. + # bottom_up - Perform a bottom-up hierarchical flow. + # hierarchical - Same as bottom-up (legacy). + + module_mode: flat # Hierarchical par mode for the current module. (str) + # 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. @@ -323,7 +331,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 @@ -477,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. @@ -879,7 +893,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 efe311574..981c86cbb 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]] @@ -88,9 +88,15 @@ vlsi.inputs: GND: str hierarchical: - # Hierarchical par mode. (str) + # Flow mode. (str) mode: str + # 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 @@ -157,9 +163,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 @@ -226,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..101e50b5c 100644 --- a/hammer/par/innovus/__init__.py +++ b/hammer/par/innovus/__init__.py @@ -14,7 +14,7 @@ from hammer.vlsi.units import CapacitanceValue from hammer.logging import HammerVLSILogging import hammer.tech as hammer_tech -from hammer.tech import RoutingDirection +from hammer.tech import RoutingDirection, Metal, RoutingDirection from hammer.tech.specialcells import CellType from decimal import Decimal from hammer.common.cadence import CadenceTool @@ -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,12 @@ def fill_outputs(self) -> bool: else: self.output_ilms = [] + # Output databases depends on whether we ran partition_design. + if self.ran_partition_design: + self.output_dbs = list(map(lambda x: os.path.join(self.run_dir, f"{x}.enc.dat"), self.partitions)) + 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 +221,48 @@ 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}: + # 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 == HierarchicalMode.TDTop and self.partitioning: + # Partition + steps = pre_partition + [self.partition_design] + post_partition + elif self.hierarchical_mode == HierarchicalMode.TDHierarchical and self.partitioning: + # Only add additional fp constraints & partition + steps = [self.floorplan_design, self.partition_design] + post_partition + elif self.hierarchical_mode in {HierarchicalMode.TDTop, HierarchicalMode.TDHierarchical}: + # Assemble + steps = [self.assemble_design] + write_design_step + elif self.hierarchical_mode == HierarchicalMode.TDLeaf: + # Add floorplan constraints and continue + steps = [self.floorplan_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 +271,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) @@ -522,7 +554,15 @@ def clock_tree(self) -> bool: flatten_ilm update_constraint_mode -name my_constraint_mode -ilm_sdc_files {sdc} '''.format(sdc=self.post_synth_sdc), clean=True) - if len(self.get_clock_ports()) > 0: + # Check for clock constraints either in inputs or post-syn SDC file + clock_present = len(self.get_clock_ports()) > 0 + if not clock_present and self.post_synth_sdc is not None: + with open(self.post_synth_sdc, "r") as f: + for line in f: + if line.startswith("create_clock"): + clock_present = True + break + if clock_present: # Ignore clock tree when there are no clocks # If special cells are specified, explicitly set them instead of letting tool infer from libs buffer_cells = self.technology.get_special_cell_by_type(CellType.CTSBuffer) @@ -633,10 +673,23 @@ def opt_design(self) -> bool: self.verbose_append("opt_design -post_route -setup -hold -expanded_views") if self.hierarchical_mode.is_nonleaf_hierarchical(): self.verbose_append("unflatten_ilm") + + # Write a post-partitioning database to continue from later + if self.get_setting("vlsi.inputs.hierarchical.mode") in {"top_down", "top-down"}: + self.verbose_append(f"write_db {self.post_partition_db} -def") 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}") + # Trim timing graph for top-level timing optimization + self.verbose_append("create_active_logic_view -type flat_top") + self.verbose_append("opt_design -post_route -incremental -expanded_views") + self.verbose_append("time_design -post_route") return True def write_netlist(self) -> bool: @@ -858,6 +911,52 @@ 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: + return self.hierarchical_mode != HierarchicalMode.TDLeaf and self.get_setting("vlsi.inputs.hierarchical.partitioning") + + @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 post_partition_db(self) -> str: + return f"{self.top_module}_post_partition" + + def partition_design(self) -> bool: + """Partitioning in top-down hierarchical.""" + # Early global placement + self.verbose_append("place_design -concurrent_macros") + #TODO: feedthru insertion for narrow-channel/channel-less flows + # Early global routing + 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") + # Pin assignment + self.verbose_append("check_pin_assignment -pre_check") + self.verbose_append("assign_partition_pins") + self.verbose_append("check_pin_assignment -report_violating_pin") + # Timing budget + self.verbose_append("create_timing_budget") + # Write partition DBs + for partition in self.partitions: + self.verbose_append(f"commit_partitions -partition {partition} -pg_pushdown_honor_1801") + self.verbose_append(f"write_partitions {partition} -def -verilog") + self.verbose_append(f"write_partition_pins -partition {partition} {partition}_pin_info") + self.ran_partition_design = True + return True + def run_innovus(self) -> bool: # Quit Innovus. self.verbose_append("exit") @@ -931,7 +1030,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 +1080,10 @@ 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"} + master_partitions = {} # type: Dict[str, str] + ############## Actually generate the constraints ################ for constraint in floorplan_constraints: # Floorplan names/insts need to not include the top-level module, @@ -1030,7 +1133,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, @@ -1055,6 +1159,42 @@ def generate_floorplan_tcl(self) -> List[str]: 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: + if not constraint.master in master_partitions: + # Create a fence and master partition + 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_name = get_or_else(constraint.top_layer, global_top_layer) + halo_top_layer_idx = self.get_stackup().get_metal(halo_top_layer_name).index if halo_top_layer_name is not None else None + reserved_layers = [] # type: List[Metal] + tb_pin_layers = [] # type: List[Metal] + lr_pin_layers = [] # type: List[Metal] + if halo_top_layer_name is not None: + reserved_layers = self.get_stackup().get_metals_below_layer(halo_top_layer_name) + [self.get_stackup().get_metal(halo_top_layer_name)] + if len(reserved_layers) > 0: + tb_pin_layers = list(filter(lambda m: m.direction == RoutingDirection.Vertical and m.index != 1, reserved_layers)) + lr_pin_layers = list(filter(lambda m: m.direction == RoutingDirection.Horizontal and m.index != 1, reserved_layers)) + output.append("create_partition -hinst {inst} -place_halo {{{s} {s} {s} {s}}} -route_halo {s} {halo_top_layer_str} {reserved_layers_str} {tb_pin_layers_str} {lr_pin_layers_str}".format( + inst=new_path, + s=self.get_setting("par.blockage_spacing"), + halo_top_layer_str=f"-route_halo_top_layer {halo_top_layer_idx}" if halo_top_layer_idx is not None else "", + reserved_layers_str=f"-reserved_layer {{{' '.join(list(map(lambda m: m.name, reserved_layers)))}}}" if len(reserved_layers) > 0 else "", + tb_pin_layers_str="-pin_layer_top {{{layers}}} -pin_layer_bottom {{{layers}}}".format(layers=' '.join(list(map(lambda m: m.name, tb_pin_layers)))) if len(tb_pin_layers) > 0 else "", + lr_pin_layers_str="-pin_layer_left {{{layers}}} -pin_layer_right {{{layers}}}".format(layers=' '.join(list(map(lambda m: m.name, lr_pin_layers)))) if len(lr_pin_layers) > 0 else "" + )) + master_partitions[get_or_else(constraint.master, "")] = new_path + else: + # Create a clone partition and place it + output.append(f"create_partition -hinst {new_path} -copy_from {constraint.master}") + output.append(f"place_partition_clone -hinst {new_path} -location {{{constraint.x} {constraint.y}}}") + # Then legalize the placement to align with parent power grids + # TODO: without a bunch of flags, this does automatic detection. May not work in many cases. + output.append(f"align_partition_clones {constraint.master}") elif constraint.type == PlacementConstraintType.Obstruction: obs_types = get_or_else(constraint.obs_types, []) # type: List[ObstructionType] if ObstructionType.Place in obs_types: @@ -1084,7 +1224,12 @@ def generate_floorplan_tcl(self) -> List[str]: )) else: assert False, "Should not reach here" - return [chip_size_constraint] + output + + # If top-down hierarchical, chip size constraint is not needed for hier & leaf nodes + if td_hier and self.hierarchical_mode != HierarchicalMode.TDTop: + return output + else: + return [chip_size_constraint] + output def specify_std_cell_power_straps(self, blockage_spacing: Decimal, bbox: Optional[List[Decimal]], nets: List[str]) -> List[str]: """ 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/par/nop/__init__.py b/hammer/par/nop/__init__.py index 06116a851..0a5bdfd05 100644 --- a/hammer/par/nop/__init__.py +++ b/hammer/par/nop/__init__.py @@ -11,6 +11,7 @@ class NopPlaceAndRoute(HammerPlaceAndRouteTool, DummyHammerTool): def fill_outputs(self) -> bool: self.output_ilms = [] + self.output_dbs = [] self.output_gds = "/dev/null" self.output_netlist = "/dev/null" self.output_sim_netlist = "/dev/null" 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 6928d51a7..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,6 +825,15 @@ def hier_par_to_syn_action(self, driver: HammerDriver, append_error_func: Callab else: return self.get_full_config(driver, syn_input_only) + 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_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. """ drc_input_only = HammerDriver.par_output_to_drc_input(driver.project_config) @@ -1599,6 +1616,7 @@ def generate_build_inputs(driver: HammerDriver, append_error_func: Callable[[str :return: The diplomacy graph """ build_system = str(driver.database.get_setting("vlsi.core.build_system", "none")) + if build_system in BuildSystems: return BuildSystems[build_system](driver, append_error_func) else: diff --git a/hammer/vlsi/driver.py b/hammer/vlsi/driver.py index 9051826d0..edfbac2a3 100644 --- a/hammer/vlsi/driver.py +++ b/hammer/vlsi/driver.py @@ -222,7 +222,7 @@ def set_up_synthesis_tool(self, syn_tool: HammerSynthesisTool, syn_tool.set_database(self.database) syn_tool.run_dir = run_dir syn_tool.hierarchical_mode = HierarchicalMode.from_str( - self.database.get_setting("vlsi.inputs.hierarchical.mode")) + self.database.get_setting("vlsi.inputs.hierarchical.module_mode")) syn_tool.input_files = self.database.get_setting("synthesis.inputs.input_files") syn_tool.top_module = self.database.get_setting("synthesis.inputs.top_module", nullvalue="") syn_tool.submit_command = HammerSubmitCommand.get("synthesis", self.database) @@ -286,13 +286,14 @@ def set_up_par_tool(self, par_tool: HammerPlaceAndRouteTool, par_tool.set_database(self.database) par_tool.run_dir = run_dir par_tool.hierarchical_mode = HierarchicalMode.from_str( - self.database.get_setting("vlsi.inputs.hierarchical.mode")) + self.database.get_setting("vlsi.inputs.hierarchical.module_mode")) par_tool.submit_command = HammerSubmitCommand.get("par", self.database) missing_inputs = False # 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 = "" @@ -353,7 +354,7 @@ def set_up_drc_tool(self, drc_tool: HammerDRCTool, drc_tool.technology = self.tech drc_tool.set_database(self.database) drc_tool.hierarchical_mode = HierarchicalMode.from_str( - self.database.get_setting("vlsi.inputs.hierarchical.mode")) + self.database.get_setting("vlsi.inputs.hierarchical.module_mode")) drc_tool.submit_command = HammerSubmitCommand.get("drc", self.database) drc_tool.run_dir = run_dir # TODO hierarchical @@ -415,7 +416,7 @@ def set_up_lvs_tool(self, lvs_tool: HammerLVSTool, lvs_tool.technology = self.tech lvs_tool.set_database(self.database) lvs_tool.hierarchical_mode = HierarchicalMode.from_str( - self.database.get_setting("vlsi.inputs.hierarchical.mode")) + self.database.get_setting("vlsi.inputs.hierarchical.module_mode")) lvs_tool.submit_command = HammerSubmitCommand.get("lvs", self.database) lvs_tool.run_dir = run_dir @@ -540,7 +541,7 @@ def set_up_sim_tool(self, sim_tool: HammerSimTool, sim_tool.input_files = self.database.get_setting("sim.inputs.input_files") sim_tool.top_module = self.database.get_setting("sim.inputs.top_module", nullvalue="") sim_tool.hierarchical_mode = HierarchicalMode.from_str( - self.database.get_setting("vlsi.inputs.hierarchical.mode")) + self.database.get_setting("vlsi.inputs.hierarchical.module_mode")) # Special case: if non-leaf hierarchical and gate-level, append ilm sim netlists if sim_tool.hierarchical_mode.is_nonleaf_hierarchical() and sim_tool.level.is_gatelevel(): for ilm in sim_tool.get_input_ilms(): @@ -607,7 +608,7 @@ def set_up_power_tool(self, power_tool: HammerPowerTool, power_tool.technology = self.tech power_tool.set_database(self.database) power_tool.hierarchical_mode = HierarchicalMode.from_str( - self.database.get_setting("vlsi.inputs.hierarchical.mode")) + self.database.get_setting("vlsi.inputs.hierarchical.module_mode")) power_tool.submit_command = HammerSubmitCommand.get("power", self.database) power_tool.run_dir = run_dir @@ -681,7 +682,7 @@ def set_up_formal_tool(self, formal_tool: HammerFormalTool, formal_tool.check = self.database.get_setting("formal.inputs.check") formal_tool.input_files = self.database.get_setting("formal.inputs.input_files") formal_tool.hierarchical_mode = HierarchicalMode.from_str( - self.database.get_setting("vlsi.inputs.hierarchical.mode")) + self.database.get_setting("vlsi.inputs.hierarchical.module_mode")) # Special case: if non-leaf hierarchical, append ilm sim netlists if formal_tool.hierarchical_mode.is_nonleaf_hierarchical(): for ilm in formal_tool.get_input_ilms(): @@ -750,7 +751,7 @@ def set_up_timing_tool(self, timing_tool: HammerTimingTool, timing_tool.input_files = self.database.get_setting("timing.inputs.input_files") timing_tool.hierarchical_mode = HierarchicalMode.from_str( - self.database.get_setting("vlsi.inputs.hierarchical.mode")) + self.database.get_setting("vlsi.inputs.hierarchical.module_mode")) timing_tool.top_module = self.database.get_setting("timing.inputs.top_module") missing_inputs = False if len(timing_tool.input_files) == 0: @@ -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]: """ @@ -1659,6 +1684,25 @@ def get_hierarchical_settings(self) -> List[Tuple[str, dict]]: """ return self._hierarchical_helper()[0] + def get_hierarchical_flow_mode(self) -> str: + """ + hierarchical flow mode specified in the design yml file has to be one of the following options: + flat (the flat flow) + hierarchical or bottom_up (the bottom-up flow) + top_down (the top-down flow) + + if the mode specified is neither, it raises ValueError. + """ + + hier_mode_key = "vlsi.inputs.hierarchical.mode" + hier_mode = str(self.database.get_setting(hier_mode_key)) + + if(hier_mode not in ["flat", "hierarchical", "bottom_up", "bottom-up", "top_down", "top-down"]): + raise ValueError("Invalid value for " + hier_mode_key) + + return hier_mode + + def _hierarchical_helper(self) -> Tuple[List[Tuple[str, dict]], Dict[str, Tuple[List[str], List[str]]]]: """ Read settings from the database, determine leaf/hierarchical modules, an order of execution, and return an @@ -1668,6 +1712,7 @@ def _hierarchical_helper(self) -> Tuple[List[Tuple[str, dict]], Dict[str, Tuple[ :return: Tuple of (List of tuples of (module name, config snippet), the dependency graph) """ + hier_flow_mode = str(self.database.get_setting("vlsi.inputs.hierarchical.mode")) 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]] @@ -1783,20 +1828,34 @@ def visit_module(mod: str) -> None: output = [] # type: List[Tuple[str, dict]] + # initialize + mode = HierarchicalMode.Flat for module in order: - mode = HierarchicalMode.Hierarchical - if module == top_module: - mode = HierarchicalMode.Top - elif module in leaf_modules: - mode = HierarchicalMode.Leaf - elif module in intermediate_modules: - mode = HierarchicalMode.Hierarchical + if hier_flow_mode in ["hierarchical", "bottom_up", "bottom-up"]: + if module == top_module: + mode = HierarchicalMode.BUTop + elif module in leaf_modules: + mode = HierarchicalMode.BULeaf + elif module in intermediate_modules: + mode = HierarchicalMode.BUHierarchical + else: + assert "Should not get here" + elif hier_flow_mode in ["top_down", "top-down"]: + if module == top_module: + mode = HierarchicalMode.TDTop + elif module in leaf_modules: + mode = HierarchicalMode.TDLeaf + elif module in intermediate_modules: + mode = HierarchicalMode.TDHierarchical + else: + assert "Should not get here" else: assert "Should not get here" constraint_dict = { - "vlsi.inputs.hierarchical.mode": str(mode), + "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 e91cc25a1..ce62fb0ef 100644 --- a/hammer/vlsi/hammer_build_systems.py +++ b/hammer/vlsi/hammer_build_systems.py @@ -8,7 +8,93 @@ import os import sys import textwrap -from typing import List, Dict, Tuple, Callable +from typing import List, Dict, Tuple, Callable, Optional, Union +from hammer.utils import get_or_else + +class MakeActionRecipe: + def __init__(self, + # Name of the action + action: str, + # Override input configuration files + proj_confs: Optional[str] = None, + # Override the recipe dependencies + deps_ovrd: Optional[str] = None, + # Hierarchical target + hier: bool = True + ): + self.action = action + 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": # 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") + 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") + if lvl == "rtl": + default_pconf = [in_json] + else: + default_pconf.append(in_json) + self.pconf_str = get_or_else(proj_confs, " ".join(["-p " + x for x in default_pconf])) + self.deps = get_or_else(deps_ovrd, " ".join(default_pconf)) + + def phony_target(self) -> str: + 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}} + """) + +class MakeLinkRecipe: + def __init__(self, + # Name of the action + action: str, + # Hierarchical target + hier: bool = True + ): + self.action = action + (x, y) = action.split("-to-") + x_base = x.split("-")[0] + self.base = x_base + "-to-" + y + rd_suffix = "{suffix}" if hier else "-rundir" + self.x_out = os.path.join("{obj_dir}", f"{x}{rd_suffix}", f"{x_base}-output-full.json") + # Actions that can happen after multiple actions (syn, par) + if y in ["sim", "power", "formal", "timing"]: + y += "-" + x + y_in_suffix = "{suffix}-input" if hier else "-input" + 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}\n" + + def recipe(self) -> str: + return textwrap.dedent(f""" + {self.y_in}: {self.x_out} + \t$(HAMMER_EXEC) {{env_confs}} -p {self.x_out} $(HAMMER_EXTRA_ARGS) -o {self.y_in} --obj_dir {{obj_dir}} {self.base} + """) + + def redo_recipe(self) -> str: + return textwrap.dedent(f""" + redo-{self.action}{{suffix}}: + \t$(HAMMER_EXEC) {{env_confs}} -p {self.x_out} $(HAMMER_EXTRA_ARGS) -o {self.y_in} --obj_dir {{obj_dir}} {self.base} + """) def build_noop(driver: HammerDriver, append_error_func: Callable[[str], None]) -> dict: """ @@ -20,10 +106,9 @@ def build_noop(driver: HammerDriver, append_error_func: Callable[[str], None]) - dependency_graph = driver.get_hierarchical_dependency_graph() return dependency_graph - def build_makefile(driver: HammerDriver, append_error_func: Callable[[str], None]) -> dict: """ - Build a Makefile include in the obj_dir called hammer.d. This is intended to be dynamically + Build a Makefile include in the obj_dir called hammer.mk. This is intended to be dynamically created and included into a top-level Makefile. The Makefile will contain targets for the following hammer actions, as well as any necessary @@ -80,11 +165,11 @@ def build_makefile(driver: HammerDriver, append_error_func: Callable[[str], None $(OBJ_DIR)/hammer.d: $(GENERATED_CONF) $(HAMMER_EXEC) -e env.yaml $(foreach x,$(INPUT_CONFS) $(GENERATED_CONF), -p $(x)) --obj_dir $(OBJ_DIR) build - include $(OBJ_DIR)/hammer.d + include $(OBJ_DIR)/hammer.mk ``` The generated Makefile has a few variables that are set if absent. This allows the user to override them without - modifying hammer.d. They are listed as follows: + modifying hammer.mk. They are listed as follows: - HAMMER_EXEC: This sets the actual python executable containing the HammerDriver main() function. It is set to the executable used to generate the Makefile by default. - HAMMER_DEPENDENCIES: The list of dependences to use for the initial syn and pcb targets. It is set to the set @@ -99,8 +184,88 @@ def build_makefile(driver: HammerDriver, append_error_func: Callable[[str], None :param driver: The HammerDriver :return: The dependency graph """ + def mod_make_text(actions: List[Union[MakeActionRecipe, MakeLinkRecipe]]) -> str: + make_text = textwrap.dedent(""" + #################################################################################### + ## Steps for {mod} + #################################################################################### + """) + make_text += ".PHONY: " + " ".join([a.action + "{suffix}" for a in actions]) + "\n\n" + make_text += "\n".join([a.phony_target() for a in actions]) + "\n\n" + make_text += "\n".join([a.recipe() for a in actions]) + "\n\n" + make_text += textwrap.dedent(""" + # Redo steps + # These intentionally break the dependency graph, but allow the flexibility to rerun a step after changing a config. + # Hammer doesn't know what settings impact synthesis only, e.g., so these are for power-users who "know better." + # The HAMMER_EXTRA_ARGS variable allows patching in of new configurations with -p or using flow control (--to_step or --from_step), for example. + """) + make_text += ".PHONY: " + " ".join(["redo-" + a.action + "{suffix}" for a in actions]) + "\n\n" + make_text += "\n".join([a.redo_recipe() for a in actions]) + "\n\n" + make_text += "{custom_recipes}" + return make_text + + def gen_actions(hier_mode: str) -> List[Union[MakeActionRecipe, MakeLinkRecipe]]: + hier = False if hier_mode == "flat" else True + + # For exclusion from top-down nodes where synthesis is not run + syn_only = [ + MakeActionRecipe("syn", "{p_syn_in}", "{syn_deps}", hier=hier), + MakeLinkRecipe("syn-to-par", hier=hier), + MakeLinkRecipe("syn-to-sim", hier=hier), + MakeActionRecipe("sim-syn", hier=hier), + MakeLinkRecipe("sim-syn-to-power", hier=hier), + MakeLinkRecipe("syn-to-power", hier=hier), + MakeActionRecipe("power-syn", hier=hier), + MakeLinkRecipe("syn-to-formal", hier=hier), + MakeActionRecipe("formal-syn", hier=hier), + MakeLinkRecipe("syn-to-timing", hier=hier), + MakeActionRecipe("timing-syn", hier=hier), + ] # type: List[Union[MakeActionRecipe, MakeLinkRecipe]] + + # P&R is different for flat/bottom-up vs. top-down + common_par = [MakeActionRecipe("par", hier=hier)] # type: List[Union[MakeActionRecipe, MakeLinkRecipe]] + top_down_leaf_par = [MakeActionRecipe("par")] # type: List[Union[MakeActionRecipe, MakeLinkRecipe]] + top_down_top_par = [MakeActionRecipe("par-partition"), + MakeActionRecipe("par-assemble")] # type: List[Union[MakeActionRecipe, MakeLinkRecipe]] + top_down_hier_par = [MakeActionRecipe("par-partition"), + MakeActionRecipe("par-assemble")] # type: List[Union[MakeActionRecipe, MakeLinkRecipe]] + + # Common to all nodes + common = [ + MakeActionRecipe("sim-rtl", "{p_sim_rtl_in}", "{syn_deps}", hier=hier), + MakeLinkRecipe("sim-rtl-to-power", hier=hier), + MakeActionRecipe("power-rtl", hier=hier), + MakeLinkRecipe("par-to-sim", hier=hier), + MakeActionRecipe("sim-par", hier=hier), + MakeLinkRecipe("sim-par-to-power", hier=hier), + MakeLinkRecipe("par-to-power", hier=hier), + MakeActionRecipe("power-par", hier=hier), + MakeLinkRecipe("par-to-drc", hier=hier), + MakeActionRecipe("drc", hier=hier), + MakeLinkRecipe("par-to-lvs", hier=hier), + MakeActionRecipe("lvs", hier=hier), + MakeLinkRecipe("par-to-formal", hier=hier), + MakeActionRecipe("formal-par", hier=hier), + MakeLinkRecipe("par-to-timing", hier=hier), + MakeActionRecipe("timing-par", hier=hier) + ] # type: List[Union[MakeActionRecipe, MakeLinkRecipe]] + + if hier_mode in ["flat", "bottom_up"]: + return syn_only + common_par + common + elif hier_mode == "top_down_top": + return syn_only + top_down_top_par + common + elif hier_mode == "top_down_hier": + return top_down_hier_par + common + elif hier_mode == "top_down_leaf": + return top_down_leaf_par + common + else: + raise ValueError(f"Unknown hierarchical mode {hier_mode}") + + flow_mode = driver.get_hierarchical_flow_mode() dependency_graph = driver.get_hierarchical_dependency_graph() - makefile = os.path.join(driver.obj_dir, "hammer.d") + makefile = os.path.join(driver.obj_dir, "hammer.mk") + 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 @@ -116,7 +281,7 @@ def build_makefile(driver: HammerDriver, append_error_func: Callable[[str], None # Global steps that are the same for hier or flat pcb_run_dir = os.path.join(obj_dir, "pcb-rundir") pcb_out = os.path.join(pcb_run_dir, "pcb-output-full.json") - output += textwrap.dedent(""" + output += textwrap.dedent(f""" #################################################################################### ## Global steps #################################################################################### @@ -124,385 +289,108 @@ def build_makefile(driver: HammerDriver, append_error_func: Callable[[str], None pcb: {pcb_out} {pcb_out}: {syn_deps} - \t$(HAMMER_EXEC) {env_confs} {all_inputs} --obj_dir {obj_dir} pcb - - """.format(pcb_out=pcb_out, syn_deps=syn_deps, env_confs=env_confs, all_inputs=proj_confs, obj_dir=obj_dir)) - - make_text = textwrap.dedent(""" - #################################################################################### - ## Steps for {mod} - #################################################################################### - .PHONY: sim-rtl{suffix} syn{suffix} syn-to-sim{suffix} sim-syn{suffix} syn-to-par{suffix} par{suffix} par-to-sim{suffix} sim-par{suffix} sim-par-to-power{suffix} par-to-power{suffix} power-par{suffix} power-rtl{suffix} sim-rtl-to-power{suffix} sim-syn-to-power{suffix} syn-to-power{suffix} power-syn{suffix} par-to-drc{suffix} drc{suffix} par-to-lvs{suffix} lvs{suffix} syn-to-formal{suffix} formal-syn{suffix} par-to-formal{suffix} formal-par{suffix} syn-to-timing{suffix} timing-syn{suffix} par-to-timing{suffix} timing-par{suffix} - - sim-rtl{suffix} : {sim_rtl_out} - syn{suffix} : {syn_out} - - syn-to-sim{suffix} : {sim_syn_in} - sim-syn{suffix} : {sim_syn_out} - - syn-to-par{suffix} : {par_in} - par{suffix} : {par_out} - - par-to-sim{suffix} : {sim_par_in} - sim-par{suffix} : {sim_par_out} - - sim-par-to-power{suffix} : {power_sim_par_in} - par-to-power{suffix} : {power_par_in} - power-par{suffix} : {power_par_out} - - sim-rtl-to-power{suffix} : {power_sim_rtl_in} - power-rtl{suffix} : {power_rtl_out} - - sim-syn-to-power{suffix} : {power_sim_syn_in} - syn-to-power{suffix} : {power_syn_in} - power-syn{suffix} : {power_syn_out} - - par-to-drc{suffix} : {drc_in} - drc{suffix} : {drc_out} - - par-to-lvs{suffix} : {lvs_in} - lvs{suffix} : {lvs_out} - - syn-to-formal{suffix} : {formal_syn_in} - formal-syn{suffix} : {formal_syn_out} - - par-to-formal{suffix} : {formal_par_in} - formal-par{suffix} : {formal_par_out} - - syn-to-timing{suffix} : {timing_syn_in} - timing-syn{suffix} : {timing_syn_out} - - par-to-timing{suffix} : {timing_par_in} - timing-par{suffix} : {timing_par_out} - - {par_to_syn} - - {sim_rtl_out}: {syn_deps} $(HAMMER_SIM_RTL_DEPENDENCIES) - \t$(HAMMER_EXEC) {env_confs} {p_sim_rtl_in} $(HAMMER_EXTRA_ARGS) --sim_rundir {sim_rtl_run_dir} --obj_dir {obj_dir} sim{suffix} - - {power_sim_rtl_in}: {sim_rtl_out} - \t$(HAMMER_EXEC) {env_confs} -p {sim_rtl_out} $(HAMMER_EXTRA_ARGS) -o {power_sim_rtl_in} --obj_dir {obj_dir} sim-to-power - - {power_rtl_out}: {power_sim_rtl_in} $(HAMMER_POWER_RTL_DEPENDENCIES) - \t$(HAMMER_EXEC) {env_confs} -p {power_sim_rtl_in} $(HAMMER_EXTRA_ARGS) --power_rundir {power_rtl_run_dir} --obj_dir {obj_dir} power{suffix} - - {syn_out}: {syn_deps} $(HAMMER_SYN_DEPENDENCIES) - \t$(HAMMER_EXEC) {env_confs} {p_syn_in} $(HAMMER_EXTRA_ARGS) --obj_dir {obj_dir} syn{suffix} - - {sim_syn_in}: {syn_out} - \t$(HAMMER_EXEC) {env_confs} -p {syn_out} $(HAMMER_EXTRA_ARGS) -o {sim_syn_in} --obj_dir {obj_dir} syn-to-sim - - {sim_syn_out}: {sim_syn_in} $(HAMMER_SIM_SYN_DEPENDENCIES) - \t$(HAMMER_EXEC) {env_confs} -p {sim_syn_in} $(HAMMER_EXTRA_ARGS) --sim_rundir {sim_syn_run_dir} --obj_dir {obj_dir} sim{suffix} - - {power_sim_syn_in}: {sim_syn_out} - \t$(HAMMER_EXEC) {env_confs} -p {sim_syn_out} $(HAMMER_EXTRA_ARGS) -o {power_sim_syn_in} --obj_dir {obj_dir} sim-to-power - - {power_syn_in}: {syn_out} - \t$(HAMMER_EXEC) {env_confs} -p {syn_out} $(HAMMER_EXTRA_ARGS) -o {power_syn_in} --obj_dir {obj_dir} syn-to-power - - {power_syn_out}: {power_sim_syn_in} {power_syn_in} $(HAMMER_POWER_SYN_DEPENDENCIES) - \t$(HAMMER_EXEC) {env_confs} -p {power_sim_syn_in} -p {power_syn_in} $(HAMMER_EXTRA_ARGS) --power_rundir {power_syn_run_dir} --obj_dir {obj_dir} power{suffix} - - {par_in}: {syn_out} - \t$(HAMMER_EXEC) {env_confs} -p {syn_out} $(HAMMER_EXTRA_ARGS) -o {par_in} --obj_dir {obj_dir} syn-to-par - - {par_out}: {par_in} $(HAMMER_PAR_DEPENDENCIES) - \t$(HAMMER_EXEC) {env_confs} -p {par_in} $(HAMMER_EXTRA_ARGS) --obj_dir {obj_dir} par{suffix} - - {sim_par_in}: {par_out} - \t$(HAMMER_EXEC) {env_confs} -p {par_out} $(HAMMER_EXTRA_ARGS) -o {sim_par_in} --obj_dir {obj_dir} par-to-sim - - {sim_par_out}: {sim_par_in} $(HAMMER_SIM_PAR_DEPENDENCIES) - \t$(HAMMER_EXEC) {env_confs} -p {sim_par_in} $(HAMMER_EXTRA_ARGS) --sim_rundir {sim_par_run_dir} --obj_dir {obj_dir} sim{suffix} - - {power_sim_par_in}: {sim_par_out} - \t$(HAMMER_EXEC) {env_confs} -p {sim_par_out} $(HAMMER_EXTRA_ARGS) -o {power_sim_par_in} --obj_dir {obj_dir} sim-to-power - - {power_par_in}: {par_out} - \t$(HAMMER_EXEC) {env_confs} -p {par_out} $(HAMMER_EXTRA_ARGS) -o {power_par_in} --obj_dir {obj_dir} par-to-power - - {power_par_out}: {power_sim_par_in} {power_par_in} $(HAMMER_POWER_PAR_DEPENDENCIES) - \t$(HAMMER_EXEC) {env_confs} -p {power_sim_par_in} -p {power_par_in} $(HAMMER_EXTRA_ARGS) --power_rundir {power_par_run_dir} --obj_dir {obj_dir} power{suffix} - - {drc_in}: {par_out} - \t$(HAMMER_EXEC) {env_confs} -p {par_out} $(HAMMER_EXTRA_ARGS) -o {drc_in} --obj_dir {obj_dir} par-to-drc - - {drc_out}: {drc_in} $(HAMMER_DRC_DEPENDENCIES) - \t$(HAMMER_EXEC) {env_confs} -p {drc_in} $(HAMMER_EXTRA_ARGS) --obj_dir {obj_dir} drc{suffix} - - {lvs_in}: {par_out} - \t$(HAMMER_EXEC) {env_confs} -p {par_out} $(HAMMER_EXTRA_ARGS) -o {lvs_in} --obj_dir {obj_dir} par-to-lvs - - {lvs_out}: {lvs_in} $(HAMMER_LVS_DEPENDENCIES) - \t$(HAMMER_EXEC) {env_confs} -p {lvs_in} $(HAMMER_EXTRA_ARGS) --obj_dir {obj_dir} lvs{suffix} - - {formal_syn_in}: {syn_out} - \t$(HAMMER_EXEC) {env_confs} -p {syn_out} $(HAMMER_EXTRA_ARGS) -o {formal_syn_in} --obj_dir {obj_dir} syn-to-formal - - {formal_syn_out}: {formal_syn_in} $(HAMMER_FORMAL_SYN_DEPENDENCIES) - \t$(HAMMER_EXEC) {env_confs} -p {formal_syn_in} $(HAMMER_EXTRA_ARGS) --formal_rundir {formal_syn_run_dir} --obj_dir {obj_dir} formal{suffix} - - {formal_par_in}: {par_out} - \t$(HAMMER_EXEC) {env_confs} -p {par_out} $(HAMMER_EXTRA_ARGS) -o {formal_par_in} --obj_dir {obj_dir} par-to-formal - - {formal_par_out}: {formal_syn_in} $(HAMMER_FORMAL_PAR_DEPENDENCIES) - \t$(HAMMER_EXEC) {env_confs} -p {formal_par_in} $(HAMMER_EXTRA_ARGS) --formal_rundir {formal_par_run_dir} --obj_dir {obj_dir} formal{suffix} - - {timing_syn_in}: {syn_out} - \t$(HAMMER_EXEC) {env_confs} -p {syn_out} $(HAMMER_EXTRA_ARGS) -o {timing_syn_in} --obj_dir {obj_dir} syn-to-timing - - {timing_syn_out}: {timing_syn_in} $(HAMMER_TIMING_SYN_DEPENDENCIES) - \t$(HAMMER_EXEC) {env_confs} -p {timing_syn_in} $(HAMMER_EXTRA_ARGS) --timing_rundir {timing_syn_run_dir} --obj_dir {obj_dir} timing{suffix} - - {timing_par_in}: {par_out} - \t$(HAMMER_EXEC) {env_confs} -p {par_out} $(HAMMER_EXTRA_ARGS) -o {timing_par_in} --obj_dir {obj_dir} par-to-timing - - {timing_par_out}: {timing_syn_in} $(HAMMER_TIMING_PAR_DEPENDENCIES) - \t$(HAMMER_EXEC) {env_confs} -p {timing_par_in} $(HAMMER_EXTRA_ARGS) --timing_rundir {timing_par_run_dir} --obj_dir {obj_dir} timing{suffix} - - # Redo steps - # These intentionally break the dependency graph, but allow the flexibility to rerun a step after changing a config. - # Hammer doesn't know what settings impact synthesis only, e.g., so these are for power-users who "know better." - # The HAMMER_EXTRA_ARGS variable allows patching in of new configurations with -p or using --to_step or --from_step, for example. - .PHONY: redo-sim-rtl{suffix} redo-sim-rtl-to-power{suffix} redo-syn{suffix} redo-syn-to-sim{suffix} redo-syn-to-power{suffix} redo-sim-syn{suffix} redo-sim-syn-to-power{suffix} redo-syn-to-par{suffix} redo-par{suffix} redo-par-to-sim{suffix} redo-sim-par{suffix} redo-sim-par-to-power{suffix} redo-par-to-power{suffix} redo-power-par{suffix} redo-par-to-drc{suffix} redo-drc{suffix} redo-par-to-lvs{suffix} redo-lvs{suffix} redo-syn-to-formal{suffix} redo-formal-syn{suffix} redo-par-to-formal{suffix} redo-formal-par{suffix} redo-syn-to-timing{suffix} redo-timing-syn{suffix} redo-par-to-timing{suffix} redo-timing-par{suffix} - - redo-sim-rtl{suffix}: - \t$(HAMMER_EXEC) {env_confs} {p_sim_rtl_in} $(HAMMER_EXTRA_ARGS) --sim_rundir {sim_rtl_run_dir} --obj_dir {obj_dir} sim{suffix} - - redo-sim-rtl-to-power{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {sim_rtl_out} $(HAMMER_EXTRA_ARGS) -o {power_sim_rtl_in} --obj_dir {obj_dir} sim-to-power - - redo-power-rtl{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {power_sim_rtl_in} $(HAMMER_EXTRA_ARGS) --power_rundir {power_rtl_run_dir} --obj_dir {obj_dir} power{suffix} - - redo-syn{suffix}: - \t$(HAMMER_EXEC) {env_confs} {p_syn_in} $(HAMMER_EXTRA_ARGS) --obj_dir {obj_dir} syn{suffix} - - redo-syn-to-sim{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {syn_out} $(HAMMER_EXTRA_ARGS) -o {sim_syn_in} --obj_dir {obj_dir} syn-to-sim - - redo-syn-to-power{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {syn_out} $(HAMMER_EXTRA_ARGS) -o {power_syn_in} --obj_dir {obj_dir} syn-to-power - - redo-sim-syn{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {sim_syn_in} $(HAMMER_EXTRA_ARGS) --sim_rundir {sim_syn_run_dir} --obj_dir {obj_dir} sim{suffix} - - redo-sim-syn-to-power{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {sim_syn_out} $(HAMMER_EXTRA_ARGS) -o {power_sim_syn_in} --obj_dir {obj_dir} sim-to-power - - redo-syn-to-par{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {syn_out} $(HAMMER_EXTRA_ARGS) -o {par_in} --obj_dir {obj_dir} syn-to-par - - redo-power-syn{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {power_sim_syn_in} -p {power_syn_in} $(HAMMER_EXTRA_ARGS) --power_rundir {power_syn_run_dir} --obj_dir {obj_dir} power{suffix} - - redo-par{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {par_in} $(HAMMER_EXTRA_ARGS) --obj_dir {obj_dir} par{suffix} - - redo-par-to-sim{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {par_out} $(HAMMER_EXTRA_ARGS) -o {sim_par_in} --obj_dir {obj_dir} par-to-sim - - redo-sim-par{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {sim_par_in} $(HAMMER_EXTRA_ARGS) --sim_rundir {sim_par_run_dir} --obj_dir {obj_dir} sim{suffix} - - redo-sim-par-to-power{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {sim_par_out} $(HAMMER_EXTRA_ARGS) -o {power_sim_par_in} --obj_dir {obj_dir} sim-to-power - - redo-par-to-power{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {par_out} $(HAMMER_EXTRA_ARGS) -o {power_par_in} --obj_dir {obj_dir} par-to-power - - redo-power-par{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {power_sim_par_in} -p {power_par_in} $(HAMMER_EXTRA_ARGS) --power_rundir {power_par_run_dir} --obj_dir {obj_dir} power{suffix} - - redo-par-to-drc{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {par_out} $(HAMMER_EXTRA_ARGS) -o {drc_in} --obj_dir {obj_dir} par-to-drc - - redo-drc{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {drc_in} $(HAMMER_EXTRA_ARGS) --obj_dir {obj_dir} drc{suffix} - - redo-par-to-lvs{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {par_out} $(HAMMER_EXTRA_ARGS) -o {lvs_in} --obj_dir {obj_dir} par-to-lvs - - redo-lvs{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {lvs_in} $(HAMMER_EXTRA_ARGS) --obj_dir {obj_dir} lvs{suffix} - - redo-syn-to-formal{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {syn_out} $(HAMMER_EXTRA_ARGS) -o {formal_syn_in} --obj_dir {obj_dir} syn-to-formal - - redo-formal-syn{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {formal_syn_in} $(HAMMER_EXTRA_ARGS) --formal_rundir {formal_syn_run_dir} --obj_dir {obj_dir} formal{suffix} - - redo-par-to-formal{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {par_out} $(HAMMER_EXTRA_ARGS) -o {formal_par_in} --obj_dir {obj_dir} par-to-formal - - redo-formal-par{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {formal_par_in} $(HAMMER_EXTRA_ARGS) --formal_rundir {formal_par_run_dir} --obj_dir {obj_dir} formal{suffix} - - redo-syn-to-timing{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {syn_out} $(HAMMER_EXTRA_ARGS) -o {timing_syn_in} --obj_dir {obj_dir} syn-to-timing - - redo-timing-syn{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {timing_syn_in} $(HAMMER_EXTRA_ARGS) --timing_rundir {timing_syn_run_dir} --obj_dir {obj_dir} timing{suffix} - - redo-par-to-timing{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {par_out} $(HAMMER_EXTRA_ARGS) -o {timing_par_in} --obj_dir {obj_dir} par-to-timing - - redo-timing-par{suffix}: - \t$(HAMMER_EXEC) {env_confs} -p {timing_par_in} $(HAMMER_EXTRA_ARGS) --timing_rundir {timing_par_run_dir} --obj_dir {obj_dir} timing{suffix} + \t$(HAMMER_EXEC) {env_confs} {proj_confs} --obj_dir {obj_dir} pcb """) if not dependency_graph: # Flat flow top_module = str(driver.database.get_setting("synthesis.inputs.top_module")) + output += mod_make_text(gen_actions("flat")).format( + suffix="", mod=top_module, env_confs=env_confs, + p_sim_rtl_in=proj_confs, p_syn_in=proj_confs, obj_dir=obj_dir, + syn_deps=syn_deps, custom_recipes="") - # TODO make this DRY - sim_rtl_run_dir = os.path.join(obj_dir, "sim-rtl-rundir") - power_rtl_run_dir = os.path.join(obj_dir, "power-rtl-rundir") - syn_run_dir = os.path.join(obj_dir, "syn-rundir") - sim_syn_run_dir = os.path.join(obj_dir, "sim-syn-rundir") - power_syn_run_dir = os.path.join(obj_dir, "power-syn-rundir") - par_run_dir = os.path.join(obj_dir, "par-rundir") - sim_par_run_dir = os.path.join(obj_dir, "sim-par-rundir") - power_par_run_dir = os.path.join(obj_dir, "power-par-rundir") - drc_run_dir = os.path.join(obj_dir, "drc-rundir") - lvs_run_dir = os.path.join(obj_dir, "lvs-rundir") - formal_syn_run_dir = os.path.join(obj_dir, "formal-syn-rundir") - formal_par_run_dir = os.path.join(obj_dir, "formal-par-rundir") - timing_syn_run_dir = os.path.join(obj_dir, "timing-syn-rundir") - timing_par_run_dir = os.path.join(obj_dir, "timing-par-rundir") - - p_sim_rtl_in = proj_confs - sim_rtl_out = os.path.join(sim_rtl_run_dir, "sim-output-full.json") - power_sim_rtl_in = os.path.join(obj_dir, "power-sim-rtl-input.json") - #power_rtl_in = os.path.join(obj_dir, "power-rtl-input.json") - power_rtl_out = os.path.join(power_rtl_run_dir, "power-output-full.json") - p_syn_in = proj_confs - syn_out = os.path.join(syn_run_dir, "syn-output-full.json") - sim_syn_in = os.path.join(obj_dir, "sim-syn-input.json") - sim_syn_out = os.path.join(sim_syn_run_dir, "sim-output-full.json") - power_sim_syn_in = os.path.join(obj_dir, "power-sim-syn-input.json") - power_syn_in = os.path.join(obj_dir, "power-syn-input.json") - power_syn_out = os.path.join(power_syn_run_dir, "power-output-full.json") - par_in = os.path.join(obj_dir, "par-input.json") - par_out = os.path.join(par_run_dir, "par-output-full.json") - sim_par_in = os.path.join(obj_dir, "sim-par-input.json") - sim_par_out = os.path.join(sim_par_run_dir, "sim-output-full.json") - power_sim_par_in = os.path.join(obj_dir, "power-sim-par-input.json") - power_par_in = os.path.join(obj_dir, "power-par-input.json") - power_par_out = os.path.join(power_par_run_dir, "power-output-full.json") - drc_in = os.path.join(obj_dir, "drc-input.json") - drc_out = os.path.join(drc_run_dir, "drc-output-full.json") - lvs_in = os.path.join(obj_dir, "lvs-input.json") - lvs_out = os.path.join(lvs_run_dir, "lvs-output-full.json") - formal_syn_in = os.path.join(obj_dir, "formal-syn-input.json") - formal_syn_out = os.path.join(formal_syn_run_dir, "formal-output-full.json") - formal_par_in = os.path.join(obj_dir, "formal-par-input.json") - formal_par_out = os.path.join(formal_par_run_dir, "formal-output-full.json") - timing_syn_in = os.path.join(obj_dir, "timing-syn-input.json") - timing_syn_out = os.path.join(timing_syn_run_dir, "timing-output-full.json") - timing_par_in = os.path.join(obj_dir, "timing-par-input.json") - timing_par_out = os.path.join(timing_par_run_dir, "timing-output-full.json") - - par_to_syn = "" - - output += make_text.format(suffix="", mod=top_module, env_confs=env_confs, obj_dir=obj_dir, syn_deps=syn_deps, - par_to_syn=par_to_syn, - p_sim_rtl_in=p_sim_rtl_in, sim_rtl_out=sim_rtl_out, sim_rtl_run_dir=sim_rtl_run_dir, - sim_syn_in=sim_syn_in, sim_syn_out=sim_syn_out, sim_syn_run_dir=sim_syn_run_dir, - power_sim_rtl_in=power_sim_rtl_in, power_rtl_out=power_rtl_out, power_rtl_run_dir=power_rtl_run_dir, - sim_par_in=sim_par_in, sim_par_out=sim_par_out, sim_par_run_dir=sim_par_run_dir, - p_syn_in=p_syn_in, syn_out=syn_out, par_in=par_in, par_out=par_out, - power_sim_syn_in=power_sim_syn_in, power_syn_in=power_syn_in, power_syn_out=power_syn_out, power_syn_run_dir=power_syn_run_dir, - power_sim_par_in=power_sim_par_in, power_par_in=power_par_in, power_par_out=power_par_out, power_par_run_dir=power_par_run_dir, - drc_in=drc_in, drc_out=drc_out, lvs_in=lvs_in, lvs_out=lvs_out, - formal_syn_in=formal_syn_in, formal_syn_out=formal_syn_out, formal_syn_run_dir=formal_syn_run_dir, - formal_par_in=formal_par_in, formal_par_out=formal_par_out, formal_par_run_dir=formal_par_run_dir, - timing_syn_in=timing_syn_in, timing_syn_out=timing_syn_out, timing_syn_run_dir=timing_syn_run_dir, - timing_par_in=timing_par_in, timing_par_out=timing_par_out, timing_par_run_dir=timing_par_run_dir) else: - # Hierarchical flow - for node, edges in dependency_graph.items(): - out_edges = edges[1] - - # TODO make this DRY - sim_rtl_run_dir = os.path.join(obj_dir, "sim-rtl-" + node) - power_rtl_run_dir = os.path.join(obj_dir, "power-rtl-" + node) - syn_run_dir = os.path.join(obj_dir, "syn-" + node) - sim_syn_run_dir = os.path.join(obj_dir, "sim-syn-" + node) - power_syn_run_dir = os.path.join(obj_dir, "power-syn-" + node) - par_run_dir = os.path.join(obj_dir, "par-" + node) - sim_par_run_dir = os.path.join(obj_dir, "sim-par-" + node) - power_par_run_dir = os.path.join(obj_dir, "power-par-" + node) - drc_run_dir = os.path.join(obj_dir, "drc-" + node) - lvs_run_dir = os.path.join(obj_dir, "lvs-" + node) - formal_syn_run_dir = os.path.join(obj_dir, "formal-syn-" + node) - formal_par_run_dir = os.path.join(obj_dir, "formal-par-" + node) - timing_syn_run_dir = os.path.join(obj_dir, "timing-syn-" + node) - timing_par_run_dir = os.path.join(obj_dir, "timing-par-" + node) - - p_sim_rtl_in = proj_confs - sim_rtl_out = os.path.join(sim_rtl_run_dir, "sim-output-full.json") - power_sim_rtl_in = os.path.join(obj_dir, "power-sim-rtl-{}-input.json".format(node)) - #power_rtl_in = os.path.join(obj_dir, "power-rtl-{}-input.json".format(node)) - power_rtl_out = os.path.join(power_rtl_run_dir, "power-output-full.json") - p_syn_in = proj_confs - syn_out = os.path.join(syn_run_dir, "syn-output-full.json") - sim_syn_in = os.path.join(obj_dir, "sim-syn-{}-input.json".format(node)) - sim_syn_out = os.path.join(sim_syn_run_dir, "sim-output-full.json") - power_sim_syn_in = os.path.join(obj_dir, "power-sim-syn-{}-input.json".format(node)) - power_syn_in = os.path.join(obj_dir, "power-syn-{}-input.json".format(node)) - power_syn_out = os.path.join(power_syn_run_dir, "power-output-full.json") - par_in = os.path.join(obj_dir, "par-{}-input.json".format(node)) - par_out = os.path.join(par_run_dir, "par-output-full.json") - sim_par_in = os.path.join(obj_dir, "sim-par-{}-input.json".format(node)) - sim_par_out = os.path.join(sim_par_run_dir, "sim-output-full.json") - power_sim_par_in = os.path.join(obj_dir, "power-sim-par-{}-input.json".format(node)) - power_par_in = os.path.join(obj_dir, "power-par-{}-input.json".format(node)) - power_par_out = os.path.join(power_par_run_dir, "power-output-full.json") - drc_in = os.path.join(obj_dir, "drc-{}-input.json".format(node)) - drc_out = os.path.join(drc_run_dir, "drc-output-full.json") - lvs_in = os.path.join(obj_dir, "lvs-{}-input.json".format(node)) - lvs_out = os.path.join(lvs_run_dir, "lvs-output-full.json") - formal_syn_in = os.path.join(obj_dir, "formal-syn-{}-input.json".format(node)) - formal_syn_out = os.path.join(formal_syn_run_dir, "formal-output-full.json") - formal_par_in = os.path.join(obj_dir, "formal-par-{}-input.json".format(node)) - formal_par_out = os.path.join(formal_par_run_dir, "formal-output-full.json") - timing_syn_in = os.path.join(obj_dir, "timing-syn-{}-input.json".format(node)) - timing_syn_out = os.path.join(timing_syn_run_dir, "timing-output-full.json") - timing_par_in = os.path.join(obj_dir, "timing-par-{}-input.json".format(node)) - timing_par_out = os.path.join(timing_par_run_dir, "timing-output-full.json") - - # need to revert this each time - syn_deps = "$(HAMMER_DEPENDENCIES)" - par_to_syn = "" - if len(out_edges) > 0: - syn_deps = os.path.join(obj_dir, "syn-{}-input.json".format(node)) - p_syn_in = "-p {}".format(syn_deps) - out_confs = [os.path.join(obj_dir, "par-" + x, "par-output-full.json") for x in out_edges] - prereqs = " ".join(out_confs) - pstring = " ".join(["-p " + x for x in out_confs]) - par_to_syn = textwrap.dedent(""" - {syn_deps}: {prereqs} - \t$(HAMMER_EXEC) {env_confs} {pstring} -o {syn_deps} --obj_dir {obj_dir} hier-par-to-syn - """.format(syn_deps=syn_deps, prereqs=prereqs, env_confs=env_confs, pstring=pstring, - obj_dir=obj_dir)) - - output += make_text.format(suffix="-"+node, mod=node, env_confs=env_confs, obj_dir=obj_dir, syn_deps=syn_deps, - par_to_syn=par_to_syn, - p_sim_rtl_in=p_sim_rtl_in, sim_rtl_out=sim_rtl_out, sim_rtl_run_dir=sim_rtl_run_dir, - power_sim_rtl_in=power_sim_rtl_in, power_rtl_out=power_rtl_out, power_rtl_run_dir=power_rtl_run_dir, - sim_syn_in=sim_syn_in, sim_syn_out=sim_syn_out, sim_syn_run_dir=sim_syn_run_dir, - sim_par_in=sim_par_in, sim_par_out=sim_par_out, sim_par_run_dir=sim_par_run_dir, - p_syn_in=p_syn_in, syn_out=syn_out, par_in=par_in, par_out=par_out, - power_sim_syn_in=power_sim_syn_in, power_syn_in=power_syn_in, power_syn_out=power_syn_out, power_syn_run_dir=power_syn_run_dir, - power_sim_par_in=power_sim_par_in, power_par_in=power_par_in, power_par_out=power_par_out, power_par_run_dir=power_par_run_dir, - drc_in=drc_in, drc_out=drc_out, lvs_in=lvs_in, lvs_out=lvs_out, - formal_syn_in=formal_syn_in, formal_syn_out=formal_syn_out, formal_syn_run_dir=formal_syn_run_dir, - formal_par_in=formal_par_in, formal_par_out=formal_par_out, formal_par_run_dir=formal_par_run_dir, - timing_syn_in=timing_syn_in, timing_syn_out=timing_syn_out, timing_syn_run_dir=timing_syn_run_dir, - timing_par_in=timing_par_in, timing_par_out=timing_par_out, timing_par_run_dir=timing_par_run_dir) + # Top-down hierarchical flow + if flow_mode in ["top_down", "top-down"]: + for node, edges in dependency_graph.items(): + parent_edges = edges[0] + out_edges = edges[1] + + # Common linking recipes + assemble_confs = [os.path.join(obj_dir, "par-" + x, "par-output-full.json") for x in out_edges] + pstring=" ".join(["-p " + x for x in assemble_confs]) + par_out_confs=" ".join(assemble_confs) + par_deps = os.path.join(obj_dir, f"par-assemble-{node}-input.json") + par_to_par_assemble = textwrap.dedent(f""" + .PHONY: hier-par-to-par-assemble-{node} + hier-par-to-par-assemble-{node}: {par_deps} + + {par_deps}: {par_out_confs} + \t$(HAMMER_EXEC) {env_confs} {pstring} -o {par_deps} --obj_dir {obj_dir} hier-par-to-par-assemble + + redo-hier-par-to-par-assemble-{node}: + \t$(HAMMER_EXEC) {env_confs} {pstring} -o {par_deps} --obj_dir {obj_dir} hier-par-to-par-assemble + + """) + + 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) + 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} + + {par_deps}: {par_out_confs} + \t$(HAMMER_EXEC) {env_confs} {pstring} -o {par_deps} --obj_dir {obj_dir} hier-par-partition-to-par + + redo-hier-par-partition-to-par-{node}: + \t$(HAMMER_EXEC) {env_confs} {pstring} -o {par_deps} --obj_dir {obj_dir} hier-par-partition-to-par + + """) + + # Generate the makefile text based on node type + if len(parent_edges) == 0: # top node + output += mod_make_text(gen_actions("top_down_top")).format( + suffix="-"+node, mod=node, env_confs=env_confs, obj_dir=obj_dir, syn_deps=syn_deps, + p_sim_rtl_in=proj_confs, p_syn_in=proj_confs, + custom_recipes=par_to_par_assemble) + + elif len(out_edges) == 0: # leaf node + output += mod_make_text(gen_actions("top_down_leaf")).format( + suffix="-"+node, mod=node, env_confs=env_confs, obj_dir=obj_dir, syn_deps=syn_deps, + p_sim_rtl_in=proj_confs, + custom_recipes=par_partition_to_par) + + else: # hierarchical node + output += mod_make_text(gen_actions("top_down_hier")).format( + suffix="-"+node, mod=node, env_confs=env_confs, obj_dir=obj_dir, syn_deps=syn_deps, + p_sim_rtl_in=proj_confs, + custom_recipes=par_partition_to_par + par_to_par_assemble) + + # Bottom-up hierarchical flow + else: + for node, edges in dependency_graph.items(): + parent_edges = edges[0] + out_edges = edges[1] + + # need to revert these each time we encounter a leaf node + syn_deps = "$(HAMMER_DEPENDENCIES)" + p_syn_in = "" + par_to_syn = "" + + if len(out_edges) > 0: + syn_deps = os.path.join(obj_dir, f"syn-{node}-input.json") + p_syn_in = f"-p {syn_deps}" + out_confs = [os.path.join(obj_dir, "par-" + x, "par-output-full.json") for x in out_edges] + pstring = " ".join(["-p " + x for x in out_confs]) + par_to_syn = textwrap.dedent(f""" + .PHONY: hier-par-to-syn-{node} + hier-par-to-syn-{node}: {syn_deps} + + {syn_deps}: {" ".join(out_confs)} + \t$(HAMMER_EXEC) {env_confs} {pstring} -o {syn_deps} --obj_dir {obj_dir} hier-par-to-syn + + redo-hier-par-to-syn-{node}: + \t$(HAMMER_EXEC) {env_confs} {pstring} -o {syn_deps} --obj_dir {obj_dir} hier-par-to-syn + + """) + + output += mod_make_text(gen_actions("bottom_up")).format( + suffix="-"+node, mod=node, env_confs=env_confs, obj_dir=obj_dir, syn_deps=syn_deps, + p_sim_rtl_in=proj_confs, p_syn_in=p_syn_in, custom_recipes=par_to_syn) with open(makefile, "w") as f: f.write(output) diff --git a/hammer/vlsi/hammer_tool.py b/hammer/vlsi/hammer_tool.py index 2949a68a7..17bf5ad17 100644 --- a/hammer/vlsi/hammer_tool.py +++ b/hammer/vlsi/hammer_tool.py @@ -309,8 +309,7 @@ def input_files(self, value: List[str]) -> None: @property def hierarchical_mode(self) -> HierarchicalMode: """ - Input files for this tool library. - The exact nature of the files will depend on the type of library. + HierarchicalMode for the module being run through this tool. """ try: return self.attr_getter("_hierarchical_mode", None) @@ -320,8 +319,7 @@ def hierarchical_mode(self) -> HierarchicalMode: @hierarchical_mode.setter def hierarchical_mode(self, value: HierarchicalMode) -> None: """ - Set the input files for this tool library. - The exact nature of the files will depend on the type of library. + HierarchicalMode for the module being run through this tool. """ if not isinstance(value, HierarchicalMode): raise TypeError("hierarchical_mode must be a HierarchicalMode") diff --git a/hammer/vlsi/hammer_vlsi_impl.py b/hammer/vlsi/hammer_vlsi_impl.py index 551881451..02be05a0e 100644 --- a/hammer/vlsi/hammer_vlsi_impl.py +++ b/hammer/vlsi/hammer_vlsi_impl.py @@ -22,17 +22,23 @@ class HierarchicalMode(Enum): Flat = 1 - Leaf = 2 - Hierarchical = 3 - Top = 4 + BULeaf = 2 + BUHierarchical = 3 + BUTop = 4 + TDLeaf = 5 + TDHierarchical = 6 + TDTop = 7 @classmethod def __mapping(cls) -> Dict[str, "HierarchicalMode"]: return { "flat": HierarchicalMode.Flat, - "leaf": HierarchicalMode.Leaf, - "hierarchical": HierarchicalMode.Hierarchical, - "top": HierarchicalMode.Top + "bu_leaf": HierarchicalMode.BULeaf, + "bu_hierarchical": HierarchicalMode.BUHierarchical, + "bu_top": HierarchicalMode.BUTop, + "td_leaf": HierarchicalMode.TDLeaf, + "td_hierarchical": HierarchicalMode.TDHierarchical, + "td_top": HierarchicalMode.TDTop } @staticmethod @@ -48,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.Top + return self in {HierarchicalMode.BUHierarchical, HierarchicalMode.BUTop} class FlowLevel(Enum): RTL = 1 @@ -395,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) @@ -429,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]: """ @@ -454,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 b8046b10b..d912b4d3e 100644 --- a/tests/test_build_systems.py +++ b/tests/test_build_systems.py @@ -54,10 +54,10 @@ def test_flat_makefile(self, tmpdir) -> None: CLIDriver.generate_build_inputs(driver, lambda x: None) - d_file = os.path.join(driver.obj_dir, "hammer.d") - assert os.path.exists(d_file) + mk_file = os.path.join(driver.obj_dir, "hammer.mk") + assert os.path.exists(mk_file) - with open(d_file, "r") as f: + with open(mk_file, "r") as f: contents = f.readlines() targets = self._read_targets_from_makefile(contents) @@ -76,16 +76,16 @@ def test_flat_makefile(self, tmpdir) -> None: # TODO at some point we should add more tests - def test_hier_makefile(self, tmpdir) -> None: + def test_bottom_up_makefile(self, tmpdir) -> None: """ - Test that a Makefile for a hierarchical design is generated correctly. + Test that a Makefile for a bottom-up hierarchical design is generated correctly. """ proj_config = os.path.join(str(tmpdir), "config.json") settings = { "vlsi.core.technology": "hammer.technology.nop", "vlsi.core.build_system": "make", - "vlsi.inputs.hierarchical.mode": "hierarchical", + "vlsi.inputs.hierarchical.mode": "bottom-up", "vlsi.inputs.hierarchical.top_module": "TopMod", "vlsi.inputs.hierarchical.config_source": "manual", "vlsi.inputs.hierarchical.manual_modules": [{"TopMod": ["SubModA", "SubModB"]}], @@ -115,10 +115,10 @@ def test_hier_makefile(self, tmpdir) -> None: CLIDriver.generate_build_inputs(driver, lambda x: None) - d_file = os.path.join(driver.obj_dir, "hammer.d") - assert os.path.exists(d_file) + mk_file = os.path.join(driver.obj_dir, "hammer.mk") + assert os.path.exists(mk_file) - with open(d_file, "r") as f: + with open(mk_file, "r") as f: contents = f.readlines() targets = self._read_targets_from_makefile(contents) @@ -134,13 +134,13 @@ def test_hier_makefile(self, tmpdir) -> None: for task in bridge_tasks: expected_targets.update({task + "-" + x for x in mods}) expected_targets.update({"redo-" + task + "-" + x for x in mods}) + expected_targets.update({"hier-par-to-syn-TopMod", "redo-hier-par-to-syn-TopMod"}) # Only non-leafs get a syn-*-input.json target expected_targets.update({os.path.join(tmpdir, "syn-" + x + "-input.json") for x in mods if x in {"TopMod"}}) expected_targets.update({os.path.join(tmpdir, "sim-syn-" + x + "-input.json") for x in mods}) expected_targets.update({os.path.join(tmpdir, "par-" + x + "-input.json") for x in mods}) expected_targets.update({os.path.join(tmpdir, "sim-par-" + x + "-input.json") for x in mods}) - #expected_targets.update({os.path.join(tmpdir, "power-rtl-" + x + "-input.json") for x in mods}) expected_targets.update({os.path.join(tmpdir, "power-syn-" + x + "-input.json") for x in mods}) expected_targets.update({os.path.join(tmpdir, "power-par-" + x + "-input.json") for x in mods}) expected_targets.update({os.path.join(tmpdir, "power-sim-rtl-" + x + "-input.json") for x in mods}) @@ -156,3 +156,109 @@ def test_hier_makefile(self, tmpdir) -> None: assert set(targets.keys()) == expected_targets # TODO at some point we should add more tests + + def test_top_down_makefile(self, tmpdir) -> None: + """ + Test that a Makefile for a top-down hierarchical design is generated correctly. + """ + proj_config = os.path.join(str(tmpdir), "config.json") + + settings = { + "vlsi.core.technology": "hammer.technology.nop", + "vlsi.core.build_system": "make", + "vlsi.inputs.hierarchical.mode": "top-down", + "vlsi.inputs.hierarchical.top_module": "TopMod", + "vlsi.inputs.hierarchical.config_source": "manual", + "vlsi.inputs.hierarchical.manual_modules": [{"TopMod": ["HierMod"]}, {"HierMod": ["LeafMod"]}], + "vlsi.inputs.hierarchical.manual_placement_constraints": [ + {"TopMod": [ + {"path": "top", "type": "toplevel", "x": 0, "y": 0, "width": 1234, "height": 7890, "margins": {"left": 1, "top": 2, "right": 3, "bottom": 4}}, + {"path": "top/C", "type": "placement", "x": 2, "y": 102, "width": 30, "height": 40}, + {"path": "top/A", "type": "hierarchical", "x": 200, "y": 120, "master": "HierMod"}]}, + {"HierMod": [ + {"path": "a", "type": "toplevel", "x": 0, "y": 0, "width": 100, "height": 200, "margins": {"left": 0, "top": 0, "right": 0, "bottom": 0}}, + {"path": "top/B", "type": "hierarchical", "x": 10, "y": 30, "master": "LeafMod"}]}, + {"LeafMod": [ + {"path": "b", "type": "toplevel", "x": 0, "y": 0, "width": 340, "height": 160, "margins": {"left": 0, "top": 0, "right": 0, "bottom": 0}}]} + ] + } + with open(proj_config, "w") as f: + f.write(json.dumps(settings, cls=HammerJSONEncoder, indent=4)) + + options = HammerDriverOptions( + environment_configs=[], + project_configs=[proj_config], + log_file=os.path.join(str(tmpdir), "log.txt"), + obj_dir=str(tmpdir) + ) + + driver = HammerDriver(options) + + CLIDriver.generate_build_inputs(driver, lambda x: None) + + mk_file = os.path.join(driver.obj_dir, "hammer.mk") + assert os.path.exists(mk_file) + + with open(mk_file, "r") as f: + contents = f.readlines() + + targets = self._read_targets_from_makefile(contents) + + mods = {"TopMod", "HierMod", "LeafMod"} + expected_targets = {"pcb", os.path.join(tmpdir, "pcb-rundir", "pcb-output-full.json")} + top_tasks = {"sim-rtl", "power-rtl", "syn", "sim-syn", "power-syn", "par-partition", "par-assemble", "sim-par", "power-par", "drc", "lvs", "formal-syn", "formal-par", "timing-syn", "timing-par"} + for task in top_tasks: + expected_targets.add(task + "-TopMod") + expected_targets.add("redo-" + task + "-TopMod") + if task == "par-partition": + 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", "par-output-full.json")) + else: + expected_targets.add(os.path.join(tmpdir, task + "-TopMod", task.split("-")[0] + "-output-full.json")) + if task == "par-partition": + expected_targets.add(os.path.join(tmpdir, "par-TopMod-input.json")) + elif "rtl" not in task and task != "syn": + expected_targets.add(os.path.join(tmpdir, task + "-TopMod-input.json")) + hier_tasks = {"sim-rtl", "power-rtl", "par-partition", "par-assemble", "sim-par", "power-par", "drc", "lvs", "formal-par", "timing-par"} + for task in hier_tasks: + expected_targets.add(task + "-HierMod") + expected_targets.add("redo-" + task + "-HierMod") + if task == "par-partition": + 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", "par-output-full.json")) + else: + expected_targets.add(os.path.join(tmpdir, task + "-HierMod", task.split("-")[0] + "-output-full.json")) + 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: + expected_targets.add(task + "-LeafMod") + expected_targets.add("redo-" + task + "-LeafMod") + expected_targets.add(os.path.join(tmpdir, task + "-LeafMod", task.split("-")[0] + "-output-full.json")) + if "rtl" not in task: + expected_targets.add(os.path.join(tmpdir, task + "-LeafMod-input.json")) + top_bridge_tasks = {"syn-to-sim", "syn-to-par", "par-to-sim", "par-to-lvs", "par-to-drc", "syn-to-power", "par-to-power", "sim-rtl-to-power", "sim-syn-to-power", "sim-par-to-power", "syn-to-formal", "par-to-formal", "syn-to-timing", "par-to-timing"} + for task in top_bridge_tasks: + expected_targets.add(task + "-TopMod") + expected_targets.add("redo-" + task + "-TopMod") + hier_leaf_bridge_tasks = {"par-to-sim", "par-to-lvs", "par-to-drc", "par-to-power", "sim-rtl-to-power", "sim-par-to-power", "par-to-formal", "par-to-timing"} + for task in hier_leaf_bridge_tasks: + expected_targets.update({task + "-" + x for x in {"HierMod", "LeafMod"}}) + expected_targets.update({"redo-" + task + "-" + x for x in {"HierMod", "LeafMod"}}) + # Hier linking tasks + expected_targets.update({"hier-par-partition-to-par-HierMod", "redo-hier-par-partition-to-par-HierMod"}) + expected_targets.update({"hier-par-partition-to-par-LeafMod", "redo-hier-par-partition-to-par-LeafMod"}) + expected_targets.update({"hier-par-to-par-assemble-HierMod", "redo-hier-par-to-par-assemble-HierMod"}) + expected_targets.update({"hier-par-to-par-assemble-TopMod", "redo-hier-par-to-par-assemble-TopMod"}) + # Extra power inputs + expected_targets.update({os.path.join(tmpdir, "power-sim-rtl-" + x + "-input.json") for x in mods}) + expected_targets.add(os.path.join(tmpdir, "power-sim-syn-" + "TopMod-input.json")) + expected_targets.update({os.path.join(tmpdir, "power-sim-par-" + x + "-input.json") for x in mods}) + + assert set(targets.keys()) == expected_targets + + # TODO at some point we should add more tests