Skip to content

Commit

Permalink
Units Overhaul (#800)
Browse files Browse the repository at this point in the history
* units should be set in SDC, enable reading power intent

* units overhaul - get time & cap units from the tech library

* clarify hport

* clarify custom SDC constraints, misc doc fixes
  • Loading branch information
harrisonliew authored Oct 3, 2023
1 parent 1639d65 commit f06f7ca
Show file tree
Hide file tree
Showing 21 changed files with 138 additions and 41 deletions.
7 changes: 5 additions & 2 deletions doc/Hammer-Basics/Hammer-Overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ Main Hammer

Hammer provides the Python backend for a Hammer project and exposes a set of APIs that are typical of modern VLSI flows. These APIs are then implemented by a tool plugin and a technology plugin of the designer's choice. The structure of Hammer is meant to enable re-use and portability between technologies.

Hammer takes its inputs and serializes its state in the form of YAML and JSON files. The designer sets a variety of settings in the form of keys in different namespaces that are designated in Hammer to control its functionality. These keys are contained in ``hammer/hammer/config/defaults.yml``. This file shows all of the keys that are a part of main Hammer and provides sensible defaults that may be overridden or are set to null if they must be provided by the designer.
Hammer takes its inputs and serializes its state in the form of YAML and JSON files, called Hammer IR. The designer sets a variety of settings in the form of keys in different namespaces that are designated in Hammer to control its functionality.

.. note::
Supported keys are contained in `defaults.yml <https://github.com/ucb-bar/hammer/blob/master/hammer/config/defaults.yml>__`. This file provides sensible defaults that may be overridden or are set to null if they must be provided by the designer.

Here is an example of a snippet that would be included in the user's input configuration.

.. _library-example:
.. code-block:: yaml
vlsi.core.technology: "asap7"
vlsi.core.technology: "hammer.technology.asap7"
vlsi.inputs.supplies:
VDD: "0.7 V"
GND: "0 V"
Expand Down
4 changes: 2 additions & 2 deletions doc/Hammer-Use/Hammer-APIs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Power strap constraints are specified using multiple Hammer IR keys in the ``par
.. literalinclude:: ../../hammer/technology/asap7/defaults.yml
:language: yaml
:linenos:
:lines: 59-80
:lines: 60-81
:caption: ASAP7 default power straps setting
..
Expand All @@ -47,7 +47,7 @@ The default keys for all hammer configs are defined in the `defaults.yml <https:
.. literalinclude:: ../../hammer/config/defaults.yml
:language: yaml
:linenos:
:lines: 570-604
:lines: 572-610
:caption: Hammer global default power straps setting
..
Expand Down
1 change: 0 additions & 1 deletion doc/Technology/Tech-json.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ Below is an example of the installs and tarballs from the ASAP7 plugin.
"name": "ASAP7 Library",
"grid_unit": "0.001",
"time_unit": "1 ps",
"installs": [
{
"id": "$PDK",
Expand Down
3 changes: 2 additions & 1 deletion hammer/config/config_src.py
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,8 @@ def check_setting(self, key: str, cfg: Optional[dict] = None) -> bool:
if cfg is None:
cfg = self.get_config()
if key not in self.get_config_types():
self.logger.warning(f"Key {key} is not associated with a type")
#TODO: compile this at the beginning instead of emitting every instance
#self.logger.warning(f"Key {key} is not associated with a type")
return True
try:
exp_value_type = parse_setting_type(self.get_config_types()[key])
Expand Down
1 change: 1 addition & 0 deletions hammer/config/defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ vlsi.inputs:

custom_sdc_constraints: [] # List of custom sdc constraints to use. (List[str])
# These are appended after all other generated constraints (clock, pin, delay, load, etc.).
# IMPORTANT: Since SDC is unitless, you must manually verify that your time & cap units match the tech library's units.

placement_constraints: [] # List of placement constraints for floorplanning.
# Each item is a struct member:
Expand Down
7 changes: 3 additions & 4 deletions hammer/par/innovus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,6 @@ def init_design(self) -> bool:
verbose_append("set_db timing_analysis_cppr both")
# Use OCV mode for timing analysis by default
verbose_append("set_db timing_analysis_type ocv")
# Match SDC time units to timing libraries
verbose_append("set_library_unit -time 1{}".format(self.get_time_unit().value_prefix + self.get_time_unit().unit))

# Read LEF layouts.
lef_files = self.technology.read_libs([
Expand Down Expand Up @@ -559,8 +557,9 @@ def add_fillers(self) -> bool:
else:
decap_cells = decaps[0].name
decap_caps = [] # type: List[float]
cap_unit = self.get_cap_unit().value_prefix + self.get_cap_unit().unit
if decaps[0].size is not None:
decap_caps = list(map(lambda x: CapacitanceValue(x).value_in_units("fF"), decaps[0].size))
decap_caps = list(map(lambda x: CapacitanceValue(x).value_in_units(cap_unit), decaps[0].size))
if len(decap_cells) != len(decap_caps):
self.logger.error("Each decap cell in the name list must have a corresponding decapacitance value in the size list.")
decap_consts = list(filter(lambda x: x.target=="capacitance", self.get_decap_constraints()))
Expand All @@ -580,7 +579,7 @@ def add_fillers(self) -> bool:
assert isinstance(const.height, Decimal)
area_str = " ".join(("-area", str(const.x), str(const.y), str(const.x+const.width), str(const.y+const.height)))
self.verbose_append("add_decaps -effort high -total_cap {CAP} {AREA}".format(
CAP=const.capacitance.value_in_units("fF"), AREA=area_str))
CAP=const.capacitance.value_in_units(cap_unit), AREA=area_str))

if len(stdfillers) == 0:
self.logger.warning(
Expand Down
13 changes: 7 additions & 6 deletions hammer/synthesis/genus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,16 +250,17 @@ def init_environment(self) -> bool:
verbose_append("set_db module:{top}/{mod} .preserve true".format(top=self.top_module, mod=ilm.module))
verbose_append("init_design -top {}".format(self.top_module))

# Setup power settings from cpf/upf
# Difference from other tools: apply_power_intent after read
power_cmds = self.generate_power_spec_commands()
power_cmds.insert(1, "apply_power_intent -summary")
for l in power_cmds:
verbose_append(l)

# Prevent floorplanning targets from getting flattened.
# TODO: is there a way to track instance paths through the synthesis process?
verbose_append("set_db root: .auto_ungroup none")

# Set units to pF and technology time unit.
# Must be done after elaboration.
verbose_append("set_units -capacitance 1.0pF")
verbose_append("set_load_unit -picofarads 1")
verbose_append("set_units -time 1.0{}".format(self.get_time_unit().value_prefix + self.get_time_unit().unit))

# Set "don't use" cells.
for l in self.generate_dont_use_commands():
self.append(l)
Expand Down
42 changes: 34 additions & 8 deletions hammer/tech/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@

from hammer.config import load_yaml, HammerJSONEncoder
from hammer.logging import HammerVLSILoggingContext
from hammer.utils import (LEFUtils, add_lists, deeplist, get_or_else,
from hammer.utils import (LEFUtils, LIBUtils, add_lists, deeplist, get_or_else,
in_place_unique, optional_map, reduce_list_str,
reduce_named, coerce_to_grid)
from hammer.vlsi.units import TimeValue, CapacitanceValue

if TYPE_CHECKING:
from hammer.vlsi.hooks import HammerToolHookAction
Expand Down Expand Up @@ -213,7 +214,6 @@ def from_setting(grid_unit: Decimal, d: Dict[str, Any]) -> "Site":
class TechJSON(BaseModel):
name: str
grid_unit: Optional[str]
time_unit: Optional[str]
shrink_factor: Optional[str]
installs: Optional[List[PathPrefix]]
libraries: Optional[List[Library]]
Expand Down Expand Up @@ -350,6 +350,10 @@ def __init__(self):
# Configuration
self.config: TechJSON = None

# Units
self.time_unit: Optional[TimeValue] = None
self.cap_unit: Optional[CapacitanceValue] = None

@classmethod
def load_from_module(cls, tech_module: str) -> Optional["HammerTechnology"]:
"""Load a technology from a given module.
Expand All @@ -372,9 +376,31 @@ def load_from_module(cls, tech_module: str) -> Optional["HammerTechnology"]:
elif tech_yaml.is_file():
tech.config = TechJSON.parse_raw(json.dumps(load_yaml(tech_yaml.read_text())))
return tech
else:
else: #TODO - from Pydantic model instance
return None

def get_lib_units(self) -> None:
"""
Get time and capacitance units from the first LIB file
Must be called right after the tech module is loaded.
"""
libs = self.read_libs(
[filters.get_timing_lib_with_preference("NLDM")],
HammerTechnologyUtils.to_plain_item)
if len(libs) > 0:
tu = LIBUtils.get_time_unit(libs[0])
cu = LIBUtils.get_cap_unit(libs[0])
if tu is None:
self.logger.error("Error in parsing first NLDM Liberty file for time units.")
else:
self.time_unit = TimeValue(tu)
if cu is None:
self.logger.error("Error in parsing first NLDM Liberty file for capacitance units.")
else:
self.cap_unit = CapacitanceValue(cu)
else:
self.logger.error("No NLDM libs defined. Time/cap units will be defined by the tool or another technology.")

def set_database(self, database: hammer_config.HammerDatabase) -> None:
"""Set the settings database for use by the tool."""
self._database = database # type: hammer_config.HammerDatabase
Expand Down Expand Up @@ -1259,19 +1285,19 @@ def get_timing_lib_with_preference(self, lib_pref: str = "NLDM") -> LibraryFilte
Select ASCII .lib timing libraries. Prefers NLDM, then ECSM, then CCS if multiple are present for
a single given .lib.
"""
lib_pref = lib_pref.upper()
lib_pref = lib_pref.upper()

def paths_func(lib: Library) -> List[str]:
pref_list = ["NLDM", "ECSM", "CCS"]
index = None

try:
index = pref_list.index(lib_pref)
except:
except:
raise ValueError("Library preference must be one of NLDM, ECSM, or CCS.")
pref_list.insert(0, pref_list.pop(index))

for elem in pref_list:
for elem in pref_list:
if elem == "NLDM":
if lib.nldm_liberty_file is not None:
return [lib.nldm_liberty_file]
Expand All @@ -1283,7 +1309,7 @@ def paths_func(lib: Library) -> List[str]:
return [lib.ccs_liberty_file]
else:
pass

return []

return LibraryFilter(
Expand Down
1 change: 0 additions & 1 deletion hammer/technology/asap7/asap7.tech.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "ASAP7 Library",
"grid_unit": "0.001",
"time_unit": "1 ps",
"installs": [
{
"id": "$PDK",
Expand Down
1 change: 0 additions & 1 deletion hammer/technology/nangate45/nangate45.tech.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "Nangate45 Library",
"grid_unit": "0.005",
"time_unit": "1 ps",
"installs": [{
"id": "nangate45",
"path": "technology.nangate45.install_dir"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "Skywater 130nm Library",
"grid_unit": "0.001",
"time_unit": "1 ns",
"installs": [
{
"id": "$SKY130A",
Expand All @@ -19,4 +18,4 @@
]
}
]
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "Skywater 130nm Library",
"grid_unit": "0.001",
"time_unit": "1 ns",
"installs": [
{
"id": "$SKY130_NDA",
Expand Down
3 changes: 1 addition & 2 deletions hammer/technology/sky130/sky130.tech.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"name": "Skywater 130nm Library",
"grid_unit": "0.001",
"time_unit": "1 ns",
"installs": [
{
"id": "$SKY130_NDA",
Expand Down Expand Up @@ -2023,4 +2022,4 @@
"y": 5.44
}
]
}
}
1 change: 1 addition & 0 deletions hammer/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from .verilog_utils import *
from .lef_utils import *
from .lib_utils import *


def deepdict(x: dict) -> dict:
Expand Down
57 changes: 57 additions & 0 deletions hammer/utils/lib_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# lib_utils.py
# Misc Liberty utilities
#
# See LICENSE for licence details.

import re
import os
import gzip
from decimal import Decimal
from typing import List, Optional, Tuple
__all__ = ['LIBUtils']


class LIBUtils:
@staticmethod
def get_time_unit(source: str) -> Optional[str]:
"""
Get the time unit from the given LIB source file.
"""
lines = LIBUtils.get_headers(source)
try:
match = next(line for line in lines if "time_unit" in line)
# attibute syntax: time_unit : <value><unit> ;
unit = re.split(" : | ; ", match)[1]
return unit
except StopIteration: # should not get here
return None

@staticmethod
def get_cap_unit(source: str) -> Optional[str]:
"""
Get the load capacitance unit from the given LIB source file.
"""
lines = LIBUtils.get_headers(source)
try:
match = next(line for line in lines if "capacitive_load_unit" in line)
# attibute syntax: capacitive_load_unit(<value>,<unit>);
# Also need to capitalize last f to F
split = re.split("\(|,|\)", match)
return split[1] + split[2].strip()[:-1] + split[2].strip()[-1].upper()
except StopIteration: # should not get here
return None

@staticmethod
def get_headers(source: str) -> List[str]:
"""
Get the header lines with the major info
"""
nbytes = 10000
if source.split('.')[-1] == "gz":
z = gzip.GzipFile(source)
lines = z.peek(nbytes).splitlines()
else:
# TODO: handle other compressed file types? Tools only seem to support gzip.
fd = os.open(source, os.O_RDONLY)
lines = os.pread(fd, nbytes, 0).splitlines()
return list(map(lambda l: l.decode('ascii', errors='ignore'), lines))
1 change: 1 addition & 0 deletions hammer/vlsi/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ def load_technology(self, cache_dir: str = "") -> None:
tech.set_database(self.database)
tech.cache_dir = cache_dir
tech.extract_technology_files()
tech.get_lib_units()

self.tech = tech

Expand Down
11 changes: 9 additions & 2 deletions hammer/vlsi/hammer_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from .hooks import (HammerStepFunction, HammerToolHookAction, HammerToolStep,
HookLocation, HammerStartStopStep)
from .submit_command import HammerSubmitCommand
from .units import TemperatureValue, TimeValue, VoltageValue
from .units import TemperatureValue, TimeValue, VoltageValue, CapacitanceValue

__all__ = ['HammerTool']

Expand Down Expand Up @@ -1076,8 +1076,15 @@ def get_time_unit(self) -> TimeValue:
"""
Return the library time value.
"""
return TimeValue(get_or_else(self.technology.config.time_unit, "1 ns"))
#TODO: support mixed technologies
return get_or_else(self.technology.time_unit, TimeValue("1 ns"))

def get_cap_unit(self) -> CapacitanceValue:
"""
Return the library capacitance value.
"""
#TODO: support mixed technologies
return get_or_else(self.technology.cap_unit, CapacitanceValue("1 pF"))

def get_all_supplies(self, key: str) -> List[Supply]:
supplies = self.get_setting(key)
Expand Down
Loading

0 comments on commit f06f7ca

Please sign in to comment.