Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clock & Delay constraint refinements #818

Merged
merged 2 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions hammer/config/defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,9 @@ vlsi.inputs:
# period (TimeValue) - Clock port period. e.g. "1 ns", "5ns". Default units: ns
# path (str) - Optional. If specified, this is the RTL path to the clock. Otherwise, the path is the same as the name.
# uncertainty (TimeValue) - Optional. Clock uncertainty. e.g. "1 ns", "5ns". Default units: ns
# generated (bool) - Optional. If specified this clock is generated from another clock, must specify source_path and divisor
# generated (bool) - Optional. If specified this clock is generated from another clock. Must specify source_path and divisor, but period becomes Optional.
# source_path (str) - Required if generated. The path of the clock that this clock is generated from.
# divisor (int) - Required if generated. The amount this generated clock is slowed from the source clock e.g. 2 => this clock will be two times slower than the source clock.
# divisor (int) - Required if generated. The amount this generated clock is slower/faster than the source clock e.g. 2 => this clock will be two times slower than the source clock. Negative values imply inversion (falling edge in line with the source's rising edge).
# We are constrained to integers by SDC.
# group (str) - Optional. The name of the clock group this clock belongs to. Clocks in the same group will not be marked as asynchronous.
# Clocks with no group specified will all be placed in separate groups and thus marked as asynchronous to each other and all other groups.
Expand All @@ -207,6 +207,7 @@ vlsi.inputs:
# - "input"
# - "output"
# delay (TimeValue) - Delay applied to the I/O.
# corner (Optional[str]) - "setup" or "hold" will specify -max and -min flags. If empty, the same constraint will apply for setup and hold analysis.

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.).
Expand Down
4 changes: 3 additions & 1 deletion hammer/synthesis/yosys/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,10 @@ def init_environment(self) -> bool:
clock_port = self.get_clock_ports()[0]
self.clock_port_name = clock_port.name
time_unit = "ps" # yosys requires time units in ps
assert clock_port.period is not None, "clock must have a period"
assert clock_port.uncertainty is not None, "clock must have an uncertainty"
self.clock_period = int(clock_port.period.value_in_units(time_unit))
self.clock_uncertainty = int(clock_port.period.value_in_units(time_unit))
self.clock_uncertainty = int(clock_port.uncertainty.value_in_units(time_unit))
self.clock_transition = 0.15 # SYNTH_CLOCK_TRANSITION

self.synth_cap_load = 33.5 # SYNTH_CAP_LOAD
Expand Down
27 changes: 18 additions & 9 deletions hammer/vlsi/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ def name_bump(self, definition: BumpsDefinition, assignment: BumpAssignment) ->

ClockPort = NamedTuple('ClockPort', [
('name', str),
('period', TimeValue),
('period', Optional[TimeValue]),
('path', Optional[str]),
('uncertainty', Optional[TimeValue]),
('generated', Optional[bool]),
Expand All @@ -388,34 +388,43 @@ class DelayConstraint(NamedTuple('DelayConstraint', [
('name', str),
('clock', str),
('direction', str),
('delay', TimeValue)
('delay', TimeValue),
('corner', Optional[str])
])):
__slots__ = ()

def __new__(cls, name: str, clock: str, direction: str, delay: TimeValue) -> "DelayConstraint":
def __new__(cls, name: str, clock: str, direction: str, delay: TimeValue, corner: Optional[str]) -> "DelayConstraint":
if direction not in ("input", "output"):
raise ValueError("Invalid direction {direction}".format(direction=direction))
return super().__new__(cls, name, clock, direction, delay)
raise ValueError(f"Invalid direction {direction} for a delay constraint")
if corner is not None:
if corner not in ("setup", "hold"):
raise ValueError(f"Invalid corner {corner} for a delay constraint")
return super().__new__(cls, name, clock, direction, delay, corner)

@staticmethod
def from_dict(delay_src: Dict[str, Any]) -> "DelayConstraint":
direction = str(delay_src["direction"])
if direction not in ("input", "output"):
raise ValueError("Invalid direction {direction}".format(direction=direction))
corner = None # type: Optional[str]
if "corner" in delay_src:
corner = str(delay_src["corner"])
return DelayConstraint(
name=str(delay_src["name"]),
clock=str(delay_src["clock"]),
direction=direction,
delay=TimeValue(delay_src["delay"])
delay=TimeValue(delay_src["delay"]),
corner=corner
)

def to_dict(self) -> dict:
return {
output = {
"name": self.name,
"clock": self.clock,
"direction": self.direction,
"delay": self.delay.str_value_in_units("ns", round_zeroes=False)
}
if self.corner is not None:
output.update({"corner": self.corner})
return output

class DecapConstraint(NamedTuple('DecapConstraint', [
('target', str),
Expand Down
11 changes: 10 additions & 1 deletion hammer/vlsi/hammer_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -1051,9 +1051,12 @@ def get_clock_ports(self) -> List[ClockPort]:
output = [] # type: List[ClockPort]
for clock_port in clocks:
clock = ClockPort(
name=clock_port["name"], period=TimeValue(clock_port["period"]),
name=clock_port["name"], period=None,
uncertainty=None, path=None, generated=None, source_path=None, divisor=None, group=None
)
period_assert = False
if "period" in clock_port:
clock = clock._replace(period=TimeValue(clock_port["period"]))
if "path" in clock_port:
clock = clock._replace(path=clock_port["path"])
if "uncertainty" in clock_port:
Expand All @@ -1068,7 +1071,13 @@ def get_clock_ports(self) -> List[ClockPort]:
source_path=clock_port["source_path"],
divisor=int(clock_port["divisor"])
)
else:
period_assert = True
else:
period_assert = True
clock = clock._replace(generated=generated)
if period_assert:
assert clock.period is not None, f"Non-generated clock {clock.name} must have a period specified."
output.append(clock)
return output

Expand Down
11 changes: 8 additions & 3 deletions hammer/vlsi/hammer_vlsi_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -2161,13 +2161,16 @@ def sdc_clock_constraints(self) -> str:
if get_or_else(clock.generated, False):
if any("hport" in p for p in [get_or_else(clock.path, ""), get_or_else(clock.source_path, "")]):
self.logger.error(f"In clock constraints, hports are not supported by some tools. Consider using ports/pins/hpins instead. Offending clock name: ${clock.name}")
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))
assert clock.divisor is not None, f"Generated clock {clock.name} must have a divisor"
output.append("create_generated_clock -name {n} -source {m_path} -divide_by {div} {invert} {path}".
format(n=clock.name, m_path=clock.source_path, div=abs(clock.divisor), invert="-invert" if clock.divisor < 0 else "", 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.")
assert clock.period is not None, f"Clock {clock.name} must have a period"
output.append("create_clock {0} -name {1} -period {2}".format(clock.path, clock.name, clock.period.value_in_units(time_unit)))
else:
assert clock.period is not None, f"Clock {clock.name} must have a period"
output.append("create_clock {0} -name {0} -period {1}".format(clock.name, clock.period.value_in_units(time_unit)))
if clock.uncertainty is not None:
output.append("set_clock_uncertainty {1} [get_clocks {0}]".format(clock.name, clock.uncertainty.value_in_units(time_unit)))
Expand Down Expand Up @@ -2210,10 +2213,12 @@ def sdc_pin_constraints(self) -> str:

# Also specify delays for specific pins.
for delay in self.get_delay_constraints():
output.append("set_{direction}_delay {delay} -clock {clock} [get_port {name}]".format(
minmax = {None: "", "setup": "-max", "hold": "-min"}
output.append("set_{direction}_delay {delay} -clock {clock} {minmax} [get_port {name}]".format(
delay=delay.delay.value_in_units(self.get_time_unit().value_prefix + self.get_time_unit().unit),
clock=delay.clock,
direction=delay.direction,
minmax=minmax[delay.corner],
name=delay.name
))

Expand Down
12 changes: 7 additions & 5 deletions hammer/vlsi/vendor/openroad.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class OpenROADTool(HasSDCSupport, TCLTool, HammerTool):
def env_vars(self) -> Dict[str, str]:
"""
Get the list of environment variables required for this tool.
Note to subclasses: remember to include variables from
Note to subclasses: remember to include variables from
super().env_vars!
"""
list_of_vars = self.get_setting("openroad.extra_env_vars")
Expand All @@ -36,7 +36,7 @@ def env_vars(self) -> Dict[str, str]:
def validate_openroad_installation(self) -> None:
"""
make sure OPENROAD env-var is set, and klayout is in the path (since
klayout is not installed with OPENROAD as of version 1.1.0. this
klayout is not installed with OPENROAD as of version 1.1.0. this
should be called in steps that actually run tools or touch filepaths
"""
if not shutil.which("openroad"):
Expand Down Expand Up @@ -114,7 +114,7 @@ def version_number(self, version: str) -> int:

def setup_openroad_rundir(self) -> bool:
"""
OpenROAD expects several files/dirs in the current run_dir, so we
OpenROAD expects several files/dirs in the current run_dir, so we
symlink them in from the OpenROAD-flow installation
"""
# TODO: for now, just symlink in the read-only OpenROAD stuff, since
Expand Down Expand Up @@ -151,7 +151,9 @@ def _clock_period_value(self) -> str:
"""this string is used in the makefile fragment used by OpenROAD"""

assert len(self.get_clock_ports()) == 1, "openroad only supports 1 root clock"
return str(self.get_clock_ports()[0].period.value_in_units("ns"))
period = self.get_clock_ports()[0].period
assert period is not None, "clock must have a period"
return str(period.value_in_units("ns"))

def _floorplan_bbox(self) -> str:
"""this string is used in the makefile fragment used by OpenROAD"""
Expand Down Expand Up @@ -187,7 +189,7 @@ def create_design_config(self) -> bool:
abspath_input_files = list(map(lambda name:
os.path.join(os.getcwd(), name), self.input_files))

# Add any verilog_synth wrappers (which are needed in some
# Add any verilog_synth wrappers (which are needed in some
# technologies e.g. for SRAMs) which need to be synthesized.
abspath_input_files += self.technology.read_libs([
hammer_tech.filters.verilog_synth_filter
Expand Down
54 changes: 46 additions & 8 deletions tests/test_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ 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=Decimal('0'), global_y_offset=Decimal('0'), cell="bumpcell",assignments=assignments)
assert BumpsPinNamingScheme.A1.name_bump(definition, assignments[0]) == "AAAA8421"

def test_get_by_bump_dim_pitch(self) -> None:
"""
Test the extraction of x, y, pitches.
Expand All @@ -208,7 +208,7 @@ def test_get_by_bump_dim_pitch(self) -> None:
db = hammer_config.HammerDatabase()
db.update_project([{"vlsi.inputs.bumps.pitch_x": 1}, {"vlsi.inputs.bumps.pitch": 2}])
tool.set_database(db)

pitch_set = tool._get_by_bump_dim_pitch()
assert pitch_set == {'x': 1, 'y': 2}

Expand Down Expand Up @@ -243,7 +243,8 @@ def test_round_trip(self) -> None:
name="mypin",
clock="clock",
direction="input",
delay=TimeValue("20 ns")
delay=TimeValue("20 ns"),
corner="setup"
)
copied = DelayConstraint.from_dict(orig.to_dict())
assert orig == copied
Expand All @@ -252,7 +253,8 @@ def test_round_trip(self) -> None:
name="pin_2",
clock="clock_20MHz",
direction="output",
delay=TimeValue("0.3 ns")
delay=TimeValue("0.3 ns"),
corner="hold"
)
copied = DelayConstraint.from_dict(orig.to_dict())
assert orig == copied
Expand All @@ -266,31 +268,35 @@ def test_invalid_direction(self) -> None:
name="mypin",
clock="clock",
direction="bad",
delay=TimeValue("20 ns")
delay=TimeValue("20 ns"),
corner=None
)

with pytest.raises(ValueError):
DelayConstraint(
name="mypin",
clock="clock",
direction="inputt",
delay=TimeValue("20 ns")
delay=TimeValue("20 ns"),
corner=None
)

with pytest.raises(ValueError):
DelayConstraint(
name="mypin",
clock="clock",
direction="inputoutput",
delay=TimeValue("20 ns")
delay=TimeValue("20 ns"),
corner=None
)

with pytest.raises(ValueError):
DelayConstraint(
name="mypin",
clock="clock",
direction="",
delay=TimeValue("20 ns")
delay=TimeValue("20 ns"),
corner=None
)

# Test that the error is raised with the dict as well.
Expand All @@ -302,6 +308,38 @@ def test_invalid_direction(self) -> None:
"delay": "20 ns"
})

def test_invalid_corner(self) -> None:
"""
Test that invalid corners are caught properly.
"""
with pytest.raises(ValueError):
DelayConstraint(
name="mypin",
clock="clock",
direction="input",
delay=TimeValue("20 ns"),
corner="typical"
)

with pytest.raises(ValueError):
DelayConstraint(
name="mypin",
clock="clock",
direction="input",
delay=TimeValue("20 ns"),
corner=""
)

# Test that the error is raised with the dict as well.
with pytest.raises(ValueError):
DelayConstraint.from_dict({
"name": "mypin",
"clock": "clock",
"direction": "input",
"delay": "20 ns",
"corner": "typical"
})


class TestDecapConstraint:
def test_round_trip(self) -> None:
Expand Down