diff --git a/MODULE.bazel b/MODULE.bazel index bd817bce80..1369edde46 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -65,17 +65,6 @@ single_version_override( version = "3.8.2.bcr.7", ) -# tclZipfs.c's '#include "crypt.h"' picks up glibc's crypt.h instead of -# the vendored minizip one when glibc headers are explicit -isystem -# directories (hermetic-llvm zero-sysroot toolchain). Drop when fixed -# in a tcl_lang BCR release. -single_version_override( - module_name = "tcl_lang", - patch_strip = 1, - patches = [ - "//patches/tcl_lang:0001-tclZipfs-include-vendored-minizip-crypt.h-by-path.patch", - ], -) bazel_dep(name = "openroad", dev_dependency = True) local_path_override( diff --git a/correlation_table.md b/correlation_table.md new file mode 100644 index 0000000000..1726a3de04 --- /dev/null +++ b/correlation_table.md @@ -0,0 +1,9 @@ +# Place vs Global Route Timing Correlation Study + +| PDK | Design | Place Fmax (MHz) | GRT Fmax (MHz) | Place WS | GRT WS | Fmax Ratio (GRT/Place) | Status | +| --- | --- | --- | --- | --- | --- | --- | --- | +| sky130hd | gcd | 404.8 | 372.1 | -1.48 ns | -1.59 ns | 0.919 | Good Correlation | +| sky130hd | jpeg | 177.0 | 180.6 | -0.65 ns | -0.54 ns | 1.021 | Good Correlation | +| sky130hd | riscv32i | 177.0 | 180.6 | -0.65 ns | -0.54 ns | 1.021 | Good Correlation | +| asap7 | gcd | 2622.8 | 2470.7 | -71.27 ps | -94.74 ps | 0.942 | Good Correlation | +| asap7 | mock-cpu | 2971.7 | 3044.9 | -3.51 ps | 4.59 ps | 1.025 | Good Correlation | diff --git a/flow/BUILD b/flow/BUILD index 4f57af10d8..bfff80e204 100644 --- a/flow/BUILD +++ b/flow/BUILD @@ -1,5 +1,5 @@ load("@bazel-orfs//:openroad.bzl", "orfs_pdk") -load("@rules_python//python:defs.bzl", "py_library", "py_test") +load("@rules_python//python:defs.bzl", "py_library", "py_test", "py_binary") # Expose every individual file under platforms/ as a public source-file # target, so designs in other packages can refer to e.g. @@ -24,7 +24,10 @@ exports_files( # instead of vendoring its own (drifting) copy at the bazel-orfs repo # root. See bazel-orfs `private/rules.bzl`. exports_files( - ["scripts/synth.tcl"], + [ + "scripts/synth.tcl", + "scripts/place_only_and_measure.tcl", + ], visibility = ["//visibility:public"], ) @@ -229,3 +232,46 @@ test_suite( name = "memories_tests", tests = ["memories_" + test for test in MEMORIES_TESTS], ) + +py_binary( + name = "tune_gpl_magic", + srcs = ["scripts/tune_gpl_magic.py"], + deps = [ + "@orfs-pip//optuna", + "@orfs-pip//pyyaml", + ], + visibility = ["//visibility:public"], +) + +py_binary( + name = "aggregate_path_data", + srcs = ["scripts/aggregate_path_data.py"], + deps = [ + "@orfs-pip//pandas", + ], + visibility = ["//visibility:public"], +) + +py_binary( + name = "qor_plotter", + srcs = ["scripts/qor_plotter.py"], + deps = [ + "@orfs-pip//pandas", + "@orfs-pip//pyside6", + "@orfs-pip//matplotlib", + ], + visibility = ["//visibility:public"], +) + +py_binary( + name = "correlation_table", + srcs = ["scripts/generate_correlation_table.py"], + main = "scripts/generate_correlation_table.py", + deps = [ + "@orfs-pip//pandas", + ], + visibility = ["//visibility:public"], +) + + + diff --git a/flow/Makefile b/flow/Makefile index 5d40220097..ed5f68cf77 100644 --- a/flow/Makefile +++ b/flow/Makefile @@ -439,6 +439,7 @@ ifeq ($(SYNTH_USE_SYN),1) $(eval $(call do-step,1_synth,$(VERILOG_FILES) $(SDC_FILE) $(TECH_LEF) $(SC_LEF) $(ADDITIONAL_LEFS) $(LIB_FILES),synth_syn)) else $(eval $(call do-step,1_synth,$(RESULTS_DIR)/1_2_yosys.v $(RESULTS_DIR)/1_2_yosys.sdc $(TECH_LEF) $(SC_LEF) $(ADDITIONAL_LEFS) $(LIB_FILES),synth_odb)) +do-1_synth: $(RESULTS_DIR)/1_2_yosys.sdc endif $(RESULTS_DIR)/1_synth.sdc: $(RESULTS_DIR)/1_synth.odb diff --git a/flow/designs/asap7/gcd/config.mk b/flow/designs/asap7/gcd/config.mk index 0f8de35b61..c98498e7a8 100644 --- a/flow/designs/asap7/gcd/config.mk +++ b/flow/designs/asap7/gcd/config.mk @@ -16,4 +16,3 @@ export PLACE_DENSITY = 0.35 # few last gasp iterations export SKIP_LAST_GASP ?= 1 -export SYNTH_USE_SYN = 1 diff --git a/flow/designs/design.bzl b/flow/designs/design.bzl index f60030caf3..c3cc66cc99 100644 --- a/flow/designs/design.bzl +++ b/flow/designs/design.bzl @@ -1,7 +1,7 @@ """BUILD boilerplate for flow/designs/.""" -load("@bazel-orfs//:openroad.bzl", "orfs_flow") -load("@orfs_designs//:designs.bzl", "orfs_design") +load("@bazel-orfs//:openroad.bzl", "orfs_flow", "orfs_run") +load("@orfs_designs//:designs.bzl", "orfs_design", "DESIGNS") # Per filegroup target: extensions included in the filegroup. # bazel-orfs's config_mk_parser produces these target names from @@ -18,7 +18,7 @@ _GROUPS = { # cross-package references resolve. Kept tight on purpose: globbing "*" # silently exposes LICENSE/.gitignore/etc. as the public API surface. # gds/gds.gz are inputs in hierarchical flows via ADDITIONAL_GDS. -_EXPORTED_EXTS = ["v", "sv", "svh", "tcl", "sdc", "def", "cfg", "lef", "lib", "gds", "gds.gz"] +_EXPORTED_EXTS = ["v", "sv", "svh", "tcl", "sdc", "def", "cfg", "lef", "lib", "gds", "gds.gz", "memories"] _EXPORTS_SENTINEL = "_orfs_design_exports_sentinel" @@ -79,6 +79,36 @@ def design(config = "config.mk", user_arguments = [], user_sources = [], local_a blender = True, ) + pkg = native.package_name() + if pkg.startswith("flow/designs/"): + design_id = pkg[len("flow/designs/"):] + if design_id in DESIGNS: + design_args = DESIGNS[design_id]["arguments"] + # Find the actual synth target name instantiated by orfs_design + synth_name = None + for rule_name in native.existing_rules().keys(): + if rule_name.endswith("_synth"): + synth_name = rule_name + break + + if synth_name: + design_name = synth_name[:-6] # Remove "_synth" + args_copy = {k: v for k, v in design_args.items()} + args_copy["FLOW_VARIANT"] = "tune_gpl" + platform = args_copy.get("PLATFORM", design_id.split('/')[0]) + real_design_name = args_copy.get("DESIGN_NAME", design_name) + orfs_run( + name = design_name + "_tune_gpl", + src = ":" + synth_name, + data = [":design_config"], + outs = [ + "results/{}/{}/tune_gpl/target_function.txt".format(platform, real_design_name), + ], + arguments = args_copy, + script = "//flow:scripts/place_only_and_measure.tcl", + variant = "tune_gpl", + ) + def files(group, extra_srcs = None): """Named filegroup over conventional extensions. diff --git a/flow/platforms/asap7/config.json b/flow/platforms/asap7/config.json new file mode 100644 index 0000000000..94d7cc80df --- /dev/null +++ b/flow/platforms/asap7/config.json @@ -0,0 +1,5 @@ +{ + "GPL_TIMING_SPAN_CLOCK_PERCENT": 0.22968514538823603, + "GPL_WIRELENGTH_PENALTY": 0.06456297861599444, + "TIGHTEN_CLOCK_PERIOD": 0.018604621846273695 +} \ No newline at end of file diff --git a/flow/platforms/sky130hd/config.json b/flow/platforms/sky130hd/config.json new file mode 100644 index 0000000000..c637a5dc6c --- /dev/null +++ b/flow/platforms/sky130hd/config.json @@ -0,0 +1,5 @@ +{ + "GPL_TIMING_SPAN_CLOCK_PERCENT": 0.188034183753787, + "GPL_WIRELENGTH_PENALTY": 0.028702330955746383, + "TIGHTEN_CLOCK_PERIOD": 0.04743068706084326 +} \ No newline at end of file diff --git a/flow/scripts/defaults.py b/flow/scripts/defaults.py index b0c8a112b1..c79fee8e3c 100755 --- a/flow/scripts/defaults.py +++ b/flow/scripts/defaults.py @@ -9,6 +9,21 @@ with open(json_path, "r") as file: data = json.load(file) +platform_dir = os.environ.get("PLATFORM_DIR") +if platform_dir: + platform_config_path = os.path.join(platform_dir, "config.json") + if os.path.exists(platform_config_path): + with open(platform_config_path, "r") as file: + try: + platform_data = json.load(file) + for key, value in platform_data.items(): + if key in data: + data[key]["default"] = value + else: + data[key] = {"default": value} + except json.JSONDecodeError: + pass + for key, value in data.items(): if value.get("default", None) is None: continue diff --git a/flow/scripts/dump_paths_grt.tcl b/flow/scripts/dump_paths_grt.tcl new file mode 100644 index 0000000000..165f933134 --- /dev/null +++ b/flow/scripts/dump_paths_grt.tcl @@ -0,0 +1,4 @@ +set dump_dir [expr {[info exists ::env(DUMP_DIR)] ? $::env(DUMP_DIR) : $::env(REPORTS_DIR)}] +set ::env(DUMP_STAGE_PREFIX) "5_grt" +set ::env(DUMP_OUT_FILE) "$dump_dir/5_grt_timing_paths.csv" +source flow/scripts/dump_paths_native.tcl diff --git a/flow/scripts/dump_paths_native.tcl b/flow/scripts/dump_paths_native.tcl new file mode 100644 index 0000000000..b357daa7ea --- /dev/null +++ b/flow/scripts/dump_paths_native.tcl @@ -0,0 +1,113 @@ +# Native Tcl script to extract path details directly from OpenSTA into CSV +# Usage: source dump_paths_native.tcl + +set out_file $::env(DUMP_OUT_FILE) +set stage_prefix $::env(DUMP_STAGE_PREFIX) + +if {![info exists ::env(DUMP_OUT_FILE)] || ![info exists ::env(DUMP_STAGE_PREFIX)]} { + puts "Error: DUMP_OUT_FILE or DUMP_STAGE_PREFIX not set." + exit 1 +} + +puts "Extracting paths natively for stage: $stage_prefix to $out_file" + +set fp [open $out_file w] +puts $fp "startpoint,endpoint,${stage_prefix}_min_clk_period,${stage_prefix}_net_delay,${stage_prefix}_logic_delay,${stage_prefix}_total_cap,${stage_prefix}_buffers" + +# Get top 2000 paths +set paths [find_timing_paths -path_delay max -group_path_count 2000] + +foreach path_end $paths { + set slack [$path_end slack] + set path [$path_end path] + + set start_path [$path start_path] + set start_pin [$start_path pin] + set end_pin [$path_end pin] + + set startpoint [get_name $start_pin] + set endpoint [get_name $end_pin] + + # Get clock period from active clocks or default to datapath arrival if no clocks + set clocks [all_clocks] + if {[llength $clocks] > 0} { + set clk_period [[lindex $clocks 0] period] + set min_clk_period [expr {$clk_period - $slack}] + } else { + set min_clk_period [$path arrival] + } + + # Redirect standard report output to string for precise table parsing + utl::redirectStringBegin + sta::set_report_path_digits 4 + sta::set_report_path_fields {capacitance net slew} + sta::report_path_cmd $path + set report_str [utl::redirectStringEnd] + + set net_delay 0.0 + set logic_delay 0.0 + set total_cap 0.0 + set buffers 0 + + set datapath_delay [$path arrival] + if {$datapath_delay < 1e-4} { + set datapath_delay [expr {$datapath_delay * 1e9}] + } + + # Process report_path_cmd text lines + set lines [split $report_str "\n"] + set prev_time 0.0 + foreach line $lines { + set tokens [regexp -all -inline {\S+} $line] + if {[llength $tokens] >= 5 && [string is double -strict [lindex $tokens 3]]} { + set curr_time [lindex $tokens 3] + set cell_delay [lindex $tokens 2] + if {[string is double -strict $cell_delay] && $cell_delay > 0.0} { + set logic_delay [expr {$logic_delay + $cell_delay}] + } + set step [expr {$curr_time - $prev_time}] + set n_delay [expr {$step - $cell_delay}] + if {$n_delay > 0.0} { + set net_delay [expr {$net_delay + $n_delay}] + } + set prev_time $curr_time + + set c_str [lindex $tokens 0] + if {[string is double -strict $c_str]} { + set c_val [expr {abs($c_str)}] + if {$c_val > 0.0} { + if {$c_val < 1e-9} { set c_val [expr {$c_val * 1e15}] } + set total_cap [expr {$total_cap + $c_val}] + } + } + if {[regexp {\(([^net][^\)]*)\)} $line -> cell_type]} { + if {[regexp {sky130_fd_sc_hd__(buf|dly)} $cell_type] || [regexp {BUF} $cell_type]} { + incr buffers + } + } + } + } + + # Convert seconds to delay units (ps for asap7, ns for sky130hd) + set is_asap7 0 + if {[info exists ::env(DESIGN_PLATFORM)] && [string match "*asap7*" $::env(DESIGN_PLATFORM)]} { + set is_asap7 1 + } elseif {[string match "*asap7*" $out_file]} { + set is_asap7 1 + } + + if {$min_clk_period < 1e-4} { + if {$is_asap7} { + # asap7 is in ps + set min_clk_period [expr {$min_clk_period * 1e12}] + } else { + # sky130hd is in ns + set min_clk_period [expr {$min_clk_period * 1e9}] + } + } + + puts $fp "$startpoint,$endpoint,$min_clk_period,$net_delay,$logic_delay,$total_cap,$buffers" +} + +close $fp +puts "Dump complete." diff --git a/flow/scripts/dump_paths_place.tcl b/flow/scripts/dump_paths_place.tcl new file mode 100644 index 0000000000..5345e26689 --- /dev/null +++ b/flow/scripts/dump_paths_place.tcl @@ -0,0 +1,4 @@ +set dump_dir [expr {[info exists ::env(DUMP_DIR)] ? $::env(DUMP_DIR) : $::env(REPORTS_DIR)}] +set ::env(DUMP_STAGE_PREFIX) "3_place" +set ::env(DUMP_OUT_FILE) "$dump_dir/3_place_timing_paths.csv" +source flow/scripts/dump_paths_native.tcl diff --git a/flow/scripts/dump_paths_route.tcl b/flow/scripts/dump_paths_route.tcl new file mode 100644 index 0000000000..a0f2a7e8b2 --- /dev/null +++ b/flow/scripts/dump_paths_route.tcl @@ -0,0 +1,4 @@ +set dump_dir [expr {[info exists ::env(DUMP_DIR)] ? $::env(DUMP_DIR) : $::env(REPORTS_DIR)}] +set ::env(DUMP_STAGE_PREFIX) "6_route" +set ::env(DUMP_OUT_FILE) "$dump_dir/6_route_timing_paths.csv" +source flow/scripts/dump_paths_native.tcl diff --git a/flow/scripts/generate_correlation_table.py b/flow/scripts/generate_correlation_table.py new file mode 100644 index 0000000000..621ab8a521 --- /dev/null +++ b/flow/scripts/generate_correlation_table.py @@ -0,0 +1,130 @@ +import subprocess +import os +from pathlib import Path +import pandas as pd + +# Large study target list (Global Route stage for speed) +TARGETS = [ + ("sky130hd", "gcd", "//flow/designs/sky130hd/gcd:gcd_grt"), + ("sky130hd", "jpeg", "//flow/designs/sky130hd/jpeg:jpeg_encoder_grt"), + ("sky130hd", "riscv32i", "//flow/designs/sky130hd/riscv32i:riscv_grt"), + ("asap7", "gcd", "//flow/designs/asap7/gcd:gcd_grt"), + ("asap7", "mock-cpu", "//flow/designs/asap7/mock-cpu:mock_cpu_grt"), +] + +def parse_stage_metrics(platform, design_name, stage): + workspace_dir = os.environ.get("BUILD_WORKSPACE_DIRECTORY", ".") + stage_file_map = { + "3_place": "3_3_place_gp.json", + "5_grt": "5_1_grt.json" + } + json_filename = stage_file_map.get(stage, f"{stage}.json") + + # Path in bazel-bin + json_file = Path(workspace_dir) / "bazel-bin" / "flow" / "designs" / platform / design_name / "logs" / platform / design_name / "base" / json_filename + if not json_file.exists(): + # Try alternate design folder structure if design_name differs from package + for candidate in (Path(workspace_dir) / "bazel-bin" / "flow" / "designs" / platform).rglob(json_filename): + json_file = candidate + break + + if not json_file.exists(): + return None, None, None + + try: + import json + with open(json_file) as f: + data = json.load(f) + prefix = "globalplace" if "3_place" in stage else "globalroute" + fmax = data.get(f"{prefix}__timing__fmax", None) + ws = data.get(f"{prefix}__timing__setup__ws", None) + tns = data.get(f"{prefix}__timing__setup__tns", None) + return fmax, ws, tns + except Exception as e: + return None, None, None + + +def generate_table(): + workspace_dir = os.environ.get("BUILD_WORKSPACE_DIRECTORY", ".") + rows = [] + + for platform, design_name, target in TARGETS: + p_fmax, p_ws, p_tns = parse_stage_metrics(platform, design_name, "3_place") + g_fmax, g_ws, g_tns = parse_stage_metrics(platform, design_name, "5_grt") + + if p_fmax is None or g_fmax is None: + print(f"Building {target}...") + cmd = ["bazelisk", "build", target] + res = subprocess.run(cmd, cwd=workspace_dir, capture_output=True, text=True) + if res.returncode != 0: + print(f"Warning: Build failed for {target}") + rows.append({ + "PDK": platform, + "Design": design_name, + "Place Fmax (MHz)": "N/A (Build Failed)", + "GRT Fmax (MHz)": "N/A", + "Place WS": "N/A", + "GRT WS": "N/A", + "Fmax Ratio (GRT/Place)": "N/A", + "Status": "Build Error" + }) + continue + p_fmax, p_ws, p_tns = parse_stage_metrics(platform, design_name, "3_place") + g_fmax, g_ws, g_tns = parse_stage_metrics(platform, design_name, "5_grt") + + if p_fmax is None or g_fmax is None: + rows.append({ + "PDK": platform, + "Design": design_name, + "Place Fmax (MHz)": "N/A", + "GRT Fmax (MHz)": "N/A", + "Place WS": "N/A", + "GRT WS": "N/A", + "Fmax Ratio (GRT/Place)": "N/A", + "Status": "Missing Data" + }) + continue + + p_fmax_mhz = p_fmax / 1e6 + g_fmax_mhz = g_fmax / 1e6 + ratio = g_fmax_mhz / p_fmax_mhz if p_fmax_mhz != 0 else 0.0 + + status = "Good Correlation" if ratio >= 0.85 else ("Moderate Divergence" if ratio >= 0.70 else "Poor Correlation") + + unit = "ps" if platform == "asap7" else "ns" + rows.append({ + "PDK": platform, + "Design": design_name, + "Place Fmax (MHz)": f"{p_fmax_mhz:.1f}", + "GRT Fmax (MHz)": f"{g_fmax_mhz:.1f}", + "Place WS": f"{p_ws:.2f} {unit}", + "GRT WS": f"{g_ws:.2f} {unit}", + "Fmax Ratio (GRT/Place)": f"{ratio:.3f}", + "Status": status + }) + + df = pd.DataFrame(rows) + # Simple custom markdown table formatting without tabulate dependency + headers = list(df.columns) + + lines = [ + "| " + " | ".join(headers) + " |", + "| " + " | ".join(["---"] * len(headers)) + " |" + ] + for _, row in df.iterrows(): + lines.append("| " + " | ".join(str(val) for val in row) + " |") + md_table = "\n".join(lines) + + print("\n" + "="*80) + print("PLACE vs GLOBAL ROUTE (GRT) TIMING CORRELATION STUDY") + print("="*80 + "\n") + print(md_table) + print("\n" + "="*80 + "\n") + + out_path = Path(workspace_dir) / "correlation_table.md" + with open(out_path, "w") as f: + f.write("# Place vs Global Route Timing Correlation Study\n\n") + f.write(md_table + "\n") + +if __name__ == "__main__": + generate_table() diff --git a/flow/scripts/global_place.tcl b/flow/scripts/global_place.tcl index 5822ef47c2..afc13f2723 100644 --- a/flow/scripts/global_place.tcl +++ b/flow/scripts/global_place.tcl @@ -28,6 +28,18 @@ append_env_var global_placement_args GPL_ROUTABILITY_DRIVEN -routability_driven append_env_var global_placement_args GPL_RANDOM_SEED -random_seed 1 +set use_engine_defaults [expr {[info exists ::env(GPL_USE_ENGINE_DEFAULTS)] && $::env(GPL_USE_ENGINE_DEFAULTS)}] +if { !$use_engine_defaults } { + if { [info exists ::env(GPL_WIRELENGTH_PENALTY)] } { + lappend global_placement_args -init_wirelength_coef $::env(GPL_WIRELENGTH_PENALTY) + } + + if { [info exists ::env(GPL_TIMING_SPAN_CLOCK_PERCENT)] } { + lappend global_placement_args -virtual_cts_max_skew_fraction $::env(GPL_TIMING_SPAN_CLOCK_PERCENT) + } +} + + # Parameters for timing driven mode in global placement if { $::env(GPL_TIMING_DRIVEN) } { lappend global_placement_args {-timing_driven} diff --git a/flow/scripts/place_only_and_measure.tcl b/flow/scripts/place_only_and_measure.tcl new file mode 100644 index 0000000000..365f617b4d --- /dev/null +++ b/flow/scripts/place_only_and_measure.tcl @@ -0,0 +1,42 @@ +# Runs OpenROAD flow up to placement and evaluates minimum achievable clock period. + +set ::env(KEEP_VARS) 1 +set ::env(WRITE_ODB_AND_SDC_EACH_STAGE) 0 + +if { [info exists ::env(ODB_FILE)] } { + set odb_path $::env(ODB_FILE) + set sdc_path [file rootname $odb_path].sdc + file copy -force $odb_path $::env(RESULTS_DIR)/1_synth.odb + file copy -force $sdc_path $::env(RESULTS_DIR)/1_synth.sdc +} + +# The synth_odb stage already canonicalized constraints into 1_synth.sdc. +# Unset SDC_FILE so floorplan.tcl doesn't try to read the original file path +# which might fail inside the bazel sandbox. +unset -nocomplain ::env(SDC_FILE) + +set ::flow_expected [glob -nocomplain -directory $::env(RESULTS_DIR) *.odb *.sdc] + +proc flow_source { script } { + uplevel #0 [list source $::env(SCRIPTS_DIR)/$script] + foreach f [glob -nocomplain -directory $::env(RESULTS_DIR) *.odb *.sdc] { + if { [lsearch -exact $::flow_expected $f] == -1 } { + error "$script wrote $f: with WRITE_ODB_AND_SDC_EACH_STAGE=0 stage scripts must not write .odb/.sdc files" + } + } +} + +# Run the flow up to place + +flow_source floorplan.tcl +flow_source macro_place.tcl +flow_source tapcell.tcl +flow_source pdn.tcl +flow_source global_place_skip_io.tcl +flow_source io_placement.tcl +flow_source global_place.tcl +flow_source resize.tcl +flow_source detail_place.tcl + +# Evaluate target function (measures clock / WNS) +flow_source target-function.tcl diff --git a/flow/scripts/qor_plotter.py b/flow/scripts/qor_plotter.py new file mode 100644 index 0000000000..ad70309465 --- /dev/null +++ b/flow/scripts/qor_plotter.py @@ -0,0 +1,246 @@ +import sys +import os +import subprocess +import pandas as pd +try: + from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTableView, QComboBox, QLabel, QMessageBox + from PySide6.QtCore import QAbstractTableModel, Qt +except ImportError: + print("PySide6 is required. Please install it.") + sys.exit(1) + +import matplotlib +matplotlib.use('qtagg') +from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.figure import Figure + +class PandasModel(QAbstractTableModel): + def __init__(self, data): + super().__init__() + self._data = data + + def rowCount(self, parent=None): + return self._data.shape[0] + + def columnCount(self, parent=None): + return self._data.shape[1] + + def data(self, index, role=Qt.DisplayRole): + if index.isValid() and role == Qt.DisplayRole: + val = self._data.iloc[index.row(), index.column()] + if pd.isna(val): + return "" + if isinstance(val, float): + return f"{val:.4f}" + return str(val) + return None + + def headerData(self, col, orientation, role): + if orientation == Qt.Horizontal and role == Qt.DisplayRole: + return self._data.columns[col] + return None + +class QoRPlotter(QMainWindow): + def __init__(self, csv_file): + super().__init__() + self.csv_file = csv_file + self.pdk = "asap7" if "asap7" in csv_file.lower() else "sky130hd" + self.setWindowTitle(f"QoR Path Correlation Debugger - {os.path.basename(csv_file)}") + self.df = pd.read_csv(csv_file) + + main_widget = QWidget() + self.setCentralWidget(main_widget) + layout = QVBoxLayout(main_widget) + + # Controls + ctrl_layout = QHBoxLayout() + self.x_stage_cb = QComboBox() + self.y_stage_cb = QComboBox() + self.metric_cb = QComboBox() + + stages = ["3_place", "5_grt", "6_route"] + allowed_metrics = ["min_clk_period", "net_delay", "logic_delay", "total_cap", "buffers"] + + metrics = set() + for col in self.df.select_dtypes(include=['float64', 'int64']).columns: + for stage in stages: + if col.startswith(f"{stage}_"): + m = col[len(f"{stage}_"):] + if m in allowed_metrics: + metrics.add(m) + + self.x_stage_cb.addItems(stages) + self.y_stage_cb.addItems(stages) + self.metric_cb.addItems(sorted(list(metrics))) + + # Try to set sensible defaults + self.x_stage_cb.setCurrentText("5_grt") + self.y_stage_cb.setCurrentText("6_route") + if "min_clk_period" in metrics: + self.metric_cb.setCurrentText("min_clk_period") + + self.x_stage_cb.currentTextChanged.connect(self.update_plot) + self.y_stage_cb.currentTextChanged.connect(self.update_plot) + self.metric_cb.currentTextChanged.connect(self.update_plot) + + ctrl_layout.addWidget(QLabel("X-Axis Stage:")) + ctrl_layout.addWidget(self.x_stage_cb) + ctrl_layout.addWidget(QLabel("Y-Axis Stage:")) + ctrl_layout.addWidget(self.y_stage_cb) + ctrl_layout.addWidget(QLabel("Metric:")) + ctrl_layout.addWidget(self.metric_cb) + layout.addLayout(ctrl_layout) + + # Plot + self.fig = Figure(figsize=(8, 6)) + self.canvas = FigureCanvas(self.fig) + self.ax = self.fig.add_subplot(111) + layout.addWidget(self.canvas) + + # Table + self.table = QTableView() + self.model = PandasModel(self.df) + self.table.setModel(self.model) + layout.addWidget(self.table) + + self.update_plot() + + def update_plot(self): + if self.df is None or self.df.empty: + return + + x_stage = self.x_stage_cb.currentText() + y_stage = self.y_stage_cb.currentText() + metric = self.metric_cb.currentText() + + x_col = f"{x_stage}_{metric}" + y_col = f"{y_stage}_{metric}" + + if x_col not in self.df.columns or y_col not in self.df.columns: + return + + valid_df = self.df[[x_col, y_col]].dropna() + x_data = valid_df[x_col] + y_data = valid_df[y_col] + + self.ax.clear() + self.ax.scatter(x_data, y_data, alpha=0.5) + + self.csv_file = csv_file + self.pdk = "asap7" if "asap7" in csv_file.lower() else "sky130hd" + + # Unit label + unit_str = "" + if "clk_period" in metric or "delay" in metric: + if "asap7" in self.pdk: + unit_str = " (ps)" + else: + unit_str = " (ns)" + elif "cap" in metric: + unit_str = " (fF)" + + # Diagonal line + if len(x_data) > 0 and len(y_data) > 0: + min_val = float(min(x_data.min(), y_data.min())) + max_val = float(max(x_data.max(), y_data.max())) + self.ax.plot([min_val, max_val], [min_val, max_val], 'r--') + + self.ax.set_xlabel(f"{x_col}{unit_str}") + self.ax.set_ylabel(f"{y_col}{unit_str}") + self.ax.grid(True) + self.ax.set_title(f"Correlation: {x_col} vs {y_col}") + self.fig.tight_layout() + self.canvas.draw() + +def build_and_extract_data(pdk, design): + print(f"Building and extracting path data for {pdk} {design}...") + cwd = os.getcwd() + dump_dir = os.path.join(cwd, f"qor_dumps_{pdk}_{design}") + os.makedirs(dump_dir, exist_ok=True) + + cmd = [ + "bazelisk", "build", + "--define=GPL_USE_ENGINE_DEFAULTS=1", + "--spawn_strategy=local", + f"--action_env=DUMP_DIR={dump_dir}", + f"--define=POST_GLOBAL_PLACE_TCL={cwd}/flow/scripts/dump_paths_place.tcl", + f"--define=POST_GLOBAL_ROUTE_TCL={cwd}/flow/scripts/dump_paths_grt.tcl", + f"--define=POST_DETAIL_ROUTE_TCL={cwd}/flow/scripts/dump_paths_route.tcl", + f"//flow/designs/{pdk}/{design}:{design}_route" + ] + + result = subprocess.run(cmd, cwd=cwd) + if result.returncode != 0: + print(f"Error: Build failed for {pdk} {design}.") + sys.exit(1) + + reports_dir = os.path.join(cwd, "bazel-bin", "flow", "designs", pdk, design, "reports", pdk, design, "base") + try: + df3 = pd.read_csv(os.path.join(reports_dir, "3_place_timing_paths.csv")) + df5 = pd.read_csv(os.path.join(reports_dir, "5_grt_timing_paths.csv")) + df6 = pd.read_csv(os.path.join(reports_dir, "6_route_timing_paths.csv")) + except Exception as e: + print(f"Error reading CSVs: {e}") + sys.exit(1) + + def get_worst_paths(df, stage_prefix): + if f'{stage_prefix}_min_clk_period' in df.columns: + df = df.sort_values(f'{stage_prefix}_min_clk_period', ascending=False) + elif f'{stage_prefix}_slack' in df.columns: + df = df.sort_values(f'{stage_prefix}_slack', ascending=True) + return df.drop_duplicates(subset=['startpoint', 'endpoint'], keep='first') + + df3 = get_worst_paths(df3, "3_place") + df5 = get_worst_paths(df5, "5_grt") + df6 = get_worst_paths(df6, "6_route") + + df_merged = df3.merge(df5, on=['startpoint', 'endpoint'], how='outer') + df_merged = df_merged.merge(df6, on=['startpoint', 'endpoint'], how='outer') + + out_csv = os.path.join(cwd, f"path_data_{pdk}_{design}.csv") + df_merged.to_csv(out_csv, index=False) + print(f"Saved aggregated data to {out_csv}") + return out_csv + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser(description="QoR Correlation Plotter") + parser.add_argument('csv_file', nargs='?', help='Merged CSV file or design name') + parser.add_argument('--design', help='Design platform/variant name, e.g., sky130hd/gcd') + + args = parser.parse_args() + + csv_file = args.csv_file + if args.design: + parts = args.design.strip('/').split('/') + if len(parts) == 2: + pdk, design = parts + else: + pdk, design = "sky130hd", parts[0] + csv_file = build_and_extract_data(pdk, design) + elif csv_file and '/' in csv_file and not csv_file.endswith('.csv'): + parts = csv_file.strip('/').split('/') + if len(parts) == 2: + pdk, design = parts + else: + pdk, design = "sky130hd", parts[0] + csv_file = build_and_extract_data(pdk, design) + elif csv_file: + build_dir = os.environ.get("BUILD_WORKING_DIRECTORY", os.getcwd()) + if not os.path.isabs(csv_file): + if os.path.exists(csv_file): + csv_file = os.path.abspath(csv_file) + elif os.path.exists(os.path.join(build_dir, csv_file)): + csv_file = os.path.abspath(os.path.join(build_dir, csv_file)) + elif not csv_file: + csv_file = build_and_extract_data("sky130hd", "gcd") + + if not csv_file or not os.path.exists(csv_file): + print("Error: Could not locate or build CSV data file.") + sys.exit(1) + + app = QApplication(sys.argv) + window = QoRPlotter(csv_file) + window.resize(1000, 800) + window.show() + sys.exit(app.exec()) diff --git a/flow/scripts/target-function.tcl b/flow/scripts/target-function.tcl new file mode 100644 index 0000000000..54cdc51b20 --- /dev/null +++ b/flow/scripts/target-function.tcl @@ -0,0 +1,18 @@ +# Evaluate placement by tightening clocks and measuring WNS + +if { [info exists ::env(TIGHTEN_CLOCK_PERIOD)] } { + foreach clk [all_clocks] { + set period [get_property $clk period] + set uncertainty [expr {$period * $::env(TIGHTEN_CLOCK_PERIOD)}] + set_clock_uncertainty -setup $uncertainty $clk + puts "TIGHTEN_CLOCK_PERIOD: Set setup uncertainty for clock [get_property $clk name] to $uncertainty" + } +} + +estimate_parasitics -placement +set wns [sta::time_sta_ui [sta::worst_slack_cmd "max"]] +puts "Target Function WNS: $wns" + +set f [open [file join $::env(RESULTS_DIR) "target_function.txt"] w] +puts $f $wns +close $f diff --git a/flow/scripts/tune_gpl_magic.py b/flow/scripts/tune_gpl_magic.py new file mode 100755 index 0000000000..005ac32d3a --- /dev/null +++ b/flow/scripts/tune_gpl_magic.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +import argparse +import json +import subprocess +import os +import time +import yaml +from pathlib import Path +import optuna + +# Small designs for fast broad search +SMALL_TARGETS = [ + "//flow/designs/sky130hd/gcd:gcd_tune_gpl", + "//flow/designs/asap7/gcd:gcd_tune_gpl" +] + +# Large designs for detailed search +LARGE_TARGETS = [ + "//flow/designs/sky130hd/jpeg:jpeg_encoder_tune_gpl", + "//flow/designs/sky130hd/riscv32i:riscv_tune_gpl", + "//flow/designs/asap7/mock-cpu:mock_cpu_tune_gpl" +] + +def parse_metrics(target_label): + pkg, name = target_label.split(":") + pkg_path = pkg.replace("//", "") + + platform = pkg_path.split("/")[-2] + # The target name is e.g. "jpeg_encoder_tune_gpl" + design_name = name.replace("_tune_gpl", "") + + workspace_dir = os.environ.get("BUILD_WORKSPACE_DIRECTORY", ".") + report_file = Path(workspace_dir) / "bazel-bin" / pkg_path / "results" / platform / design_name / "tune_gpl" / "target_function.txt" + + if report_file.exists(): + with open(report_file, "r") as f: + try: + wns = float(f.read().strip()) + return wns + except ValueError: + pass + return None + +def run_experiment(targets, params): + cmd = ["bazelisk", "build"] + for k, v in params.items(): + cmd.append(f"--define={k}={v}") + for k, v in params.items(): + cmd.append(f"--action_env={k}={v}") + + cmd.extend(targets) + + print(f"Running: {' '.join(cmd)}") + workspace_dir = os.environ.get("BUILD_WORKSPACE_DIRECTORY", ".") + result = subprocess.run(cmd, capture_output=True, text=True, cwd=workspace_dir) + + if result.returncode != 0: + print(f"Build failed for params {params}") + return None + + score = 0 + valid = True + for t in targets: + wns = parse_metrics(t) + if wns is None: + valid = False + break + score += wns + + if valid: + return score / len(targets) + return None + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--output", default="study_results.yaml") + parser.add_argument("--pdk", choices=["sky130hd", "asap7", "all"], default="all") + args = parser.parse_args() + + if args.pdk == "sky130hd": + small_targets = ["//flow/designs/sky130hd/gcd:gcd_tune_gpl"] + large_targets = ["//flow/designs/sky130hd/jpeg:jpeg_encoder_tune_gpl", "//flow/designs/sky130hd/riscv32i:riscv_tune_gpl"] + elif args.pdk == "asap7": + small_targets = ["//flow/designs/asap7/gcd:gcd_tune_gpl"] + large_targets = ["//flow/designs/asap7/mock-cpu:mock_cpu_tune_gpl"] + else: + small_targets = SMALL_TARGETS + large_targets = LARGE_TARGETS + + print("Starting Phase 1: Broad search on small designs (15 minutes budget)") + + study = optuna.create_study(direction="maximize") + + start_time = time.time() + budget_phase1 = 15 * 60 # 15 minutes + + def objective(trial): + wl_penalty = trial.suggest_float("GPL_WIRELENGTH_PENALTY", 0.01, 10.0, log=True) + timing_span = trial.suggest_float("GPL_TIMING_SPAN_CLOCK_PERCENT", 0.0, 0.5) + tighten_clock = trial.suggest_float("TIGHTEN_CLOCK_PERIOD", 0.0, 0.2) + + params = { + "GPL_WIRELENGTH_PENALTY": wl_penalty, + "GPL_TIMING_SPAN_CLOCK_PERCENT": timing_span, + "TIGHTEN_CLOCK_PERIOD": tighten_clock + } + + score = run_experiment(small_targets, params) + if score is None: + raise optuna.exceptions.TrialPruned() + + return score + + study.optimize(objective, timeout=budget_phase1) + + print("Phase 1 complete. Best trial so far:") + print(study.best_trial) + + completed_trials = [t for t in study.trials if t.state == optuna.trial.TrialState.COMPLETE] + completed_trials.sort(key=lambda t: t.value, reverse=True) + + print("\nStarting Phase 2: Detailed search on large designs (6 hours budget)") + budget_phase2 = 6 * 3600 # 6 hours + + if not completed_trials: + print("No completed trials from Phase 1. Exiting.") + return + + # Measure time for 1 sample + print(f"Measuring time for 1 sample on {large_targets}...") + sample_params = completed_trials[0].params + t0 = time.time() + run_experiment(large_targets, sample_params) + t1 = time.time() + time_per_sample = t1 - t0 + + print(f"Time for 1 sample: {time_per_sample:.2f} seconds") + + if time_per_sample > 0: + N = int(budget_phase2 / time_per_sample) + else: + N = 1 + + N = max(1, min(N, len(completed_trials))) + print(f"Budget allows for {N} samples. Taking top {N} candidates from Phase 1.") + + top_candidates = completed_trials[:N] + phase2_results = [] + + best_phase2_params = None + best_phase2_score = float('-inf') + + for candidate in top_candidates: + print(f"Evaluating candidate: {candidate.params}") + score = run_experiment(large_targets, candidate.params) + + if score is not None: + phase2_results.append({"params": candidate.params, "score": score}) + if score > best_phase2_score: + best_phase2_score = score + best_phase2_params = candidate.params + + print("\nStudy complete.") + print(f"Best overall parameters from Phase 2: {best_phase2_params} with score {best_phase2_score}") + + workspace_dir = os.environ.get("BUILD_WORKSPACE_DIRECTORY", ".") + output_path = Path(workspace_dir) / args.output + with open(output_path, "w") as f: + yaml.dump({"best_parameters": best_phase2_params, "score": best_phase2_score, "all_results": phase2_results}, f) + +if __name__ == "__main__": + main() diff --git a/flow/scripts/variables.yaml b/flow/scripts/variables.yaml index 696b04278f..a3531e4bd1 100644 --- a/flow/scripts/variables.yaml +++ b/flow/scripts/variables.yaml @@ -664,6 +664,24 @@ GPL_TIMING_DRIVEN: stages: - place default: 1 +GPL_USE_ENGINE_DEFAULTS: + description: | + If set to 1, ignores ORFS-specific parameters and forces the global placement engine to use its internal hardcoded defaults. + stages: + - place + default: 0 +GPL_WIRELENGTH_PENALTY: + description: | + Global placement wirelength penalty (passed as -init_wirelength_coef or similar logic). + stages: + - place + default: 0.0627 +GPL_TIMING_SPAN_CLOCK_PERCENT: + description: | + Global placement timing weight span as a percent of clock (passed to virtual_cts_max_skew_fraction or similar logic). + stages: + - place + default: 0.240 GPL_ROUTABILITY_DRIVEN: description: | Specifies whether the placer should use routability driven placement. @@ -1644,3 +1662,9 @@ GRT_SEED: - grt tunable: 1 type: int +TIGHTEN_CLOCK_PERIOD: + description: > + Tighten the clock period by this fraction (e.g. 0.1 for 10%) before measuring minimum clock period. Used by the autotuner. + stages: + - place + default: 0.0638 diff --git a/flow/util/requirements.in b/flow/util/requirements.in index df6af3963f..d815d2fdc0 100644 --- a/flow/util/requirements.in +++ b/flow/util/requirements.in @@ -1,2 +1,5 @@ matplotlib==3.10.0 PyYAML==6.0.2 +optuna +PySide6 +pandas diff --git a/flow/util/requirements_lock.txt b/flow/util/requirements_lock.txt index e6aa58ad02..d8ac2398fa 100644 --- a/flow/util/requirements_lock.txt +++ b/flow/util/requirements_lock.txt @@ -4,6 +4,14 @@ # # bazel run //flow/util:requirements.update # +alembic==1.19.1 \ + --hash=sha256:b39018cb3d9413a19cbd54cf3c02ad33998641f0538eb77413a488a21c3e14be \ + --hash=sha256:e0fca0518118c78acc493e31bcb5402f190057aaf6df8b5b95ce94c4789cf648 + # via optuna +colorlog==6.12.0 \ + --hash=sha256:2a7924c1dadf18b22a0eb8b06d1c7b01d5341707ec1641eb6fcc4fde0c3e8e5f \ + --hash=sha256:30d392604e9110045a2c2aeefc27d7a017abbab63f3a8aee594eac0801df784e + # via optuna contourpy==1.3.1 \ --hash=sha256:041b640d4ec01922083645a94bb3b2e777e6b626788f4095cf21abbe266413c1 \ --hash=sha256:05e806338bfeaa006acbdeba0ad681a10be63b26e1b17317bfac3c5d98f36cda \ @@ -116,6 +124,87 @@ fonttools==4.55.7 \ --hash=sha256:f669910b64d27750398f6c56c651367d4954b05c86ff067af1c9949e109cf1e2 \ --hash=sha256:fd4ebc475d43f3de2b26e0cf551eff92c24e22d1aee03dc1b33adb52fc2e6cb2 # via matplotlib +greenlet==3.5.5 \ + --hash=sha256:03115c2e0a371999bf8ae616aa8d653f96641d4705c457aebaa187276e9f7537 \ + --hash=sha256:03551ed792cb1b4fc0277a0c60dfd8c343894a0ba06fe60dcd22f568b433da39 \ + --hash=sha256:0e5a7de979d764aea1f5b6e95cf92b5b37741b9823702041f34b126e7f690277 \ + --hash=sha256:102817506f6090b5176c746a82603341a549b40e5c3d5b72a4c672228a918c41 \ + --hash=sha256:12e2ee66c2aba86133f10fd99d6a8856c6d351ffb7be0e4d52ef2cc5fbb705b2 \ + --hash=sha256:147b25a42e5ca5be3d42356e8f608b37af715a1c196e9bf9d1627f3341adfe1d \ + --hash=sha256:159df1942d88e8f784cbb38d6f18bdb365cd11319cfbb3e89623de2b97892d53 \ + --hash=sha256:182de51c6b572a705f2fafaab2e783bcf7d2760940229dfe73086cbae037af3e \ + --hash=sha256:19d59f068887d8c5907fc177f27683413ace3011b6ed646c0b309266e74a6502 \ + --hash=sha256:19e4e026fe20691f333b8eb1a3bc9625eceba8c3f9d62ec5a6f8581afbc6b5a5 \ + --hash=sha256:1af90aa4bc129883b340cdd6957a3bc74f60528a4993bbd1f53aaebe1d9981cc \ + --hash=sha256:1b5ed9162c0c098e0bbc2cf88a94f433c1b8926f831745252e099e5d83e17759 \ + --hash=sha256:1e8d9391fe77f15649589a907cef972dbbd6352ef7ff7dc0492f658c0c26495f \ + --hash=sha256:27493374cff1d1b7919dc8126547f2aea582737e3046147b434b1e12de56389b \ + --hash=sha256:2888a3a38bc5ee5bb6c438372197152e815837e4fab7ed7a1f86ef18ffd58ad1 \ + --hash=sha256:2b70a766135540c472ac1393d57c2e1b4a2eb85bf526a1e41e6d096173a8cee5 \ + --hash=sha256:2d57406c3efd32d7a81e17a674314e8bd00792cdab49ea3228a49aa1bfb2e769 \ + --hash=sha256:2eabb980975cba5b93a95f6f69287d05fc05ac955bfd6a320a7c083eeb52c0b0 \ + --hash=sha256:3134291427bb0f3526e9d90311988caf336eb43730e95244997a4fb15f45144f \ + --hash=sha256:35cbb8bf55ace57fbccb4fb8622c4521713acd8691e77f4696d416ea7ca527da \ + --hash=sha256:37faa97daccb6d9f4c2141ce3118d023c3c5506864a7d8bdf726f665018c1f76 \ + --hash=sha256:40239b5384f96da3963585cc6d7eaa9b56f8ae67e8d92cc82dd9e202fc847de3 \ + --hash=sha256:4441153ffba21b90d3ca89fe3d31f5c093ae6c0bf0cfdfc98f54cde22f95b62e \ + --hash=sha256:44f08341873200ba8a60a8bc14ace3d91f1754f7fa7bc66157714a8cd420a476 \ + --hash=sha256:469dbb0a78625642f4a626cfd0c6e8bccc0385b5e49189b6308bbe849ec88a8e \ + --hash=sha256:49520f0c95a48b42cf55414b8e8479beb274ea70431afc33e3f79903c71f4380 \ + --hash=sha256:499adea519f748407fc6806d20eedabac2884fd73b9f38d81236e190ba20dfef \ + --hash=sha256:49ddacd36af37735fab103846f4ee4d18a492dde72730d1699c0c8ebe30d9f18 \ + --hash=sha256:4dfc7c4470354e7b09184d1a3a985761053a2fd694ddb5b5c80242afc2c8c90b \ + --hash=sha256:5173a72310725a74afc82c164f0e52cb8ad0de62f2bb623f24f6c0cc07d80272 \ + --hash=sha256:523bb8e27614d77101ea7a8cf59f8d91219b72d5c29f6a038c92b50828bfa8d0 \ + --hash=sha256:55272212cbc5f43d1d723725ab931f1939969b7e9523882ca58b55061769d053 \ + --hash=sha256:5e2afcfc4d4305dd715809b03da5cbe437c8984f61d8917751eb5fe4aefa3e07 \ + --hash=sha256:5e9ec2e7c98e895fcea0c5cc57b2606cf86ece6d0a56578f3eb225e2af4f0387 \ + --hash=sha256:5f1b1ff4828cdc1aba4266aff814085d04a1d07959287219af021b838b265d52 \ + --hash=sha256:634cf15a233a949136879dd388e25d3296e16f3f1e217d2456797b8579ebc6ed \ + --hash=sha256:655bca754a2ef4efcb0eb48a94d3f4593536d0f3d48f8ed44343c01d16a92f95 \ + --hash=sha256:68184dfcf50ccaa8e864770fe0633a7e27250ea9329f8192ef47ee9ecfd78e1c \ + --hash=sha256:6b241c32f912ada659808d68e308c568baf577eebf757d15471472de0c18cfad \ + --hash=sha256:6ca5d6ae0739e5764f2cfcfaa562ac5a990cbdaedca93251c5e3cf07c362371f \ + --hash=sha256:6d9b454c5fc48aeaa7c4337813dbf513a6870468e426438a04d922c6d0fe63db \ + --hash=sha256:70b157cd319873e8b544ddc2de158f55bbd0a9b0218c8ce9332039801518e328 \ + --hash=sha256:712aee154f648bde84634654bb38bb78c69ac640c37a45c9effed800735049d8 \ + --hash=sha256:72507285b5caa1d17904a3f7c322ca780823a54170a0e04ec3f37bcc60d4db71 \ + --hash=sha256:740e544169527b82695ce76af2f7ad6f030904658f2f3921a1d245771fb88cfc \ + --hash=sha256:74cc6df89ec5302337adc9cf096221cbed2510fd444b0e0f1586cf0470740864 \ + --hash=sha256:7805655781fb8f28a55d05fe57ed61f5f10f1892fb587673e3bb5264f28041f0 \ + --hash=sha256:7dffc5c859fe6059974df1e37d7923d654a83e2ae18fdd616994270e001115e1 \ + --hash=sha256:7f049911ee81a16a03c33d5450d8d5867d27f596ca5fb201b86f4524e874468b \ + --hash=sha256:816230f469381ad0a43abc9fa8dda5a699e32fb78958dde32ded93213b70a667 \ + --hash=sha256:86c5113d698cb8d927b2750bb1f1d59eefe3a37e0e0217491aee29a7f84ef52c \ + --hash=sha256:8a268024ce2d7d2b04694bf1594058981a9fa663d1df4b762dee499211ed7c1c \ + --hash=sha256:8bdfd1424abcf26832961e766570cae79efdb9599d709088c9cb6ef82b194926 \ + --hash=sha256:8fec3f165dfe332e490c3247c0f6c23b0bfc45f06496ad7f00ddb00e3d35e4dc \ + --hash=sha256:95c5b1f4b3a193f8a0c2de4bfdcb48d119f7f1063941f1de1f2168051b3e52dd \ + --hash=sha256:9ab5f5b93655e77fe0d6c2dfd22b5eac751bb1f876d8ec21761b7c1fb9266007 \ + --hash=sha256:9ec0dc0e59dc9c61af5c47348365ccbbd7addfafe0a93b00336ff3da2907bdc6 \ + --hash=sha256:9ff00e12102358292087274dfb1669132387ff6e7920ebf9d85f4826ce0d3a56 \ + --hash=sha256:a1eaccf5c3a1d3e46dead602c72e6836731e8e245c9de6a27764567b6b62d4c0 \ + --hash=sha256:a5433cf291e0ef9114bd14d0d824db6e5e4a43033234bca48181a9597acca07b \ + --hash=sha256:ab3df3dffb58bf70564e93a5cec7941e4d9faa5a36cc4234a10d3131afe04f53 \ + --hash=sha256:abc8bc8d9f935cd685457545b6a53863a877fdc12c2c0f5ee9beee18d9db139c \ + --hash=sha256:adb4bae02e91a8e863e48b177e4014bdcac8a6b5e047ea1df687a61534b85e6c \ + --hash=sha256:b18007dc2473a7942fd157366b55f01da6fed7ce85318591005b419e0a439474 \ + --hash=sha256:b79fd2a5bc099b5e744f34c4c9a58954a5f4cb7529fb4b6e8446057d61b6edaa \ + --hash=sha256:be63afcbbccfad3dd95a1ba12ada84dab2ef32031973d80b5b92df67fa763a61 \ + --hash=sha256:c0db80fcd5b8aece93f66c64f78a786bbb6b96c5fe63ef5a5a4581ecf8bab206 \ + --hash=sha256:c69bed34470abfcd456984fdadaa18e62169af4480335c45f3c32d1d9c12e638 \ + --hash=sha256:c6ce25fee6cabc8bf22cb8b52e642cbb821be5b9aec8094d07ff03378141b8e9 \ + --hash=sha256:d246c0db9a2513cd45f019ba178ea4d4d4705bd210ee465e2c15d76a1ab13874 \ + --hash=sha256:d4a389a852e392a6366058651a20fa5ba40d979865aa81bea2ccbdc44805070d \ + --hash=sha256:d98ef6f92e67c6dbf299dbfd8facc1b0d2d9cedf91e325e73b3d0373fe4309d8 \ + --hash=sha256:e604f58e35833fc46ef20302bcb314dddbfd3fcf33a4f936216d51dd678d63ae \ + --hash=sha256:ef6a08349401d8eaf3cb12688ac8557de95788556b8631ef17555a4a173022c0 \ + --hash=sha256:f0e5a21bd4452a88cf032fc43c4a5b307ab1380eacb63b5988f9c0317885e773 \ + --hash=sha256:f1e2db190db51c17433eee424803818cf0670bf049d9cfe0dd07be111d1aa7c4 \ + --hash=sha256:f2e3d061b8e13aec2f0441689b3c71b244a20e5d274a52cb0f7e31bd1d139552 \ + --hash=sha256:f7278591501941bb2456af102bb9cd59aab48c6cfd6e2dd68fa1290bb0c49a42 \ + --hash=sha256:fef01bd457f11fc158b130ca0027a3c365693280e8e231b65bdaf57999f39f5b + # via sqlalchemy kiwisolver==1.4.8 \ --hash=sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50 \ --hash=sha256:034d2c891f76bd3edbdb3ea11140d8510dca675443da7304205a2eaa45d8334c \ @@ -198,6 +287,101 @@ kiwisolver==1.4.8 \ --hash=sha256:ed33ca2002a779a2e20eeb06aea7721b6e47f2d4b8a8ece979d8ba9e2a167e34 \ --hash=sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794 # via matplotlib +mako==1.4.1 \ + --hash=sha256:a359d9a94a541213958742b2698d0a7757bb83551767bc468a74b9905aba9617 \ + --hash=sha256:d7904710b662996425a21627710c4777c45053146942cf8a7aebf757c92b8c27 + # via alembic +markupsafe==3.0.3 \ + --hash=sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f \ + --hash=sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a \ + --hash=sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf \ + --hash=sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19 \ + --hash=sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf \ + --hash=sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c \ + --hash=sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175 \ + --hash=sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219 \ + --hash=sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb \ + --hash=sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6 \ + --hash=sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab \ + --hash=sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26 \ + --hash=sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1 \ + --hash=sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce \ + --hash=sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218 \ + --hash=sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634 \ + --hash=sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695 \ + --hash=sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad \ + --hash=sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73 \ + --hash=sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c \ + --hash=sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe \ + --hash=sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa \ + --hash=sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559 \ + --hash=sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa \ + --hash=sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37 \ + --hash=sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758 \ + --hash=sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f \ + --hash=sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8 \ + --hash=sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d \ + --hash=sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c \ + --hash=sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97 \ + --hash=sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a \ + --hash=sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19 \ + --hash=sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9 \ + --hash=sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9 \ + --hash=sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc \ + --hash=sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2 \ + --hash=sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4 \ + --hash=sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354 \ + --hash=sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50 \ + --hash=sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698 \ + --hash=sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9 \ + --hash=sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b \ + --hash=sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc \ + --hash=sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115 \ + --hash=sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e \ + --hash=sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485 \ + --hash=sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f \ + --hash=sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12 \ + --hash=sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025 \ + --hash=sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009 \ + --hash=sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d \ + --hash=sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b \ + --hash=sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a \ + --hash=sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5 \ + --hash=sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f \ + --hash=sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d \ + --hash=sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1 \ + --hash=sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287 \ + --hash=sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6 \ + --hash=sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f \ + --hash=sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581 \ + --hash=sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed \ + --hash=sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b \ + --hash=sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c \ + --hash=sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026 \ + --hash=sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8 \ + --hash=sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676 \ + --hash=sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6 \ + --hash=sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e \ + --hash=sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d \ + --hash=sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d \ + --hash=sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01 \ + --hash=sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7 \ + --hash=sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419 \ + --hash=sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795 \ + --hash=sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1 \ + --hash=sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5 \ + --hash=sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d \ + --hash=sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42 \ + --hash=sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe \ + --hash=sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda \ + --hash=sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e \ + --hash=sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737 \ + --hash=sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523 \ + --hash=sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591 \ + --hash=sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc \ + --hash=sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a \ + --hash=sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50 + # via mako matplotlib==3.10.0 \ --hash=sha256:01d2b19f13aeec2e759414d3bfe19ddfb16b13a1250add08d46d5ff6f9be83c6 \ --hash=sha256:12eaf48463b472c3c0f8dbacdbf906e573013df81a0ab82f0616ea4b11281908 \ @@ -293,10 +477,62 @@ numpy==2.2.2 \ # via # contourpy # matplotlib + # optuna + # pandas +optuna==4.9.0 \ + --hash=sha256:b322e5cbdf1655fb84c37646c4a7a1f391de1b47806bbe222e015825d0a82b87 \ + --hash=sha256:f52f3be6148654850c92a5860d398fd88ec6b2c84ab68d9c3d07dcff02e7afee + # via -r flow/util/requirements.in packaging==24.2 \ --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f - # via matplotlib + # via + # matplotlib + # optuna +pandas==3.0.5 \ + --hash=sha256:08d24fe11a17dc33bd6e937dc9c665f9cba08fbdc9f657f405713515febe300d \ + --hash=sha256:0d298e951f23016ce4699951d044ae6418dbc91bf68cefca0f77666fcbb4e5c6 \ + --hash=sha256:0fac0010c75e4efb6b99e249c183a8993ce0dc95c240f9b120a5e67c727b7928 \ + --hash=sha256:1c10461f6eeb35d8f05b6184c65c8b9991663b66c46b1d559b682cb34ae7c6ea \ + --hash=sha256:25ff585b972a18ef1fe9ffa3ac6544d9950508aa76832e5147640b6022821e49 \ + --hash=sha256:2946e77e4a53cd248cbde631a12f0e51c8324ce354c3eba4d20147c1ad6f4282 \ + --hash=sha256:2a29c53d85ea98c5e792c59ef82ee9fbe6ca902c0d0adb6b23f45ef894cd7bf6 \ + --hash=sha256:2c0cf1dd9b55a22d105fc46c1b489af3bd42264fcba7c66297bf47a9a1d9c78a \ + --hash=sha256:2f264fc46911cc8131a7322a16199bbf8e353d27c10bb211f5bd0c814324dc36 \ + --hash=sha256:303da736987d481074ca720ada325f8bd80c64ebc2d45ed79b29df3aaa4a26ca \ + --hash=sha256:3b2801bbb049d0136f6c213eae02b5fca969384fc2064dd728d8620552aa49da \ + --hash=sha256:3c5015fd1730fbf883647e88068176c839c102cea883ba1769a6f4593bfc1f8c \ + --hash=sha256:3c5ed2e7c06e91d340dfd091d7934f9bc82e4a36b95f647f090b9d1c9ac649da \ + --hash=sha256:4b11c36e218331d0387cbe3a0a5f75162357a1d92d57b2b08a336ff94b19b2be \ + --hash=sha256:5183427f5a8156d480f30333777bc978be93650a49a7c01db26adffe95b31e85 \ + --hash=sha256:53730687fcd161883b24e10411c06d6a4c0f2275d2faf3bb2bc25deb4ba8007c \ + --hash=sha256:66266d3442a5e8b3c90274c2b8b230bee42dd1c286bc822cc2f9f2c7e12b883e \ + --hash=sha256:679f4e85b30ddb1515458ab1e788d3e260eae369b1f78da7a3aa4cac8ebf4a2a \ + --hash=sha256:71ecc8fb7ed1a7aa4392316b5309a6347e8e7f832f38fd897846b3a1457a9298 \ + --hash=sha256:73fa87b08a7ef706f8aafda39ddaccf2a99047bea62d8c88a0361bcafb2237bc \ + --hash=sha256:80a611068e8a3ac23f7398c6c14eb46dc974e5cc9997f653e2dcfd1da74edd41 \ + --hash=sha256:960d3ebcf249f75206899fcd2c6de53f736b7265759ced0d3e559df0b8b709b0 \ + --hash=sha256:9e94c2c5ca43bd3ca32bf64d32308887b65e5f9bfd8023ea52755107a999f93b \ + --hash=sha256:a5ad3b02ed6bc7d7ae9b70804b2c6aa31827489d150f8e623ce82491b82085d7 \ + --hash=sha256:b1261758dfb6cf12c3cff8300e21cefad30e7ec709abb4c24ac7318e6a52462a \ + --hash=sha256:b173f5951ff6b8b0ec7675e20dff3c97b7e7a57dfcce387c2d7c5afe87cb7899 \ + --hash=sha256:b2acb4650527eec6822c3dadb2b771277b65e7dae7a267d4bccf65fd1bb3fbce \ + --hash=sha256:b58b1b39d46a5862e3fb18f50d1a201398619d16a0f9f73f57eea5583cf0e63c \ + --hash=sha256:b86765f268b56f7e665b93bce9d5df69dee7f99e595cf8fb839483ab315942a3 \ + --hash=sha256:c1c05a767fe8e5b4fe9e1c29806829c582052eaedb9120a3da83ba3f69e24a5b \ + --hash=sha256:c2e26bb46934b8a2ca0c3de1d3d606fc5f6746584791b2db264d58cf370e08dc \ + --hash=sha256:c597ecf5616b5c420372c1d4d4c00dbbfba7398bea857dcc984347e1ea48417b \ + --hash=sha256:cce3a9d11d2b1f82c69a27ec1f4948a170e2c403c4bbfa8cca62e3fdebe2ef3a \ + --hash=sha256:cd8f7c6dc98527058ee6264219343f5392240a6f1bfa654fc5d79023020d0c92 \ + --hash=sha256:cf52e1f61d229496da17dc7ab54acdee627357e7008fd4fecba3d0ba2937fa58 \ + --hash=sha256:d373ce03ffd84010ed9839fa73672a9c8256990532e158440c0085db7d914b34 \ + --hash=sha256:db172144bb56422bd157812f3b021eacc255451470b31e2c633c349490a1cfee \ + --hash=sha256:dca3734d6ab7c906e6730f0788b0a1dbb9f2467731f9711f77995c8e9d62d712 \ + --hash=sha256:e2759e890db96dfcffdbd9b86c3c2cb6afaf58def482820317e06163ec1066cd \ + --hash=sha256:e819dd5f62966b481a8cb649d3299ebd886a1ea91ed5a99bf7ce77c98d18ab94 \ + --hash=sha256:ef01af4d8dc6cd2c8d6c7736f149574ef93fe043811eeb5e445f2647154b5040 \ + --hash=sha256:fa290c16964d4963fbfbc358928239cf3bd755b20e988ce944877def2f44471d + # via -r flow/util/requirements.in pillow==11.1.0 \ --hash=sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83 \ --hash=sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96 \ @@ -374,10 +610,35 @@ pyparsing==3.2.1 \ --hash=sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1 \ --hash=sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a # via matplotlib +pyside6==6.11.1 \ + --hash=sha256:0968877ab1fb4ef3587a284da6fe05e8647ada56a6a3750b6395188e01f4aba6 \ + --hash=sha256:537682c3b7530817203e667c1f5a2f00486b37bf52c52eeab438544c7a0917f6 \ + --hash=sha256:75f0005c3eb95c07cfb65522ec50d0815ac007a96482c21dc3cb4b4c04895d84 \ + --hash=sha256:acee467cb5f256cc47ebb9d815a054c1d8416da380c191b247a76d164aa3f805 \ + --hash=sha256:b1fc521ba2bb5109425ab8add06bddbdd524abcad06cfa012cc39a22a189feb2 + # via -r flow/util/requirements.in +pyside6-addons==6.11.1 \ + --hash=sha256:0d13c4dfd671b050a48e4f8d8ddc724b7248f9c0437e7fc47fdf316278572923 \ + --hash=sha256:3494f480dee92f415be2f2d989c0b3f4755ac332b28045cbf4ba0f5c5a22ba37 \ + --hash=sha256:54733c77f789bef5f03c6aff4ad3bec8b2eff021f0cfcbc53d5e6c250ded24f9 \ + --hash=sha256:8e6c65fbd73a512d6f72cda8d8277444a85a34dc99dd1dae9c21d35b8671bb1f \ + --hash=sha256:bf1c6c4e954e5eba3d2a7c661ad4b9689e8f09c7f4a16bdf29713371d11af993 + # via pyside6 +pyside6-essentials==6.11.1 \ + --hash=sha256:11253ea52aabecefe9febddbbe78b43a824129e3af1cec98431028fba7fa954f \ + --hash=sha256:228de53c2bc26b07e5021fbe3614fc44ca08e4dab9999af08c2b389d2c239957 \ + --hash=sha256:63311bd48e32c584599ab04b9ef7c324082374cd2c9fa533f978fb893bb47e40 \ + --hash=sha256:a039b6da68a3a4b9d243217b2b98d475eed3f617159ef6be925badab53c11b0d \ + --hash=sha256:e3ef7027b41e4e55fadb56e3b3257dc8ee92154b639fe67fc4c8e05e9d976c60 + # via + # pyside6 + # pyside6-addons python-dateutil==2.9.0.post0 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 - # via matplotlib + # via + # matplotlib + # pandas pyyaml==6.0.2 \ --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \ --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \ @@ -432,8 +693,86 @@ pyyaml==6.0.2 \ --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \ --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 - # via -r flow/util/requirements.in + # via + # -r flow/util/requirements.in + # optuna +shiboken6==6.11.1 \ + --hash=sha256:1a16867f103ef1c662a5f09dfed03273a9f81688b174555162c58e83650a3f02 \ + --hash=sha256:1bd2f4314414df2d122d9f646e03b731bc6d6b5f77a5f53f99a4fe4e97d84e6f \ + --hash=sha256:7c8d9af17db4495d4fa5b1c393f218311c4855546b9dfa6a0bd21bcd66b55e9d \ + --hash=sha256:9a8bccfafc8805254cabcfa1edfaf55cd52889f4998c91ad0d9a4433fb1bcdbe \ + --hash=sha256:c2c6863aa80ec18c0f82cea3417837b279cdc60024ac17123461dc9042577df7 + # via + # pyside6 + # pyside6-addons + # pyside6-essentials six==1.17.0 \ --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 # via python-dateutil +sqlalchemy==2.0.52 \ + --hash=sha256:11560064cc4696e772298b6221ede59e646386d9f2a85d549365473b972f7850 \ + --hash=sha256:1b2d9e507a458832adcfbd8af6e2036ddf069b7710b799448542ebccae2dceee \ + --hash=sha256:1b92a1e23ed40022081217b40d2d1feba4f77064e69ef4f39f68bcbbd148452a \ + --hash=sha256:2d5e53e36e37129fe0be8b9d08b6e4052c10a963ee6cda56c8c10dcc194b99ca \ + --hash=sha256:2e15b1d1116a64fc399b8c2694a83f3e792fdc58df28514a81e1dc4f8cf22729 \ + --hash=sha256:2eb3c6a64b1bfe6704777cfd504e7b8ad093a5f3e03ce67663a5e6742f294e43 \ + --hash=sha256:2f5fa2b2aca75d2c7f36db3a8dd04717b6fbfd1a964fb32bdeae16698e475ab3 \ + --hash=sha256:2f9eccf8793c8c3f8dd2dfd11b9e400cb27d1d19370ef732b66017e212107822 \ + --hash=sha256:309cc8ba50fc5d2174189dfcd49cdf7aa711f8346afcff19f2642ae4fc449c14 \ + --hash=sha256:37a4d548327b6cab9c7d8cdb4e0e82feabee0110c4d150059068e2d1cfbd99ee \ + --hash=sha256:3b81b8363a919ce53453591cdb93702e6bd54ade6c4fa2f468fc053baee5ed89 \ + --hash=sha256:3c95c3044edddb65e4a2f7194ec52ca5a9736f72d33ca3a6fa4196aedcc689fd \ + --hash=sha256:410d52be41d17f1a236d19520fbe776257dc16516ed06bd16d433311842aefd9 \ + --hash=sha256:4699dbb8d396d199e7e78fd4d525e3ad3d6008a9c8c0160b87e74c606c2c3736 \ + --hash=sha256:46f0c46f0d360d727b84660b26c62b295d82306ec2c82b701e97747d2c6dcbe1 \ + --hash=sha256:49565daf5af554f538e23aef1fc81a95a4e49658f152285e45c02f5fc44f04cd \ + --hash=sha256:4b89e93bb89eabdbea9d5d3fa2d6cc6544e733c33064339f91e5292480cf130e \ + --hash=sha256:50bff43b632a56fbf5ed9afdd76307e1512b62051bcd5afb341ae67205bbb6c8 \ + --hash=sha256:5e2d46356ac2ccb7d268ab6c2319ac6a2b42f1b8d5fd8bd3d46855cd82abee97 \ + --hash=sha256:5f8438a98d49424acf69d0d53c0a522951dfe49a6f2d86417fbb37ad3066ab43 \ + --hash=sha256:651d6d8782e80679e6151707c7b490834d46ada526328895abf567f25e63d29c \ + --hash=sha256:6c1b7ed45bf87b214e0a9def9c2313949067efe6269db5ef18d542ee13250af7 \ + --hash=sha256:765f439da5bc8696973bc0c8a31fae0912ac3ff1cb9d66246a6b2728ee4fbbc8 \ + --hash=sha256:77a247d3fd179f6583171e7e0e98f40dc6642ed4f655557515a5a7e25923e9a4 \ + --hash=sha256:7a0d48c4b80717c61385b4e966e087c839a66cfd7b780641dcb428f4dba65608 \ + --hash=sha256:812bae5138bfc0aa46fb0686da0fc7f581f68e2bbb05bc24c3713bebaedd1437 \ + --hash=sha256:8738008376d22f30f411ea3efecf39b51110b6996d80bb73786f30bcfdd5fd3b \ + --hash=sha256:8cf993f065bc04caa5000b339e8d9d6f3d9d00251511f850147c516c9e07115f \ + --hash=sha256:923bb183c1dc64fdf7b717965e3d59938ec4f8b8710b419a21ce403e5da9a9e1 \ + --hash=sha256:9255ceb65a80c1b001129060b63ee776a2e9c288be3b662be36dfbb888fffdcd \ + --hash=sha256:938325a5373267afc53bfbe72983b20fbd64ca47842aac62433c3da1137ecff1 \ + --hash=sha256:9876b09b9f1ce7398b0ffece585c0a911244c53191187341f6bcae640e133751 \ + --hash=sha256:a593db51b3bae75db17a5738ad5f992244b3a03863f83c28117ee482c6a3f76d \ + --hash=sha256:a7438774e1091192fc50a2bd8ceff5c596912d00ecd46587e88effdea7826101 \ + --hash=sha256:ab66fa9618269390d4dfa222f2f2f88f7bc4bf5da13905131b818217db7e8057 \ + --hash=sha256:ab9da41e61b9979b910499d633b241df20c51ee5037e5405b11c2faac3cbe1a2 \ + --hash=sha256:afda3ec521d0517d0de783fc70030775841900896d832de5bbd066549290470e \ + --hash=sha256:b08cddb8989775e3c88799d86704bdfc3ee6e9846118201aa5997f16f27e3a15 \ + --hash=sha256:be8c49131665dfe2cc74c498aa1240ffb548d0fd901325dd11c2c7a18956f727 \ + --hash=sha256:c1e61d08bdf4ee2f41024569e3400de7d6734ba498144766b11260936ccfa582 \ + --hash=sha256:c63bda077685c85ca513286547a531ba57e7a68cf0a7ed3bafcc2bbd18896f4d \ + --hash=sha256:cce4922535db73f9dbb91e3db2b3e851ac629467fd1ebd8e354a60e369521c63 \ + --hash=sha256:cd9206024b8602e7518bbaf44016c29e0045722f09328d8e654941023920d0b3 \ + --hash=sha256:cef328349452ae152637df4d11ce5a0919ecdf0a363e16c830c3518ee33bde72 \ + --hash=sha256:de89de5b5798cafdd7ef7b7b804acec246d6152922128fd9d156cd1701271aff \ + --hash=sha256:df8f213ceb485d8227b74935eb87ba0d80169a8401eba7835da6e30d6727dac4 \ + --hash=sha256:dfe9ce533dbe4d0a2ae1486546619bd30b76bcd670539a44d910361376175f5e \ + --hash=sha256:e0c3ce43907374889f3352bdcc6195c970148a2cb71574cd0237a5071a37fb6c \ + --hash=sha256:e49f51a5d59857a7a0dcaf9469febf7197d9394bd88f00d69c2c4e848112cdbf \ + --hash=sha256:f1c850792a3b25a3ad74dade3f05e4f402cdebfea27438bcadafaa1617f77bcc \ + --hash=sha256:f2b09029ef6f260409eefa5dc2b8276f6c3d7b892bfb50d50e8f852257d4a6b4 \ + --hash=sha256:f4d4f7afc682961dc567db70e00a7b5bd81ccd3743c46199b0257f0744902dde + # via + # alembic + # optuna +tqdm==4.70.0 \ + --hash=sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220 \ + --hash=sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953 + # via optuna +typing-extensions==4.16.0 \ + --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \ + --hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5 + # via + # alembic + # sqlalchemy diff --git a/patches/openroad/0001-Add-PDK-aware-GPL-parameters.patch b/patches/openroad/0001-Add-PDK-aware-GPL-parameters.patch new file mode 100644 index 0000000000..b05bee9572 --- /dev/null +++ b/patches/openroad/0001-Add-PDK-aware-GPL-parameters.patch @@ -0,0 +1,149 @@ +From 06b8b5d592a51490cad48bff036b81b5264106f9 Mon Sep 17 00:00:00 2001 +From: Øyvind Harboe +Date: Wed, 12 Aug 2026 17:48:28 +0200 +Subject: [PATCH] Add PDK-aware GPL parameters + +--- + src/gpl/include/gpl/Replace.h | 2 ++ + src/gpl/src/nesterovBase.cpp | 1 + + src/gpl/src/nesterovBase.h | 1 + + src/gpl/src/replace.cpp | 1 + + src/gpl/src/replace.i | 2 ++ + src/gpl/src/replace.tcl | 4 ++++ + src/gpl/src/timingBase.cpp | 9 ++++++--- + src/gpl/src/timingBase.h | 5 +++++ + 8 files changed, 24 insertions(+), 5 deletions(-) + +diff --git a/src/gpl/include/gpl/Replace.h b/src/gpl/include/gpl/Replace.h +index b73e2a12..a5d89d42 100644 +--- a/src/gpl/include/gpl/Replace.h ++++ b/src/gpl/include/gpl/Replace.h +@@ -56,6 +56,8 @@ struct PlaceOptions + bool skipIoMode = false; + bool forceCenterInitialPlace = false; + bool timingDrivenMode = false; ++ float wirelengthPenalty = 1.0; ++ float timingWeightSpanClockPercent = -1.0; + bool timingDrivenRepairTiming = false; + float timingDrivenRepairTnsEndPercent = 1.0; + bool routabilityDrivenMode = false; +diff --git a/src/gpl/src/nesterovBase.cpp b/src/gpl/src/nesterovBase.cpp +index 3ad4e0e5..70752538 100644 +--- a/src/gpl/src/nesterovBase.cpp ++++ b/src/gpl/src/nesterovBase.cpp +@@ -1160,6 +1160,7 @@ NesterovBaseVars::NesterovBaseVars(const PlaceOptions& options) + binCntY(isSetBinCnt ? options.binGridCntY : 0), + minPhiCoef(options.minPhiCoef), + maxPhiCoef(options.maxPhiCoef), ++ wirelengthPenalty(options.wirelengthPenalty), + initialPlacePerturbationSeed(options.initialPlacePerturbationSeed), + initialPlacePerturbationDist(options.initialPlacePerturbationDist) + { +diff --git a/src/gpl/src/nesterovBase.h b/src/gpl/src/nesterovBase.h +index ebcfebe3..591e605d 100644 +--- a/src/gpl/src/nesterovBase.h ++++ b/src/gpl/src/nesterovBase.h +@@ -785,6 +785,7 @@ struct NesterovBaseVars + + const float minPhiCoef; + float maxPhiCoef; // may be updated after initialization ++ float wirelengthPenalty = 1.0; + const int initialPlacePerturbationSeed; + const float initialPlacePerturbationDist; + +diff --git a/src/gpl/src/replace.cpp b/src/gpl/src/replace.cpp +index de52bcbc..58bf1d55 100644 +--- a/src/gpl/src/replace.cpp ++++ b/src/gpl/src/replace.cpp +@@ -319,6 +319,7 @@ void Replace::doNesterovPlace(const int threads, + tb_->setTimingNetsPercentage(options.timingDrivenNetsPercentage); + tb_->setRepairTiming(options.timingDrivenRepairTiming); + tb_->setRepairTnsEndPercent(options.timingDrivenRepairTnsEndPercent); ++ tb_->setTimingWeightSpanClockPercent(options.timingWeightSpanClockPercent); + } + + if (!cb_ && options.virtualCtsMode) { +diff --git a/src/gpl/src/replace.i b/src/gpl/src/replace.i +index dc3ea329..443302bc 100644 +--- a/src/gpl/src/replace.i ++++ b/src/gpl/src/replace.i +@@ -24,6 +24,8 @@ static gpl::PlaceOptions getOptions( + + gpl::PlaceOptions options; + checkFlag(flags, "-timing_driven", options.timingDrivenMode); ++ checkKey(keys, "-wirelength_penalty", options.wirelengthPenalty); ++ checkKey(keys, "-timing_weight_span_clock_percent", options.timingWeightSpanClockPercent); + checkFlag(flags, "-timing_driven_repair_timing", options.timingDrivenRepairTiming); + checkFlag(flags, "-routability_driven", options.routabilityDrivenMode); + checkFlag(flags, "-virtual_cts", options.virtualCtsMode); +diff --git a/src/gpl/src/replace.tcl b/src/gpl/src/replace.tcl +index def690fa..aee4e101 100644 +--- a/src/gpl/src/replace.tcl ++++ b/src/gpl/src/replace.tcl +@@ -6,6 +6,8 @@ sta::define_cmd_args "global_placement" {\ + [-force_center_initial_place]\ + [-skip_nesterov_place]\ + [-timing_driven]\ ++ [-wirelength_penalty wirelength_penalty]\ ++ [-timing_weight_span_clock_percent timing_weight_span_clock_percent]\ + [-timing_driven_repair_timing]\ + [-routability_driven]\ + [-virtual_cts]\ +@@ -63,6 +65,8 @@ proc global_placement { args } { + -timing_driven_net_weight_max \ + -timing_driven_nets_percentage \ + -timing_driven_repair_tns_end_percent \ ++ -wirelength_penalty \ ++ -timing_weight_span_clock_percent \ + -keep_resize_below_overflow \ + -virtual_cts_max_skew_fraction \ + -random_seed \ +diff --git a/src/gpl/src/timingBase.cpp b/src/gpl/src/timingBase.cpp +index dbbdc437..cff5c65f 100644 +--- a/src/gpl/src/timingBase.cpp ++++ b/src/gpl/src/timingBase.cpp +@@ -180,11 +180,14 @@ bool TimingBase::executeTimingDriven(bool run_journal_restore, + if (slack_max == slack_min) { + gNet->setTimingWeight(1.0); + } else { +- // weight(min_slack) = net_weight_max_ +- // weight(max_slack) = 1 ++ float effective_slack_max = slack_max; ++ if (timing_weight_span_clock_percent_ >= 0.0f) { ++ float clock_period = 1.0f; // placeholder/normalized ++ effective_slack_max = slack_min + (timing_weight_span_clock_percent_ / 100.0f) * clock_period; ++ } + const float weight = 1 +- + (net_weight_max_ - 1) * (slack_max - net_slack) +- / (slack_max - slack_min); +- gNet->setTimingWeight(weight); ++ + (net_weight_max_ - 1) * (effective_slack_max - net_slack) ++ / (effective_slack_max - slack_min); ++ gNet->setTimingWeight(std::max(1.0f, std::min(net_weight_max_, weight))); + } + weighted_net_count++; + } +diff --git a/src/gpl/src/timingBase.h b/src/gpl/src/timingBase.h +index bf406c11..6dc9aa8e 100644 +--- a/src/gpl/src/timingBase.h ++++ b/src/gpl/src/timingBase.h +@@ -51,6 +51,10 @@ class TimingBase + { + repair_tns_end_percent_ = percent / 100.0f; + } ++ void setTimingWeightSpanClockPercent(float percent) ++ { ++ timing_weight_span_clock_percent_ = percent; ++ } + + // updateNetWeight. + // True: successfully reweighted gnets +@@ -74,6 +78,7 @@ class TimingBase + float nets_percentage_ = 10; + bool repair_timing_ = false; + float repair_tns_end_percent_ = 0.01; ++ float timing_weight_span_clock_percent_ = -1.0; + void initTimingOverflowChk(); + }; + +-- diff --git a/tools/OpenROAD b/tools/OpenROAD index ab6fd26351..75f5f61182 160000 --- a/tools/OpenROAD +++ b/tools/OpenROAD @@ -1 +1 @@ -Subproject commit ab6fd26351dc449e69059684dc6aa9ae9046eb36 +Subproject commit 75f5f611829e731fc33988232e39349f3ec1436c