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

P&R timing flow for Innovus/Tempus #858

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
13 changes: 6 additions & 7 deletions hammer/common/cadence/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
class CadenceTool(HasSDCSupport, HasCPFSupport, HasUPFSupport, TCLTool, HammerTool):
"""Mix-in trait with functions useful for Cadence-based tools."""

constraint_mode = "my_constraint_mode"

@property
def env_vars(self) -> Dict[str, str]:
"""
Expand Down Expand Up @@ -127,9 +129,6 @@ def generate_mmmc_script(self) -> str:
def append_mmmc(cmd: str) -> None:
self.verbose_tcl_append(cmd, mmmc_output)

# Create an Innovus constraint mode.
constraint_mode = "my_constraint_mode"

sdc_files = self.generate_sdc_files()

# Append any custom SDC files.
Expand All @@ -150,13 +149,13 @@ def append_mmmc(cmd: str) -> None:
self.run_executable(["touch", blank_sdc])
sdc_files_arg = "-sdc_files {{ {} }}".format(blank_sdc)
if self.hierarchical_mode.is_nonleaf_hierarchical():
ilm_sdcs = reduce_list_str(add_lists, list(map(lambda ilm: ilm.sdcs, self.get_input_ilms()))) # type: List[str]
ilm_sdcs = reduce_list_str(add_lists, list(map(lambda ilm: ilm.sdcs, self.get_input_ilms())), []) # type: List[str]
ilm_sdc_files_arg = "-ilm_sdc_files [list {sdc_files}]".format(
sdc_files=" ".join(ilm_sdcs))
else:
ilm_sdc_files_arg = ""
append_mmmc("create_constraint_mode -name {name} {sdc_files_arg} {ilm_sdc_files_arg}".format(
name=constraint_mode,
name=self.constraint_mode,
sdc_files_arg=sdc_files_arg,
ilm_sdc_files_arg=ilm_sdc_files_arg
))
Expand Down Expand Up @@ -205,7 +204,7 @@ def append_mmmc(cmd: str) -> None:
# Next, create the analysis views
append_mmmc("create_analysis_view -name {name}_view -delay_corner {name}_delay -constraint_mode {constraint}".format(
name=corner_name,
constraint=constraint_mode
constraint=self.constraint_mode
))

# Finally, apply the analysis view.
Expand Down Expand Up @@ -251,7 +250,7 @@ def append_mmmc(cmd: str) -> None:
# Next, create an Innovus analysis view.
analysis_view_name = "my_view"
append_mmmc("create_analysis_view -name {name} -delay_corner {corner} -constraint_mode {constraint}".format(
name=analysis_view_name, corner=delay_corner_name, constraint=constraint_mode))
name=analysis_view_name, corner=delay_corner_name, constraint=self.constraint_mode))
# Finally, apply the analysis view.
# TODO: introduce different views of setup/hold and true multi-corner
append_mmmc("set_analysis_view -setup {{ {setup_view} }} -hold {{ {hold_view} }}".format(
Expand Down
5 changes: 4 additions & 1 deletion hammer/generate_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ def main(args) -> int:
"(optional) output ILM information for hierarchical mode"),
InterfaceVar("output_gds", "str", "path to the output GDS file"),
InterfaceVar("output_netlist", "str", "path to the output netlist file"),
InterfaceVar("output_physical_netlist", "Optional[str]", "(optional) path to the output physical netlist file"),
InterfaceVar("output_sim_netlist", "str", "path to the output simulation netlist file"),
InterfaceVar("hcells_list", "List[str]",
"list of cells to explicitly map hierarchically in LVS"),
Expand Down Expand Up @@ -249,7 +250,9 @@ def main(args) -> int:
InterfaceVar("spefs", "Optional[List]",
"(optional) list of SPEF files"),
InterfaceVar("sdf_file", "Optional[str]",
"(optional) input SDF file")
"(optional) input SDF file"),
InterfaceVar("def_file", "Optional[str]",
"(optional) input DEF file")
],
outputs=[]
)
Expand Down
30 changes: 27 additions & 3 deletions hammer/par/innovus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def export_config_outputs(self) -> Dict[str, Any]:
outputs["par.outputs.seq_cells"] = self.output_seq_cells
outputs["par.outputs.all_regs"] = self.output_all_regs
outputs["par.outputs.sdf_file"] = self.output_sdf_path
outputs["par.outputs.def_file"] = self.output_def_path
outputs["par.outputs.spefs"] = self.output_spef_paths
return outputs

Expand Down Expand Up @@ -114,6 +115,10 @@ def output_gds_filename(self) -> str:
def output_netlist_filename(self) -> str:
return os.path.join(self.run_dir, "{top}.lvs.v".format(top=self.top_module))

@property
def output_physical_netlist_filename(self) -> str:
return os.path.join(self.run_dir, "{top}.physical.v".format(top=self.top_module))

@property
def output_sim_netlist_filename(self) -> str:
return os.path.join(self.run_dir, "{top}.sim.v".format(top=self.top_module))
Expand All @@ -130,6 +135,10 @@ def all_cells_path(self) -> str:
def output_sdf_path(self) -> str:
return os.path.join(self.run_dir, "{top}.par.sdf".format(top=self.top_module))

@property
def output_def_path(self) -> str:
return os.path.join(self.run_dir, "{top}.def".format(top=self.top_module))

@property
def output_spef_paths(self) -> List[str]:
corners = self.get_mmmc_corners()
Expand Down Expand Up @@ -554,8 +563,8 @@ def place_opt_design(self) -> bool:
if self.hierarchical_mode.is_nonleaf_hierarchical():
self.verbose_append('''
flatten_ilm
update_constraint_mode -name my_constraint_mode -ilm_sdc_files {sdc}
'''.format(sdc=self.post_synth_sdc), clean=True)
update_constraint_mode -name {name} -ilm_sdc_files {sdc}
'''.format(name=self.constraint_mode, sdc=self.post_synth_sdc), clean=True)

# Use place_opt_design V2 (POD-Turbo). Option must be explicitly set only in 22.1.
if self.version() >= self.version_number("221") and self.version() < self.version_number("231"):
Expand Down Expand Up @@ -771,13 +780,20 @@ def write_netlist(self) -> bool:
if pwr_gnd_net.tie is not None:
self.verbose_append("connect_global_net {tie} -type net -net_base_name {net}".format(tie=pwr_gnd_net.tie, net=pwr_gnd_net.name))

# Output the Verilog netlist for the design and include physical cells (-phys) like decaps and fill
# Output a flattened Verilog netlist for the design and include physical cells (-phys) like decaps and fill
self.verbose_append("write_netlist {netlist} -top_module_first -top_module {top} -exclude_leaf_cells -phys -flat -exclude_insts_of_cells {{ {pcells} }} ".format(
netlist=self.output_netlist_filename,
top=self.top_module,
pcells=" ".join(self.get_physical_only_cells())
))

# Output a non-flattened Verilog netlist with physical instances
self.verbose_append("write_netlist {netlist} -top_module_first -top_module {top} -exclude_leaf_cells -phys -exclude_insts_of_cells {{ {pcells} }} ".format(
netlist=self.output_physical_netlist_filename,
top=self.top_module,
pcells=" ".join(self.get_physical_only_cells())
))

self.verbose_append("write_netlist {netlist} -top_module_first -top_module {top} -exclude_leaf_cells -exclude_insts_of_cells {{ {pcells} }} ".format(
netlist=self.output_sim_netlist_filename,
top=self.top_module,
Expand Down Expand Up @@ -848,6 +864,11 @@ def write_gds(self) -> bool:
))
return True

def write_def(self) -> bool:
self.verbose_append("write_def {def_file} -floorplan -netlist -routing".format(def_file=self.output_def_path))

return True

def write_sdf(self) -> bool:
if self.hierarchical_mode.is_nonleaf_hierarchical():
self.verbose_append("flatten_ilm")
Expand Down Expand Up @@ -936,6 +957,9 @@ def write_design(self) -> bool:
# Write netlist
self.write_netlist()

# Write DEF
self.write_def()

# GDS streamout.
self.write_gds()

Expand Down
19 changes: 12 additions & 7 deletions hammer/timing/tempus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,14 @@ def init_design(self) -> bool:
mmmc_path = os.path.join(self.run_dir, "mmmc.tcl")
self.write_contents_to_path(self.generate_mmmc_script(), mmmc_path)
verbose_append("read_mmmc {mmmc_path}".format(mmmc_path=mmmc_path))
verbose_append("update_constraint_mode -name {name} -ilm_sdc_files {post_synth_sdc}".format(name=self.constraint_mode, post_synth_sdc=self.post_synth_sdc))

# Read physical LEFs (optional in Tempus)
lef_files = self.technology.read_libs([
hammer_tech.filters.lef_filter
], hammer_tech.HammerTechnologyUtils.to_plain_item)
if self.hierarchical_mode.is_nonleaf_hierarchical():
ilm_lefs = list(map(lambda ilm: ilm.lef, self.get_input_ilms()))
ilm_lefs = list(map(lambda ilm: ilm.lef, self.get_input_ilms(full_tree=True)))
lef_files.extend(ilm_lefs)
verbose_append("read_physical -lef {{ {files} }}".format(
files=" ".join(lef_files)
Expand All @@ -127,17 +128,22 @@ def init_design(self) -> bool:

if self.hierarchical_mode.is_nonleaf_hierarchical():
# Read ILMs.
for ilm in self.get_input_ilms():
for ilm in self.get_input_ilms(full_tree=True):
# Assumes that the ILM was created by Innovus (or at least the file/folder structure).
# TODO: support non-Innovus hierarchical (read netlists, etc.)
verbose_append("read_ilm -cell {module} -directory {dir}".format(dir=ilm.dir, module=ilm.module))
verbose_append("set_ilm -cell {module} -in_dir {dir}".format(dir=ilm.dir, module=ilm.module))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it now set_ilm followed by read_ilm below? Just curious.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why Cadence changed it, but in 23.1, read_ilm can only be called once; it reads all ILMs declared by set_ilm.

Copy link
Contributor

@harrisonliew harrisonliew Apr 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Can you do a version check on this change then (i.e. keep the existing code for <23.1)


# Read power intent
if self.get_setting("vlsi.inputs.power_spec_mode") != "empty":
# Setup power settings from cpf/upf
for l in self.generate_power_spec_commands():
verbose_append(l)

verbose_append("init_design")

if self.def_file is not None:
verbose_append("read_def " + os.path.join(os.getcwd(), self.def_file))

# Read parasitics
if self.spefs is not None: # post-P&R
corners = self.get_mmmc_corners()
Expand Down Expand Up @@ -166,10 +172,9 @@ def init_design(self) -> bool:
if self.sdf_file is not None:
verbose_append("read_sdf " + os.path.join(os.getcwd(), self.sdf_file))

verbose_append("init_design")

# TODO: Optionally read additional DEF or OA physical data

if self.hierarchical_mode.is_nonleaf_hierarchical() and len(self.get_input_ilms(full_tree=True)):
verbose_append("read_ilm")
verbose_append("flatten_ilm")

# Set some default analysis settings for max accuracy
# Clock path pessimism removal
Expand Down
2 changes: 1 addition & 1 deletion hammer/timing/tempus/defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ timing.tempus:

# Tempus version to use
# Used to locate the binary - e.g. the '211_ISR3' in ${cadence.cadence_home}/SSV/SSV211_ISR3/bin/tempus
version: "211_ISR3"
version: "231"

# Enable signal integrity delay and glitch analysis
# Note: your tech libs should have noise models!
Expand Down
8 changes: 6 additions & 2 deletions hammer/vlsi/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,9 @@ def set_up_timing_tool(self, timing_tool: HammerTimingTool,
missing_inputs = True
if missing_inputs:
return False
timing_tool.post_synth_sdc = self.database.get_setting("timing.inputs.post_synth_sdc")
timing_tool.spefs = self.database.get_setting("timing.inputs.spefs")
timing_tool.def_file = self.database.get_setting("timing.inputs.def_file")

self.timing_tool = timing_tool

Expand Down Expand Up @@ -1165,13 +1168,14 @@ def par_output_to_timing_input(output_dict: dict) -> Optional[dict]:
or None if output_dict was invalid
"""
try:
input_files = deeplist([output_dict["par.outputs.output_netlist"]])
input_files = deeplist([output_dict.get("par.outputs.output_physical_netlist", output_dict["par.outputs.output_netlist"])])
result = {
"timing.inputs.input_files": input_files,
"timing.inputs.input_files_meta": "append",
"timing.inputs.top_module": output_dict["par.inputs.top_module"],
"timing.inputs.post_synth_sdc": output_dict["par.inputs.post_synth_sdc"],
"timing.inputs.spefs": output_dict["par.outputs.spefs"],
"timing.inputs.sdf_file": output_dict["par.outputs.sdf_file"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the case for post-P&R timing for non-hierarchical flows? Wouldn't you need to input an SDC file that's not the post_synth_sdc?

"timing.inputs.def_file": output_dict.get("par.outputs.def_file", None),
"vlsi.builtins.is_complete": False
} # type: Dict[str, Any]
return result
Expand Down
41 changes: 41 additions & 0 deletions hammer/vlsi/hammer_vlsi_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ def export_config_outputs(self) -> Dict[str, Any]:
outputs["vlsi.inputs.ilms_meta"] = "append" # to coalesce ILMs for entire hierarchical tree
outputs["par.outputs.output_gds"] = str(self.output_gds)
outputs["par.outputs.output_netlist"] = str(self.output_netlist)
outputs["par.outputs.output_physical_netlist"] = str(self.output_physical_netlist)
outputs["par.outputs.output_sim_netlist"] = str(self.output_sim_netlist)
outputs["par.outputs.hcells_list"] = list(self.hcells_list)
outputs["par.outputs.seq_cells"] = self.output_seq_cells
Expand Down Expand Up @@ -511,6 +512,26 @@ def output_netlist(self, value: str) -> None:
self.attr_setter("_output_netlist", value)


@property
def output_physical_netlist(self) -> Optional[str]:
"""
Get the (optional) path to the output physical netlist file.

:return: The (optional) path to the output physical netlist file.
"""
try:
return self.attr_getter("_output_physical_netlist", None)
except AttributeError:
return None

@output_physical_netlist.setter
def output_physical_netlist(self, value: Optional[str]) -> None:
"""Set the (optional) path to the output physical netlist file."""
if not (isinstance(value, str) or (value is None)):
raise TypeError("output_physical_netlist must be a Optional[str]")
self.attr_setter("_output_physical_netlist", value)


@property
def output_sim_netlist(self) -> str:
"""
Expand Down Expand Up @@ -2085,6 +2106,26 @@ def sdf_file(self, value: Optional[str]) -> None:
self.attr_setter("_sdf_file", value)


@property
def def_file(self) -> Optional[str]:
"""
Get the (optional) input DEF file.

:return: The (optional) input DEF file.
"""
try:
return self.attr_getter("_def_file", None)
except AttributeError:
return None

@def_file.setter
def def_file(self, value: Optional[str]) -> None:
"""Set the (optional) input DEF file."""
if not (isinstance(value, str) or (value is None)):
raise TypeError("def_file must be a Optional[str]")
self.attr_setter("_def_file", value)


### Outputs ###
### END Generated interface HammerTimingTool ###

Expand Down
Loading