Skip to content

Commit

Permalink
support for mesh powerstrap
Browse files Browse the repository at this point in the history
  • Loading branch information
yolowiz committed Mar 6, 2024
1 parent 5897249 commit 38b8372
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 45 deletions.
3 changes: 3 additions & 0 deletions hammer/config/defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,9 @@ par:
power_nets: [] # List of power nets to generate straps for. If empty, generates straps for all nets in vlsi.inputs.supplies
# type: List[str]

pattern: "coupled" # existing default powerstrap pattern (e.g. ----VDD-VSS----VDD-VSS----VDD-VSS----)


# DRC settings
drc.inputs:
# DRC settings
Expand Down
6 changes: 6 additions & 0 deletions hammer/config/defaults_types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ par:
# type: list[str]
strap_layers: list[str]


# Indicates if Hammer should generate straps for the stdcell rail layer
# type: bool
generate_rail_layer: bool
Expand All @@ -341,6 +342,11 @@ par:
# type: list[str]
power_nets: list[str]

# Powerstrap patterns: e.g. "coupled" or "mesh"
# type: str
pattern: str


# DRC settings
drc.inputs:
# Top RTL module.
Expand Down
15 changes: 12 additions & 3 deletions hammer/vlsi/hammer_vlsi_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,9 @@ def get_weight(supply_name: str) -> int:
raise NotImplementedError("Power strap generation method %s is not implemented" % method)


def specify_power_straps_by_tracks(self, layer_name: str, bottom_via_layer: str, blockage_spacing: Decimal, track_pitch: int, track_width: int, track_spacing: int, track_start: int, track_offset: Decimal, bbox: Optional[List[Decimal]], nets: List[str], add_pins: bool, layer_is_all_power: bool, antenna_trim_shape: str) -> List[str]:

def specify_power_straps_by_tracks(self, layer_name: str, bottom_via_layer: str, blockage_spacing: Decimal, track_pitch: int, track_width: int, track_spacing: int, track_start: int, track_offset: Decimal, bbox: Optional[List[Decimal]], nets: List[str], add_pins: bool, layer_is_all_power: bool, antenna_trim_shape: str, pattern: str) -> List[str]:

"""
Generate a list of TCL commands that will create power straps on a given layer by specifying the desired track consumption.
This method assumes that power straps are built bottom-up, starting with standard cell rails.
Expand All @@ -698,6 +700,7 @@ def specify_power_straps_by_tracks(self, layer_name: str, bottom_via_layer: str,
width = Decimal(0)
spacing = Decimal(0)
strap_offset = Decimal(0)

if track_spacing == 0:
# An all-power (100% utilization) layer results in us wanting to do a uniform strap pattern, so we can just calculate the
# maximum width and minimum spacing from the desired pitch, instead of using TWWT.
Expand All @@ -710,6 +713,9 @@ def specify_power_straps_by_tracks(self, layer_name: str, bottom_via_layer: str,
else:
width, spacing, strap_start = layer.get_width_spacing_start_twt(track_width, logger=self.logger.context(layer_name))
spacing = 2*spacing + (track_spacing - 1) * layer.pitch + layer.min_width
if pattern == "mesh":
spacing = pitch / 2 - width

offset = track_offset + track_start * layer.pitch + strap_start
assert width > Decimal(0), "Width must be greater than zero. You probably have a malformed tech plugin on layer {}.".format(layer_name)
assert spacing > Decimal(0), "Spacing must be greater than zero. You probably have a malformed tech plugin on layer {}.".format(layer_name)
Expand Down Expand Up @@ -762,7 +768,8 @@ def specify_all_power_straps_by_tracks(self, layer_names: List[str], bottom_via_
assert layer.index > last.index, "Must build power straps bottom-up"
if last.direction == layer.direction:
raise ValueError("Layers {a} and {b} run in the same direction, but have no power straps between them.".format(a=last.name, b=layer.name))


pattern = self._get_by_tracks_metal_setting("pattern", layer_name)
blockage_spacing = coerce_to_grid(float(self._get_by_tracks_metal_setting("blockage_spacing", layer_name)), layer.grid_unit)
track_width = int(self._get_by_tracks_metal_setting("track_width", layer_name))
track_spacing = int(self._get_by_tracks_metal_setting("track_spacing", layer_name))
Expand All @@ -785,7 +792,9 @@ def specify_all_power_straps_by_tracks(self, layer_names: List[str], bottom_via_
nets = [ground_net, power_nets[i]]
group_offset = offset + track_offset + track_pitch * i * layer.pitch
group_pitch = sum_weights * track_pitch
output.extend(self.specify_power_straps_by_tracks(layer_name, last.name, blockage_spacing, group_pitch, track_width, track_spacing, track_start, group_offset, bbox, nets, add_pins, layer_is_all_power, antenna_trim_shape))

output.extend(self.specify_power_straps_by_tracks(layer_name, last.name, blockage_spacing, group_pitch, track_width, track_spacing, track_start, group_offset, bbox, nets, add_pins, layer_is_all_power, antenna_trim_shape, pattern))

last = layer

self._dump_power_straps_for_hardmacros()
Expand Down
205 changes: 163 additions & 42 deletions tests/test_power_straps.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,10 @@ def add_stackup_and_site(in_dict: Dict[str, Any]) -> Dict[str, Any]:
driver = hammer_vlsi.HammerDriver(options)
assert driver.load_par_tool()
yield PowerStrapsTestContext(temp_dir=tech_dir_base, driver=driver)


# one test mesh, the other mixed
def simple_straps_options() -> Dict[str, Any]:
# TODO clean this up a bit

strap_layers = ["M4", "M5", "M6", "M7", "M8"]
pin_layers = ["M7", "M8"]
track_width = 4
track_width_M7 = 8
track_width_M8 = 10
track_spacing = 0
track_spacing_M6 = 1
power_utilization = 0.2
power_utilization_M8 = 1.0
track_start_M5 = 1
track_offset_M5 = 1.2
bottom_via_layer = "rail"

# VSS comes before VDD
nets = ["VSS", "VDD"]

Expand All @@ -101,32 +87,59 @@ def simple_straps_options() -> Dict[str, Any]:
"par.power_straps_mode": "generate",
"par.generate_power_straps_method": "by_tracks",
"par.generate_power_straps_options.by_tracks": {
"strap_layers": strap_layers,
"pin_layers": pin_layers,
"track_width": track_width,
"track_width_M7": track_width_M7,
"track_width_M8": track_width_M8,
"track_spacing": track_spacing,
"track_spacing_M6": track_spacing_M6,
"power_utilization": power_utilization,
"power_utilization_M8": power_utilization_M8,
"track_start_M5": track_start_M5,
"track_offset_M5": track_offset_M5,
"bottom_via_layer": bottom_via_layer

"strap_layers": ["M4", "M5", "M6", "M7", "M8"],
"pin_layers": ["M7", "M8"],
"track_width": 4,
"track_width_M7": 8,
"track_width_M8": 10,
"track_spacing": 0,
"track_spacing_M6": 1,
"power_utilization": 0.2,
"power_utilization_M8": 1.0,
"track_start_M5": 1,
"track_offset_M5": 1.2,
"bottom_via_layer": "rail"
}
}
return straps_options

def mesh_straps_options() -> Dict[str, Any]:
# TODO clean this up a bit

def multiple_domains_straps_options() -> Dict[str, Any]:
strap_layers = ["M4", "M5", "M8"]
pin_layers = ["M8"]
track_width = 8
track_spacing = 0
power_utilization = 0.2
power_utilization_M8 = 1.0
bottom_via_layer = "rail"
# VSS comes before VDD
nets = ["VSS", "VDD"]

straps_options = {
"vlsi.inputs.supplies": {
"power": [{"name": "VDD", "pins": ["VDD"]}],
"ground": [{"name": "VSS", "pins": ["VSS"]}],
"VDD": "1.00 V",
"GND": "0 V"
},
"par.power_straps_mode": "generate",
"par.generate_power_straps_method": "by_tracks",
"par.generate_power_straps_options.by_tracks": {
"strap_layers": ["M4", "M5", "M6", "M7", "M8"],
"pin_layers": ["M7", "M8"],
"track_width": 4,
"track_width_M7": 8,
"track_width_M8": 10,
"track_spacing": 0,
"track_spacing_M6": 1,
"power_utilization": 0.2,
"power_utilization_M8": 1.0,
"track_start_M5": 1,
"track_offset_M5": 1.2,
"pattern": 'coupled',
"pattern_M4": 'mesh',
"track_spacing_M4": 1,
"bottom_via_layer": "rail"
}
}
return straps_options

def multiple_domains_straps_options() -> Dict[str, Any]:
straps_options = {
"vlsi.inputs.supplies": {
"power": [{"name": "VDD", "pins": ["VDD"]}, {"name": "VDD2", "pins": ["VDD2"]}],
Expand All @@ -137,13 +150,13 @@ def multiple_domains_straps_options() -> Dict[str, Any]:
"par.power_straps_mode": "generate",
"par.generate_power_straps_method": "by_tracks",
"par.generate_power_straps_options.by_tracks": {
"strap_layers": strap_layers,
"pin_layers": pin_layers,
"track_width": track_width,
"track_spacing": track_spacing,
"power_utilization": power_utilization,
"power_utilization_M8": power_utilization_M8,
"bottom_via_layer": bottom_via_layer
"strap_layers": ["M4", "M5", "M8"],
"pin_layers": ["M8"],
"track_width": 8,
"track_spacing": 0,
"power_utilization": 0.2,
"power_utilization_M8": 1.0,
"bottom_via_layer": "rail"
}
}
return straps_options
Expand Down Expand Up @@ -253,7 +266,115 @@ def test_simple_by_tracks_power_straps(self, power_straps_test_context) -> None:
assert strap_pitch == required_pitch
else:
assert False, "Got the wrong layer_name: {}".format(layer_name)

@pytest.mark.parametrize("straps_options, tech_name", [(mesh_straps_options(), "mesh")])
def test_mesh_power_straps(self, power_straps_test_context) -> None:
""" Creates simple power straps using the by_tracks method """
c = power_straps_test_context
success, par_output = c.driver.run_par()
assert success

par_tool = c.driver.par_tool
# It's surpringly annoying to import mockpar.MockPlaceAndRoute, which is the class
# that contains the parse_mock_power_straps_file() method, so we're just ignoring
# that particular part of this
assert isinstance(par_tool, hammer_vlsi.HammerPlaceAndRouteTool)
stackup = par_tool.get_stackup()
parsed_out = par_tool.parse_mock_power_straps_file() # type: ignore
entries = cast(List[Dict[str, Any]], parsed_out)

# TODO: this is copied, avoid that
strap_layers = ["M4", "M5", "M6", "M7", "M8"]
pin_layers = ["M7", "M8"]
track_width = 4
track_spacing_M4 = 1
pattern_M4 = 'mesh'
track_width_M7 = 8
track_width_M8 = 10
track_spacing = 0
track_spacing_M6 = 1
power_utilization = 0.2
power_utilization_M8 = 1.0
track_start_M5 = 1
track_offset_M5 = 1.2
nets = ["VSS", "VDD"]

for entry in entries:
print(entry)
c.logger.debug("Power strap entry:" + str(entry))
layer_name = entry["layer_name"]
if layer_name == "M1":
# Standard cell rails
assert entry["tap_cell_name"] == "FakeTapCell"
assert entry["bbox"] == []
assert entry["nets"] == nets
continue

strap_width = Decimal(entry["width"])
strap_spacing = Decimal(entry["spacing"])
strap_pitch = Decimal(entry["pitch"])
strap_offset = Decimal(entry["offset"])
metal = stackup.get_metal(layer_name)
min_width = metal.min_width
group_track_pitch = strap_pitch / metal.pitch
track_offset = Decimal(str(track_offset_M5)) if layer_name == "M5" else Decimal(0)
track_start = Decimal(str(track_start_M5)) if layer_name == "M5" else Decimal(0)
used_tracks = round(Decimal(strap_offset + strap_width + strap_spacing + strap_width + strap_offset - 2 * (track_offset + track_start * metal.pitch)) / metal.pitch) - 1
if layer_name == "M4":
assert entry["bbox"] == []
assert entry["nets"] == nets
# TODO more tests in a future PR
assert strap_spacing == strap_pitch / 2 - strap_width
elif layer_name == "M5":
assert entry["bbox"] == []
assert entry["nets"] == nets
# Check that the requested tracks equals the used tracks
requested_tracks = track_width * 2 + track_spacing
assert used_tracks == requested_tracks
# Spacing should be at least the min spacing
min_spacing = metal.get_spacing_for_width(strap_width)
assert strap_spacing >= min_spacing
# TODO more tests in a future PR
elif layer_name == "M6":
assert entry["bbox"] == []
assert entry["nets"] == nets
# This is a sanity check that we didn't accidentally change something up above
assert track_spacing_M6 == 1
# We should be able to fit a track in between the stripes because track_spacing_M6 == 1
wire_to_strap_spacing = (strap_spacing - min_width) / 2
min_spacing = metal.get_spacing_for_width(strap_width)
assert wire_to_strap_spacing >= min_spacing
# Check that the requested tracks equals the used tracks
requested_tracks = track_width * 2 + track_spacing_M6
assert used_tracks == requested_tracks
# Spacing should be at least the min spacing
min_spacing = metal.get_spacing_for_width(strap_width)
assert wire_to_strap_spacing >= min_spacing
# TODO more tests in a future PR
elif layer_name == "M7":
assert entry["bbox"] == []
assert entry["nets"] == nets
# TODO more tests in a future PR
elif layer_name == "M8":
other_spacing = strap_pitch - (2 * strap_width) - strap_spacing
# Track spacing should be 0
assert track_spacing == 0
# Test that the power straps are symmetric
assert other_spacing == strap_spacing
# Spacing should be at least the min spacing
min_spacing = metal.get_spacing_for_width(strap_width)
assert other_spacing >= min_spacing
# Test that a slightly larger strap would be a DRC violation
new_spacing = metal.get_spacing_for_width(strap_width + metal.grid_unit)
new_pitch = (strap_width + metal.grid_unit + new_spacing) * 2
assert strap_pitch < new_pitch
# Test that the pitch does consume the right number of tracks
required_pitch = Decimal(track_width_M8 * 2) * metal.pitch
# 100% power utilzation should produce straps that consume 2*strap_width + strap_spacing tracks
assert strap_pitch == required_pitch
else:
assert False, "Got the wrong layer_name: {}".format(layer_name)

@pytest.mark.parametrize("straps_options, tech_name", [(multiple_domains_straps_options(), "multiple_domains")])
def test_multiple_domains(self, power_straps_test_context) -> None:
""" Tests multiple power domains """
Expand Down

0 comments on commit 38b8372

Please sign in to comment.