Skip to content

Commit

Permalink
units overhaul - get time & cap units from the tech library
Browse files Browse the repository at this point in the history
  • Loading branch information
harrisonliew committed Sep 30, 2023
1 parent 81a00ff commit 788ebb9
Show file tree
Hide file tree
Showing 17 changed files with 122 additions and 32 deletions.
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
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
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 @@ -1262,19 +1288,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 @@ -1286,7 +1312,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
13 changes: 8 additions & 5 deletions hammer/vlsi/hammer_vlsi_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -2138,14 +2138,17 @@ def sdc_clock_constraints(self) -> str:

clocks = self.get_clock_ports()
time_unit = self.get_time_unit().value_prefix + self.get_time_unit().unit
output.append(f"set_units -time {time_unit}")

for clock in clocks:
# TODO: FIXME This assumes that library units are always in ns!!!
# hports causes some tools to crash
if get_or_else(clock.generated, False):
if any("get_db hports" in p for p in [get_or_else(clock.path, ""), get_or_else(clock.source_path, "")]):
self.logger.error("get_db hports will cause some tools to crash. Consider querying hpins instead.")
output.append("create_generated_clock -name {n} -source {m_path} -divide_by {div} {path}".
format(n=clock.name, m_path=clock.source_path, div=clock.divisor, path=clock.path))
elif clock.path is not None:
if "get_db hports" in clock.path:
self.logger.error("get_db hports will cause some tools to crash. Consider querying hpins instead.")
output.append("create_clock {0} -name {1} -period {2}".format(clock.path, clock.name, clock.period.value_in_units(time_unit)))
else:
output.append("create_clock {0} -name {0} -period {1}".format(clock.name, clock.period.value_in_units(time_unit)))
Expand All @@ -2172,9 +2175,9 @@ def sdc_pin_constraints(self) -> str:
"""Generate a fragment for I/O pin constraints."""
output = [] # type: List[str]

output.append("set_units -capacitance fF")
cap_unit = self.get_cap_unit().value_prefix + self.get_cap_unit().unit

default_output_load = CapacitanceValue(self.get_setting("vlsi.inputs.default_output_load")).value_in_units("fF", round_zeroes = True)
default_output_load = CapacitanceValue(self.get_setting("vlsi.inputs.default_output_load")).value_in_units(cap_unit)

# Specify default load.
output.append("set_load {load} [all_outputs]".format(
Expand All @@ -2184,7 +2187,7 @@ def sdc_pin_constraints(self) -> str:
# Also specify loads for specific pins.
for load in self.get_output_load_constraints():
output.append("set_load {load} [get_port {name}]".format(
load=load.load.value_in_units("fF", round_zeroes = True),
load=load.load.value_in_units(cap_unit),
name=load.name
))

Expand Down
6 changes: 3 additions & 3 deletions hammer/vlsi/submit_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def submit(self, args: List[str], env: Dict[str, str],
so = proc.stdout
assert so is not None
while True:
line = so.readline().decode("utf-8")
line = so.readline().decode("utf-8", errors="ignore")
if line != '':
subprocess_logger.debug(line.rstrip())
output_buf += line
Expand Down Expand Up @@ -252,7 +252,7 @@ def submit(self, args: List[str], env: Dict[str, str],
so = proc.stdout
assert so is not None
while True:
line = so.readline().decode("utf-8")
line = so.readline().decode("utf-8", errors="ignore")
if line != '':
subprocess_logger.debug(line.rstrip())
output_buf += line
Expand Down Expand Up @@ -351,7 +351,7 @@ def submit(self, args: List[str], env: Dict[str, str],
so = proc.stdout
assert so is not None
while True:
line = so.readline().decode("utf-8")
line = so.readline().decode("utf-8", errors="ignore")
if line != '':
subprocess_logger.debug(line.rstrip())
output_buf += line
Expand Down
2 changes: 2 additions & 0 deletions tests/test_sdc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Optional

from hammer import vlsi as hammer_vlsi, config as hammer_config
from hammer import technology as hammer_techs
from utils.tool import DummyTool


Expand All @@ -25,6 +26,7 @@ def test_custom_sdc_constraints(self):
}

tool = SDCDummyTool()
tool.technology = hammer_techs.nop.NopTechnology()
database = hammer_config.HammerDatabase()
hammer_vlsi.HammerVLSISettings.load_builtins_and_core(database)
database.update_project([inputs])
Expand Down
1 change: 0 additions & 1 deletion tests/utils/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ def write_tech_json(
tech_json = {
"name": tech_name,
"grid_unit": "0.001",
"time_unit": "1 ns",
"installs": [],
"libraries": [
{"milkyway_techfile": "soy"},
Expand Down

0 comments on commit 788ebb9

Please sign in to comment.