From 33c95a69bf2fec62980e454673bda2f31739a9ea Mon Sep 17 00:00:00 2001 From: Mohamed Gaber Date: Wed, 10 Jan 2024 15:23:44 +0200 Subject: [PATCH] Create Synthesis Exploration Flow * Added new flow, `SynthesisExploration`, that tries all synthesis strategies in parallel, performs STA and reports key area and delay metrics * `Yosys.Synthesis` * Updated error for bad area/delay format to make a bit more sense * Updated internal `stat` calls to Yosys to pass the liberty arguments so the area can be consistently calculated * Removed `SYNTH_STRATEGY` value `AREA 4` -- never existed or worked --- openlane/flows/builtins.py | 1 + openlane/flows/synth_explore.py | 143 ++++++++++++++++++++++++++ openlane/scripts/yosys/synthesize.tcl | 14 ++- openlane/steps/yosys.py | 1 - 4 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 openlane/flows/synth_explore.py diff --git a/openlane/flows/builtins.py b/openlane/flows/builtins.py index f7c8bcc36..cf974ba1c 100644 --- a/openlane/flows/builtins.py +++ b/openlane/flows/builtins.py @@ -15,3 +15,4 @@ from .optimizing import Optimizing from .classic import Classic, VHDLClassic from .misc import OpenInKLayout, OpenInOpenROAD +from .synth_explore import SynthesisExploration diff --git a/openlane/flows/synth_explore.py b/openlane/flows/synth_explore.py new file mode 100644 index 000000000..c7d28d239 --- /dev/null +++ b/openlane/flows/synth_explore.py @@ -0,0 +1,143 @@ +# Copyright 2023 Efabless Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from __future__ import annotations +from decimal import Decimal +import os + +import rich +import rich.table +from typing import Dict, List, Optional, Tuple +from concurrent.futures import Future + +from .flow import Flow +from ..state import State +from ..config import Config +from ..logging import success +from ..logging import options, console +from ..steps import Step, Yosys, OpenROAD, StepError + + +# "Synthesis Exploration" is a non-seqeuential flow that tries all synthesis +# strategies and shows which ones yield the best area XOR delay +@Flow.factory.register() +class SynthesisExploration(Flow): + Steps = [ + Yosys.Synthesis, + OpenROAD.CheckSDCFiles, + OpenROAD.STAPrePNR, + ] + + def run( + self, + initial_state: State, + **kwargs, + ) -> Tuple[State, List[Step]]: + step_list: List[Step] = [] + + self.progress_bar.set_max_stage_count(1) + + synth_futures: List[Tuple[Config, Future[State]]] = [] + self.progress_bar.start_stage("Synthesis Exploration") + + options.set_condensed_mode(True) + + for strategy in [ + "AREA 0", + "AREA 1", + "AREA 2", + "AREA 3", + "DELAY 0", + "DELAY 1", + "DELAY 2", + "DELAY 3", + "DELAY 4", + ]: + config = self.config.copy(SYNTH_STRATEGY=strategy) + + synth_step = Yosys.Synthesis( + config, + id=f"synthesis-{strategy}", + state_in=initial_state, + ) + synth_future = self.start_step_async(synth_step) + step_list.append(synth_step) + + sdc_step = OpenROAD.CheckSDCFiles( + config, + id=f"sdc-{strategy}", + state_in=synth_future, + ) + sdc_future = self.start_step_async(sdc_step) + step_list.append(sdc_step) + + sta_step = OpenROAD.STAPrePNR( + config, + state_in=sdc_future, + id=f"sta-{strategy}", + ) + + step_list.append(sta_step) + sta_future = self.start_step_async(sta_step) + + synth_futures.append((config, sta_future)) + + results: Dict[str, Optional[Tuple[Decimal, Decimal, Decimal, Decimal]]] = {} + for config, future in synth_futures: + strategy = config["SYNTH_STRATEGY"] + results[strategy] = None + try: + state = future.result() + results[strategy] = ( + state.metrics["design__instance__count"], + state.metrics["design__instance__area"], + state.metrics["timing__setup__ws"], + state.metrics["timing__setup__tns"], + ) + except StepError: + pass # None == failure + self.progress_bar.end_stage() + options.set_condensed_mode(False) + + successful_results = {k: v for k, v in results.items() if v is not None} + min_gates = min(map(lambda x: x[0], successful_results.values())) + min_area = min(map(lambda x: x[1], successful_results.values())) + max_slack = max(map(lambda x: x[2], successful_results.values())) + max_tns = max(map(lambda x: x[3], successful_results.values())) + + table = rich.table.Table() + table.add_column("SYNTH_STRATEGY") + table.add_column("Gates") + table.add_column("Area (µm²)") + table.add_column("Setup Slack (ns)") + table.add_column("Total Negative Slack (ns)") + for key, result in results.items(): + gates_s = "[red]Failed" + area_s = "[red]Failed" + slack_s = "[red]Failed" + tns_s = "[red]Failed" + if result is not None: + gates, area, slack, tns = result + gates_s = f"{'[green]' if gates == min_gates else ''}{gates}" + area_s = f"{'[green]' if area == min_area else ''}{area}" + slack_s = f"{'[green]' if slack == max_slack else ''}{slack}" + tns_s = f"{'[green]' if tns == max_tns else ''}{tns}" + table.add_row(key, gates_s, area_s, slack_s, tns_s) + + console.print(table) + assert self.run_dir is not None + with open(os.path.join(self.run_dir, "summary.rpt"), "w") as f: + rich.print(table, file=f) + + success("Flow complete.") + return (initial_state, step_list) diff --git a/openlane/scripts/yosys/synthesize.tcl b/openlane/scripts/yosys/synthesize.tcl index 07be6a413..4cadfe2bf 100644 --- a/openlane/scripts/yosys/synthesize.tcl +++ b/openlane/scripts/yosys/synthesize.tcl @@ -162,7 +162,7 @@ proc synth_strategy_format_err { } { upvar area_scripts area_scripts upvar delay_scripts delay_scripts log -stderr "\[ERROR] Misformatted SYNTH_STRATEGY (\"$::env(SYNTH_STRATEGY)\")." - log -stderr "\[ERROR] Correct format is \"DELAY|AREA 0-[expr [llength $delay_scripts]-1]|0-[expr [llength $area_scripts]-1]\"." + log -stderr "\[ERROR] Correct format is \"DELAY 0-[expr [llength $delay_scripts]-1]|AREA 0-[expr [llength $area_scripts]-1]\"." exit 1 } @@ -240,7 +240,8 @@ if { $::env(SYNTH_ELABORATE_ONLY) } { opt_clean -purge tee -o "$report_dir/chk.rpt" check - tee -o "$report_dir/stat.json" stat -json + tee -o "$report_dir/stat.json" stat -json {*}$lib_args + tee -o "$report_dir/stat.log" stat {*}$lib_args write_verilog -noattr -noexpr -nohex -nodec -defparam "$::env(SAVE_NETLIST)" write_json "$::env(SAVE_NETLIST).json" @@ -338,7 +339,8 @@ if { $adder_type == "FA" } { opt opt_clean -purge -tee -o "$report_dir/pre_techmap.json" stat -json +tee -o "$report_dir/pre_techmap.json" stat -json {*}$lib_args +tee -o "$report_dir/pre_techmap.log" stat {*}$lib_args # Map tri-state buffers if { $tbuf_map } { @@ -362,7 +364,8 @@ if { [info exists ::env(SYNTH_LATCH_MAP)] } { } dfflibmap {*}$dfflib_args -tee -o "$report_dir/post_dff.json" stat -json +tee -o "$report_dir/post_dff.json" stat -json {*}$lib_args +tee -o "$report_dir/post_dff.log" stat {*}$lib_args proc run_strategy {output script strategy_name {postfix_with_strategy 0}} { upvar clock_period clock_period @@ -395,7 +398,8 @@ proc run_strategy {output script strategy_name {postfix_with_strategy 0}} { } tee -o "$report_dir/chk.rpt" check - tee -o "$report_dir/stat.json" stat -json + tee -o "$report_dir/stat.json" stat -json {*}$lib_args + tee -o "$report_dir/stat.log" stat {*}$lib_args if { $::env(SYNTH_AUTONAME) } { # Generate public names for the various nets, resulting in very long names that include diff --git a/openlane/steps/yosys.py b/openlane/steps/yosys.py index b274305be..de24f3582 100644 --- a/openlane/steps/yosys.py +++ b/openlane/steps/yosys.py @@ -244,7 +244,6 @@ class SynthesisCommon(YosysStep): "AREA 1", "AREA 2", "AREA 3", - "AREA 4", "DELAY 0", "DELAY 1", "DELAY 2",