diff --git a/hammer/config/config_src.py b/hammer/config/config_src.py index f6f9080a1..ef8436f17 100644 --- a/hammer/config/config_src.py +++ b/hammer/config/config_src.py @@ -729,6 +729,11 @@ def get(self, key: str) -> Any: """Alias for get_setting().""" return self.get_setting(key) + def get_suffix(self, key: str, suffix: str) -> Any: + """Alias for get_setting_suffix().""" + return self.get_setting_suffix(key, suffix) + + def __getitem__(self, key: str) -> Any: """Alias for get_setting().""" return self.get_setting(key) @@ -755,6 +760,33 @@ def get_setting(self, key: str, nullvalue: Any = None, check_type: bool = True) value = self.get_config()[key] return nullvalue if value is None else value + def get_setting_suffix(self, key: str, suffix: str, nullvalue: Any = None, check_type: bool = True) -> Any: + """ + Retrieve a key, first trying with a suffix but returning base if found. + + :param key: Desired key. + :param suffix: Required suffix to search for. + :param nullvalue: Value to return out for nulls. + :param check_type: Flag to enforce type checking + :return: The given config + """ + default = key + override = default + "_" + suffix + value = None + try: + value = self.get_config()[override] + except: + try: + value = self.get_config()[default] + except: + raise KeyError(f"Both base key: {default} and overriden key: {override} are missing.") + + if default not in self.defaults: + warn(f"Base key: {default} does not have a default implementation") + if check_type: + self.check_setting(default) + return nullvalue if value is None else value + def set_setting(self, key: str, value: Any) -> None: """ Set the given key. The setting will be placed into the runtime dictionary. diff --git a/hammer/pcb/generic/__init__.py b/hammer/pcb/generic/__init__.py index 6eb87a3a3..817f8ba55 100644 --- a/hammer/pcb/generic/__init__.py +++ b/hammer/pcb/generic/__init__.py @@ -111,18 +111,19 @@ def create_bom_builder_pindata_txt(self) -> bool: sorted_assignments = naming_scheme.sort_by_name(bumps, assignments) # Use post-shrink pitch - pitch = self.technology.get_post_shrink_length(bumps.pitch) + pitch_x = self.technology.get_post_shrink_length(bumps.pitch_x) + pitch_y = self.technology.get_post_shrink_length(bumps.pitch_y) # There is no meaningful verion for this file # Set the units to metric (mm) output = "VER 0.0\nUNIT M\n" - x_offset = ((1 - bumps.x) * pitch) / 2 # type: Decimal - y_offset = ((1 - bumps.y) * pitch) / 2 # type: Decimal + x_offset = ((1 - bumps.x) * pitch_x) / 2 # type: Decimal + y_offset = ((1 - bumps.y) * pitch_y) / 2 # type: Decimal for bump in sorted_assignments: # note that the flip-chip mirroring happens here name = naming_scheme.name_bump(bumps, bump) - x = um2mm(Decimal(str(bumps.x - bump.x)) * pitch + x_offset, 3) - y = um2mm(Decimal(str(bump.y - 1)) * pitch + y_offset, 3) + x = um2mm(Decimal(str(bumps.x - bump.x)) * pitch_x + x_offset, 3) + y = um2mm(Decimal(str(bump.y - 1)) * pitch_y + y_offset, 3) # Fields in order (with valid options): # Name # X position (mm) @@ -182,14 +183,15 @@ def create_footprint(self) -> bool: bumps, assignments = self.get_bumps_and_assignments() # Use post-shrink pitch - pitch = self.technology.get_post_shrink_length(bumps.pitch) + pitch_x = self.technology.get_post_shrink_length(bumps.pitch_x) + pitch_y = self.technology.get_post_shrink_length(bumps.pitch_y) # Here is a good ref for the PADS V9 format: # ftp://ftp.freecalypso.org/pub/CAD/PADS/pdfdocs/Plib_ASCII.pdf # for now, let's make the outline 4 pitches larger than the bump array - outline_width = ((bumps.x + 3) * pitch) - outline_height = ((bumps.y + 3) * pitch) + outline_width = ((bumps.x + 3) * pitch_x) + outline_height = ((bumps.y + 3) * pitch_y) output = "*PADS-LIBRARY-PCB-DECALS-V9*\n\n" # Fields in order from left to right @@ -216,20 +218,20 @@ def create_footprint(self) -> bool: # we'll dog-ear the outline to indicate the reference bump in the top left # x and y are in mm, not um output += "{x} {y}\n".format(x=um2mm(-outline_width/2, 3), y=um2mm(-outline_height/2, 3)) - output += "{x} {y}\n".format(x=um2mm(-outline_width/2, 3), y=um2mm( outline_height/2 - 2*pitch, 3)) - output += "{x} {y}\n".format(x=um2mm(-outline_width/2 + 2*pitch, 3), y=um2mm( outline_height/2, 3)) + output += "{x} {y}\n".format(x=um2mm(-outline_width/2, 3), y=um2mm( outline_height/2 - 2*pitch_y, 3)) + output += "{x} {y}\n".format(x=um2mm(-outline_width/2 + 2*pitch_x, 3), y=um2mm( outline_height/2, 3)) output += "{x} {y}\n".format(x=um2mm( outline_width/2, 3), y=um2mm( outline_height/2, 3)) output += "{x} {y}\n".format(x=um2mm( outline_width/2, 3), y=um2mm(-outline_height/2, 3)) output += "{x} {y}\n".format(x=um2mm(-outline_width/2, 3), y=um2mm(-outline_height/2, 3)) # create all of the terminals - x_offset = ((1 - bumps.x) * pitch) / 2 # type: Decimal - y_offset = ((1 - bumps.y) * pitch) / 2 # type: Decimal + x_offset = ((1 - bumps.x) * pitch_x) / 2 # type: Decimal + y_offset = ((1 - bumps.y) * pitch_y) / 2 # type: Decimal naming_scheme = self.naming_scheme for bump in bumps.assignments: # note that the flip-chip mirroring happens here - x = um2mm(Decimal(str(bumps.x - bump.x)) * pitch + x_offset, 3) - y = um2mm(Decimal(str(bump.y - 1)) * pitch + y_offset, 3) + x = um2mm(Decimal(str(bumps.x - bump.x)) * pitch_x + x_offset, 3) + y = um2mm(Decimal(str(bump.y - 1)) * pitch_y + y_offset, 3) label = naming_scheme.name_bump(bumps, bump) output += "T{x} {y} {x} {y} {label}\n".format(x=x, y=y, label=label) diff --git a/hammer/tech/__init__.py b/hammer/tech/__init__.py index d79e7ee78..a59236cb9 100644 --- a/hammer/tech/__init__.py +++ b/hammer/tech/__init__.py @@ -395,6 +395,17 @@ def get_setting(self, key: str) -> Any: print(e) # TODO: fix the root cause return None + def get_setting_suffix(self, key: str) -> Any: + """Get a particular setting from the database with a suffix. + """ + try: + return self._database.get(key) + except AttributeError: + raise ValueError("Internal error: no database set by hammer-vlsi") + except KeyError as e: # this function is expected to return Optional[str] from extracted_tarballs_dir() + print(e) # TODO: fix the root cause + return None + def has_setting(self, key: str) -> bool: """Check if a setting exists in the database. """ diff --git a/hammer/vlsi/hammer_tool.py b/hammer/vlsi/hammer_tool.py index 2987b51ba..adc83ff6d 100644 --- a/hammer/vlsi/hammer_tool.py +++ b/hammer/vlsi/hammer_tool.py @@ -899,6 +899,19 @@ def get_setting(self, key: str, nullvalue: Any = None) -> Any: except AttributeError: raise ValueError("Internal error: no database set by hammer-vlsi") + def get_setting_suffix(self, key: str, suffix: str, nullvalue: Any = None) -> Any: + """ + Get a particular setting from the database with a suffix. + + :param key: Key of the setting to receive. + :param suffix: Suffix to search for on top of the base. + :param nullvalue: Value to return in case of null (leave as None to use the default). + """ + try: + return self._database.get_setting_suffix(key, suffix, nullvalue) + except AttributeError: + raise ValueError("Internal error: no database set by hammer-vlsi") + def set_setting(self, key: str, value: Any) -> None: """ Set a runtime setting in the database. @@ -1092,6 +1105,15 @@ def get_all_ground_nets(self) -> List[Supply]: def get_independent_ground_nets(self) -> List[Supply]: return list(filter(lambda x: x.tie is None, self.get_all_ground_nets())) + def _get_by_bump_dim_pitch(self) -> Dict[str, float]: + """ + Return pitches in the x and y directions. + """ + pitch_x = self.get_setting_suffix('vlsi.inputs.bumps.pitch', 'x') + pitch_y = self.get_setting_suffix('vlsi.inputs.bumps.pitch', 'y') + + return {'x': pitch_x, 'y': pitch_y} + def get_bumps(self) -> Optional[BumpsDefinition]: bumps_mode = self.get_setting("vlsi.inputs.bumps_mode") if bumps_mode == "empty": diff --git a/hammer/vlsi/hammer_vlsi_impl.py b/hammer/vlsi/hammer_vlsi_impl.py index 554f68f6e..73dd6afe1 100644 --- a/hammer/vlsi/hammer_vlsi_impl.py +++ b/hammer/vlsi/hammer_vlsi_impl.py @@ -775,15 +775,8 @@ def _get_by_tracks_metal_setting(self, key: str, layer_name: str) -> Any: :param key: The base key name (e.g. track_spacing). Do not include the namespace or metal override. :return: The value associated with the key, after applying any metal overrides """ - default = "par.generate_power_straps_options.by_tracks." + key - override = default + "_" + layer_name - try: - return self.get_setting(override) - except KeyError: - try: - return self.get_setting(default) - except KeyError: - raise ValueError("No value set for key {}".format(default)) + key = "par.generate_power_straps_options.by_tracks." + key + return self.get_setting_suffix(key, layer_name) def _get_by_tracks_track_pitch(self, layer_name: str) -> int: """ @@ -804,32 +797,6 @@ def _get_by_tracks_track_pitch(self, layer_name: str) -> int: consumed_tracks = 2 * track_width + track_spacing return round(consumed_tracks / power_utilization) - def _get_by_bump_dim_setting(self, key: str, dim_name: str) -> Any: - """ - Return bump settings based on the dimension. - :param key: The base key name (e.g., pitch). Do not include the namespace or metal override - :param dim_name: Provide a dimensional argument (x, y, z!) - """ - default = "vlsi.inputs.bumps." + key - override = default + "_" + dim_name - - try: - return self.get_setting(override) - except KeyError: - try: - return self.get_setting(default) - except KeyError: - raise ValueError("No value set for key {}".format(default)) - - def _get_by_bump_dim_pitch(self) -> Dict[str, float]: - """ - Return pitches in the x and y directions. - """ - pitch_x = self._get_by_bump_dim_setting('pitch', 'x') - pitch_y = self._get_by_bump_dim_setting('pitch', 'y') - - return {'x': pitch_x, 'y': pitch_y} - @abstractmethod def specify_power_straps(self, layer_name: str, bottom_via_layer_name: str, blockage_spacing: Decimal, pitch: Decimal, width: Decimal, spacing: Decimal, offset: Decimal, bbox: Optional[List[Decimal]], nets: List[str], add_pins: bool) -> List[str]: """ @@ -892,30 +859,6 @@ def signoff_results(self) -> int: """ pass -class DummyParTool(HammerPlaceAndRouteTool): - """ - This is a dummy implementation of PAR tool. - """ - - def fill_outputs(self) -> bool: - return True - - def specify_power_straps(self, layer_name: str, bottom_via_layer_name: str, blockage_spacing: Decimal, pitch: Decimal, width: Decimal, spacing: Decimal, offset: Decimal, bbox: Optional[List[Decimal]], nets: List[str], add_pins: bool) -> List[str]: - return [] - - @property - def specify_std_cell_power_straps(self, blockage_spacing: Decimal, bbox: Optional[List[Decimal]], nets: List[str]) -> List[str]: - return [] - def tool_config_prefix(self) -> str: - return "" - - def version_number(self, version: str) -> int: - return 1 - - @property - def steps(self) -> List[HammerToolStep]: - return [] - class HammerDRCTool(HammerSignoffTool): def export_config_outputs(self) -> Dict[str, Any]: diff --git a/tests/test_constraints.py b/tests/test_constraints.py index 9edde2046..6f55b39c8 100644 --- a/tests/test_constraints.py +++ b/tests/test_constraints.py @@ -10,12 +10,12 @@ import hammer.config as hammer_config from hammer.utils import add_dicts from hammer.tech import MacroSize -from hammer.vlsi import DummyHammerTool, DummyParTool +from hammer.vlsi import DummyHammerTool from hammer.vlsi.constraints import DelayConstraint, ClockPort, PinAssignment, PinAssignmentError, \ PinAssignmentSemiAutoError, PlacementConstraint, PlacementConstraintType, Margins, \ BumpAssignment, BumpsDefinition, BumpsPinNamingScheme, DecapConstraint from hammer.vlsi.units import TimeValue, CapacitanceValue - +from hammer.par.mockpar import MockPlaceAndRoute class TestClockConstraint: def check_src(self, yaml_src: str, ref_port: ClockPort) -> None: @@ -192,22 +192,6 @@ def test_bump_naming(self) -> None: definition = BumpsDefinition(x=8421,y=8421, pitch_x=Decimal("1.23"), pitch_y=Decimal("3.14"), global_x_offset=0, global_y_offset=0, cell="bumpcell",assignments=assignments) assert BumpsPinNamingScheme.A1.name_bump(definition, assignments[0]) == "AAAA8421" - - def test_get_by_bump_dim_setting(self) -> None: - """ - Test that we can extract a bump related key by dimensionality. - Note that the dimension is not optional because the underlying code now requires x, y. - """ - db = hammer_config.HammerDatabase() - db.update_project([{"vlsi.inputs.bumps.pitch": 1}, {"vlsi.inputs.bumps.pitch_b_x": 2}]) - tool = DummyParTool() - tool.set_database(db) - - pitch_x = tool._get_by_bump_dim_setting('pitch', 'x') - assert pitch_x == 1 - - pitch_x = tool._get_by_bump_dim_setting('pitch_b', 'x') - assert pitch_x == 2 def test_get_by_bump_dim_pitch(self) -> None: """ @@ -215,7 +199,7 @@ def test_get_by_bump_dim_pitch(self) -> None: """ db = hammer_config.HammerDatabase() db.update_project([{"vlsi.inputs.bumps.pitch": 1}]) - tool = DummyParTool() + tool = MockPlaceAndRoute() tool.set_database(db) pitch_set = tool._get_by_bump_dim_pitch() diff --git a/tests/test_tool.py b/tests/test_tool.py index 298026cd4..a3c8d56b4 100644 --- a/tests/test_tool.py +++ b/tests/test_tool.py @@ -387,6 +387,8 @@ def test_bumps(self, tmp_path, request) -> None: "y": 14, "pitch": 200, "cell": "MY_REDACTED_BUMP_CELL", + "global_x_offset": 1, + "global_y_offset": 1, "assignments": [ {"name": "reset", "x": 5, "y": 3}, {"no_connect": true, "x": 5, "y": 4},