From e65072063cc0f0fad66b85b9306c52db8ae35248 Mon Sep 17 00:00:00 2001 From: xuyipei Date: Tue, 15 Sep 2026 21:26:53 +0800 Subject: [PATCH 1/3] fix(iSTA): use data waveforms for SDF timing checks --- .../source/module/sdf_writer/SDFWriter.cpp | 20 ++++++++++++++++++- .../source/module/sdf_writer/SDFWriter.hpp | 1 + 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/operation/iSTA/source/module/sdf_writer/SDFWriter.cpp b/src/operation/iSTA/source/module/sdf_writer/SDFWriter.cpp index a7b83d4fc0..bd3e95f6f1 100644 --- a/src/operation/iSTA/source/module/sdf_writer/SDFWriter.cpp +++ b/src/operation/iSTA/source/module/sdf_writer/SDFWriter.cpp @@ -750,13 +750,31 @@ double SDFWriter::getSDFTimingCheckDelay(Instance& instance, TimingCheckArc& tim return timing_check_arc.get_check_time(); } std::string data_pin_name = STAUTIL.getString(instance.get_instance_name(), ":", timing_check_arc.get_data_port()); - double data_slew = getSDFSlew(data_pin_name, analysis_type, data_trans_type); + double data_slew = getSDFDataSlew(data_pin_name, analysis_type, data_trans_type); double clock_slew = getSDFTimingCheckSlew(instance, timing_check_arc, analysis_type, data_trans_type); double delay = timing_arc.get_check_table_map()[data_trans_type].findValue(clock_slew * timing_arc.get_time_unit_scale(), data_slew * timing_arc.get_time_unit_scale()); return delay / timing_arc.get_time_unit_scale(); } +double SDFWriter::getSDFDataSlew(std::string& pin_name, AnalysisType analysis_type, TransType trans_type) +{ + Database& database = STADM.getDatabase(); + if (database.get_timing_point_map().count(pin_name) == 0) { + return 0.0; + } + TimingPoint& timing_point = database.get_timing_point_map()[pin_name]; + if (timing_point.get_data_slew_map().count(analysis_type) > 0 && timing_point.get_data_slew_map()[analysis_type].count(trans_type) > 0) { + return timing_point.get_data_slew_map()[analysis_type][trans_type]; + } + // A clock can also drive a data pin. Its ideal reference slew must not + // replace the physical waveform used on the data side of a timing check. + if (timing_point.get_physical_clock_slew_map().count(analysis_type) > 0 && timing_point.get_physical_clock_slew_map()[analysis_type].count(trans_type) > 0) { + return timing_point.get_physical_clock_slew_map()[analysis_type][trans_type]; + } + return 0.0; +} + double SDFWriter::getSDFTimingCheckSlew(Instance& instance, TimingCheckArc& timing_check_arc, AnalysisType analysis_type, TransType data_trans_type) { if (timing_check_arc.get_check_type() == TimingCheckType::kWidth || timing_check_arc.get_check_type() == TimingCheckType::kPeriod) { diff --git a/src/operation/iSTA/source/module/sdf_writer/SDFWriter.hpp b/src/operation/iSTA/source/module/sdf_writer/SDFWriter.hpp index a5552de706..c0103ae450 100644 --- a/src/operation/iSTA/source/module/sdf_writer/SDFWriter.hpp +++ b/src/operation/iSTA/source/module/sdf_writer/SDFWriter.hpp @@ -99,6 +99,7 @@ class SDFWriter bool hasSDFDelay(SDFDelay& sdf_delay); double getSDFTimingCheckDelay(Instance& instance, TimingCheckArc& timing_check_arc, TimingArc& timing_arc, AnalysisType analysis_type, TransType data_trans_type); + double getSDFDataSlew(std::string& pin_name, AnalysisType analysis_type, TransType trans_type); double getSDFTimingCheckSlew(Instance& instance, TimingCheckArc& timing_check_arc, AnalysisType analysis_type, TransType data_trans_type); double getSDFSlew(std::string& pin_name, AnalysisType analysis_type, TransType trans_type); AnalysisType getCaptureAnalysisType(AnalysisType analysis_type); From 52130669223695f344c8472ec6dab3d0a310068a Mon Sep 17 00:00:00 2001 From: xuyipei Date: Tue, 15 Sep 2026 21:33:39 +0800 Subject: [PATCH 2/3] fix(iSTA): evaluate input-port RC waveforms --- .../delay_calculator/DelayCalculator.cpp | 195 +++++++++++++----- .../delay_calculator/DelayCalculator.hpp | 11 +- 2 files changed, 151 insertions(+), 55 deletions(-) diff --git a/src/operation/iSTA/source/module/delay_calculator/DelayCalculator.cpp b/src/operation/iSTA/source/module/delay_calculator/DelayCalculator.cpp index 99a7ad0ce8..a9cb261ae9 100644 --- a/src/operation/iSTA/source/module/delay_calculator/DelayCalculator.cpp +++ b/src/operation/iSTA/source/module/delay_calculator/DelayCalculator.cpp @@ -16,6 +16,7 @@ // *************************************************************************************** #include "DelayCalculator.hpp" +#include #include #include @@ -115,6 +116,7 @@ void DelayCalculator::clearParasiticCache() _parasitic_arnoldi_model_cache.clear(); _parasitic_arnoldi_timing_result_cache.clear(); _parasitic_arnoldi_driver_result_cache.clear(); + _parasitic_input_port_result_cache.clear(); } void DelayCalculator::initializeArcTiming(DCTask& dc_task) @@ -819,7 +821,8 @@ double DelayCalculator::calcParasiticDelay(Arc& arc, AnalysisType analysis_type, if (cached_wire_delay) { return *cached_wire_delay; } - std::optional input_port_delay = calcParasiticArnoldiInputPortDelay(parasitic_net, source_node_name, sink_node_name, analysis_type, trans_type); + std::optional input_port_delay + = calcParasiticArnoldiInputPortDelay(parasitic_net, source_node_name, sink_node_name, analysis_type, trans_type, input_slew); if (input_port_delay) { return *input_port_delay; } @@ -2644,45 +2647,153 @@ void DelayCalculator::updateParasiticArnoldiProjection(ParasiticArnoldiModel& ar } } -double DelayCalculator::calcParasiticArnoldiElmore(ParasiticArnoldiModel& arnoldi_model, std::string& sink_node_name) +std::optional DelayCalculator::calcParasiticArnoldiInputPortDelay(ParasiticNet& parasitic_net, std::string& source_node_name, + std::string& sink_node_name, AnalysisType analysis_type, TransType trans_type, + double input_slew) { - if (arnoldi_model.get_term_index_map().count(sink_node_name) == 0) { - return 0.0; + Database& database = STADM.getDatabase(); + std::string source_pin_name = getPinNameByParasiticNodeName(source_node_name); + if (!database.get_pin_map().contains(source_pin_name) || !database.get_pin_map().at(source_pin_name).get_is_port()) { + return std::nullopt; } - return calcParasiticArnoldiElmore(arnoldi_model, arnoldi_model.get_term_index_map()[sink_node_name]); + ParasiticArnoldiTimingResult& result = getParasiticInputPortResult(parasitic_net, source_node_name, analysis_type, trans_type, input_slew); + if (!result.get_is_valid() || !result.get_wire_delay_map().contains(sink_node_name)) { + return std::nullopt; + } + return result.get_wire_delay_map().at(sink_node_name); } -double DelayCalculator::calcParasiticArnoldiElmore(ParasiticArnoldiModel& arnoldi_model, std::size_t term_idx) +ParasiticArnoldiTimingResult& DelayCalculator::getParasiticInputPortResult(ParasiticNet& parasitic_net, std::string& source_node_name, + AnalysisType analysis_type, TransType trans_type, double input_slew) { - if (!arnoldi_model.get_is_valid() || arnoldi_model.get_order() <= 0 || arnoldi_model.get_diagonal_list().empty()) { - return 0.0; - } - if (arnoldi_model.get_order() == 1 || arnoldi_model.get_projection_list().size() < 2 || arnoldi_model.get_off_diagonal_list().empty()) { - return arnoldi_model.get_diagonal_list().front(); + double slew = std::isfinite(input_slew) ? std::abs(input_slew) : 0.0; + ParasiticArnoldiDriverResultKey key{source_node_name, analysis_type, trans_type, slew}; + if (!_parasitic_input_port_result_cache.contains(key)) { + _parasitic_input_port_result_cache[key] = calcParasiticInputPortResult(parasitic_net, source_node_name, analysis_type, trans_type, slew); } - if (term_idx >= arnoldi_model.get_projection_list().front().size() || term_idx >= arnoldi_model.get_projection_list()[1].size() - || std::abs(arnoldi_model.get_projection_list().front().front()) < STA_ERROR) { - return arnoldi_model.get_diagonal_list().front(); - } - return arnoldi_model.get_diagonal_list().front() - + arnoldi_model.get_off_diagonal_list().front() * arnoldi_model.get_projection_list()[1][term_idx] - / arnoldi_model.get_projection_list().front().front(); + return _parasitic_input_port_result_cache.at(key); } -std::optional DelayCalculator::calcParasiticArnoldiInputPortDelay(ParasiticNet& parasitic_net, std::string& source_node_name, - std::string& sink_node_name, AnalysisType analysis_type, TransType trans_type) +ParasiticArnoldiTimingResult DelayCalculator::calcParasiticInputPortResult(ParasiticNet& parasitic_net, std::string& source_node_name, + AnalysisType analysis_type, TransType trans_type, double input_slew) { + ParasiticArnoldiTimingResult result; Database& database = STADM.getDatabase(); std::string source_pin_name = getPinNameByParasiticNodeName(source_node_name); - if (database.get_pin_map().count(source_pin_name) == 0 || !database.get_pin_map()[source_pin_name].get_is_port()) { - return std::nullopt; - } - ParasiticArnoldiModel& arnoldi_model = getParasiticArnoldiModel(parasitic_net, source_node_name, analysis_type, trans_type); - if (!arnoldi_model.get_is_valid() || arnoldi_model.get_term_index_map().count(sink_node_name) == 0) { - return std::nullopt; + if (!database.get_pin_map().contains(source_pin_name) || !database.get_pin_map().at(source_pin_name).get_is_port()) { + return result; + } + ParasiticArnoldiModel& model = getParasiticArnoldiModel(parasitic_net, source_node_name, analysis_type, trans_type); + if (!model.get_is_valid() || model.get_order() <= 0) { + return result; + } + // The port imposes a voltage waveform. Use zero source resistance and the + // reduced RC response, retaining the fast modes lost by an Elmore estimate. + int32_t order = model.get_order(); + Eigen::MatrixXd matrix = Eigen::MatrixXd::Zero(order, order); + for (int32_t index = 0; index < order; ++index) { + matrix(index, index) = model.get_diagonal_list()[index]; + if (index + 1 < order) { + matrix(index, index + 1) = matrix(index + 1, index) = model.get_off_diagonal_list()[index]; + } + } + Eigen::SelfAdjointEigenSolver solver(matrix); + if (solver.info() != Eigen::Success || !solver.eigenvalues().allFinite()) { + return result; + } + std::vector time_constants(order); + double scale = solver.eigenvalues().cwiseAbs().maxCoeff(); + for (int32_t index = 0; index < order; ++index) { + double value = solver.eigenvalues()[index]; + if (value < -scale * 1E-12) { + return result; + } + time_constants[index] = value > scale * 1E-12 ? value : 0.0; + } + double derate = 1.0; + double lower = 0.1; + double upper = 0.9; + double voltage_log = 0.0; + double min_slew_factor = 0.0; + double x1 = 0.0; + double y1 = 0.0; + calcParasiticArnoldiThreshold(trans_type, derate, lower, upper, voltage_log, min_slew_factor, x1, y1); + TimingLibrary& library = database.get_timing_library(); + bool is_fall = trans_type == TransType::kFall; + double source_threshold = getNormalizedThreshold(is_fall ? library.get_output_threshold_pct_fall() : library.get_output_threshold_pct_rise()); + double ramp = input_slew * derate / (upper - lower); + double source_time = (is_fall ? 1.0 - source_threshold : source_threshold) * ramp; + for (std::size_t term_idx = 0; term_idx < model.get_term_node_list().size(); ++term_idx) { + std::vector weights(order, 0.0); + for (int32_t index = 0; index < order; ++index) { + double projection = 0.0; + for (int32_t basis = 0; basis < order; ++basis) { + projection += solver.eigenvectors()(basis, index) * model.get_projection_list()[basis][term_idx]; + } + weights[index] = model.get_sqrt_total_capacitance() * solver.eigenvectors()(0, index) * projection; + } + std::string& node = model.get_term_node_list()[term_idx]; + std::string pin_name = getPinNameByParasiticNodeName(node); + TimingCell* load_cell = getThresholdTimingCell(pin_name); + double load_threshold = getNormalizedThreshold(is_fall ? library.get_input_threshold_pct_fall() : library.get_input_threshold_pct_rise()); + double load_lower = lower; + double load_upper = upper; + double load_derate = derate; + if (load_cell != nullptr) { + load_threshold = getNormalizedThreshold(getTimingCellInputThreshold(*load_cell, trans_type)); + load_lower = getNormalizedThreshold(getTimingCellSlewLowerThreshold(*load_cell, trans_type)); + load_upper = getNormalizedThreshold(getTimingCellSlewUpperThreshold(*load_cell, trans_type)); + load_derate = load_cell->get_slew_derate_from_library(); + } + if (!(load_threshold > 0.0 && load_threshold < 1.0 && load_lower > 0.0 && load_upper < 1.0 && load_upper > load_lower && load_derate > 0.0 + && source_threshold > 0.0 && source_threshold < 1.0)) { + return ParasiticArnoldiTimingResult(); + } + double middle_time = calcParasiticInputPortCrossing(time_constants, weights, ramp, is_fall ? load_threshold : 1.0 - load_threshold); + double early_time = calcParasiticInputPortCrossing(time_constants, weights, ramp, is_fall ? load_upper : 1.0 - load_lower); + double late_time = calcParasiticInputPortCrossing(time_constants, weights, ramp, is_fall ? load_lower : 1.0 - load_upper); + if (!std::isfinite(middle_time) || !std::isfinite(early_time) || !std::isfinite(late_time) || late_time < early_time) { + return ParasiticArnoldiTimingResult(); + } + result.get_wire_delay_map()[node] = term_idx == 0 ? 0.0 : middle_time - source_time; + result.get_load_slew_map()[node] = term_idx == 0 ? input_slew : (late_time - early_time) / load_derate; + result.get_wire_delay_map()[pin_name] = result.get_wire_delay_map()[node]; + result.get_load_slew_map()[pin_name] = result.get_load_slew_map()[node]; + } + result.set_is_valid(true); + return result; +} + +double DelayCalculator::calcParasiticInputPortCrossing(std::vector& time_constants, std::vector& weights, double ramp, double voltage) +{ + // Integrate each exponential step response over the source ramp. Remaining + // voltage is the falling waveform, or one minus the rising waveform. + double low = 0.0; + double high = ramp + 64.0 * *std::max_element(time_constants.begin(), time_constants.end()); + for (int32_t iteration = 0; iteration < 64; ++iteration) { + double time = 0.5 * (low + high); + double remaining = 0.0; + for (std::size_t index = 0; index < time_constants.size(); ++index) { + double tau = time_constants[index]; + double response = 0.0; + if (tau == 0.0) { + response = ramp > 0.0 ? std::max(0.0, 1.0 - time / ramp) : 0.0; + } else if (ramp == 0.0) { + response = std::exp(-time / tau); + } else if (time < ramp) { + response = 1.0 - time / ramp - tau / ramp * std::expm1(-time / tau); + } else { + response = -tau / ramp * std::expm1(-ramp / tau) * std::exp(-(time - ramp) / tau); + } + remaining += weights[index] * response; + } + if (remaining > voltage) { + low = time; + } else { + high = time; + } } - double elmore = calcParasiticArnoldiElmore(arnoldi_model, sink_node_name); - return std::log(2.0) * elmore; + return 0.5 * (low + high); } std::optional DelayCalculator::calcParasiticArnoldiInputPortSlew(ParasiticNet& parasitic_net, std::string& source_node_name, @@ -2691,33 +2802,15 @@ std::optional DelayCalculator::calcParasiticArnoldiInputPortSlew(Parasit { Database& database = STADM.getDatabase(); std::string source_pin_name = getPinNameByParasiticNodeName(source_node_name); - if (database.get_pin_map().count(source_pin_name) == 0 || !database.get_pin_map()[source_pin_name].get_is_port()) { + if (!database.get_pin_map().contains(source_pin_name) || !database.get_pin_map().at(source_pin_name).get_is_port()) { return std::nullopt; } - ParasiticArnoldiModel& arnoldi_model = getParasiticArnoldiModel(parasitic_net, source_node_name, analysis_type, trans_type); - if (!arnoldi_model.get_is_valid() || arnoldi_model.get_term_index_map().count(sink_node_name) == 0) { + ParasiticArnoldiTimingResult& result = getParasiticInputPortResult(parasitic_net, source_node_name, analysis_type, trans_type, input_slew); + if (!result.get_is_valid() || !result.get_load_slew_map().contains(sink_node_name)) { return std::nullopt; } - double elmore = calcParasiticArnoldiElmore(arnoldi_model, sink_node_name); - double abs_input_slew = std::abs(input_slew); - double output_slew = abs_input_slew + getParasiticArnoldiSlewScale(trans_type) * elmore; - if (input_slew < 0.0) { - return -output_slew; - } - return output_slew; -} - -double DelayCalculator::getParasiticArnoldiSlewScale(TransType trans_type) -{ - double slew_derate = 1.0; - double lower_threshold = 0.1; - double upper_threshold = 0.9; - double voltage_log = 0.0; - double min_slew_factor = 0.0; - double x1 = 0.0; - double y1 = 0.0; - calcParasiticArnoldiThreshold(trans_type, slew_derate, lower_threshold, upper_threshold, voltage_log, min_slew_factor, x1, y1); - return voltage_log / slew_derate; + double output_slew = result.get_load_slew_map().at(sink_node_name); + return input_slew < 0.0 ? -output_slew : output_slew; } ParasiticArnoldiPoleResidue DelayCalculator::calcParasiticArnoldiPoleResidue(ParasiticArnoldiModel& arnoldi_model, double drive_resistance) diff --git a/src/operation/iSTA/source/module/delay_calculator/DelayCalculator.hpp b/src/operation/iSTA/source/module/delay_calculator/DelayCalculator.hpp index db81618008..76e4489a45 100644 --- a/src/operation/iSTA/source/module/delay_calculator/DelayCalculator.hpp +++ b/src/operation/iSTA/source/module/delay_calculator/DelayCalculator.hpp @@ -49,6 +49,7 @@ class DelayCalculator std::map _parasitic_arnoldi_model_cache; std::map _parasitic_arnoldi_timing_result_cache; std::map _parasitic_arnoldi_driver_result_cache; + std::map _parasitic_input_port_result_cache; static constexpr int32_t kStartTimeIndex = 0; static constexpr int32_t kTransitionTimeIndex = 1; static constexpr int32_t kEffectiveCapacitanceIndex = 2; @@ -275,13 +276,15 @@ class DelayCalculator std::vector& term_point_idx_list); void updateParasiticArnoldiProjection(ParasiticArnoldiModel& arnoldi_model, std::vector& basis_list, std::vector& term_point_idx_list, std::size_t order_idx); - double calcParasiticArnoldiElmore(ParasiticArnoldiModel& arnoldi_model, std::string& sink_node_name); - double calcParasiticArnoldiElmore(ParasiticArnoldiModel& arnoldi_model, std::size_t term_idx); std::optional calcParasiticArnoldiInputPortDelay(ParasiticNet& parasitic_net, std::string& source_node_name, std::string& sink_node_name, - AnalysisType analysis_type, TransType trans_type); + AnalysisType analysis_type, TransType trans_type, double input_slew); + ParasiticArnoldiTimingResult& getParasiticInputPortResult(ParasiticNet& parasitic_net, std::string& source_node_name, AnalysisType analysis_type, + TransType trans_type, double input_slew); + ParasiticArnoldiTimingResult calcParasiticInputPortResult(ParasiticNet& parasitic_net, std::string& source_node_name, AnalysisType analysis_type, + TransType trans_type, double input_slew); + double calcParasiticInputPortCrossing(std::vector& time_constants, std::vector& weights, double ramp, double voltage); std::optional calcParasiticArnoldiInputPortSlew(ParasiticNet& parasitic_net, std::string& source_node_name, std::string& sink_node_name, AnalysisType analysis_type, TransType trans_type, double input_slew); - double getParasiticArnoldiSlewScale(TransType trans_type); ParasiticArnoldiPoleResidue calcParasiticArnoldiPoleResidue(ParasiticArnoldiModel& arnoldi_model, double drive_resistance); bool solveParasiticArnoldiTridiagonalEigen(std::vector& diagonal_list, std::vector& off_diagonal_list, std::vector& eigenvalue_list, std::vector>& eigenvector_list); From 5218b4efe953189455815224d1e0b5b7eae24011 Mon Sep 17 00:00:00 2001 From: xuyipei Date: Wed, 16 Sep 2026 11:58:56 +0800 Subject: [PATCH 3/3] feat(iSTA): add opt-in minimum slew approximation --- .../python/py_ista/py_ista_utils.cpp | 15 ++++++++ .../tcl/tcl_ista/src/tcl_init_sta.cpp | 18 ++++++++- src/operation/iSTA/README.md | 33 +++++++++++++++++ src/operation/iSTA/interface/STAInterface.cpp | 9 +++++ .../iSTA/source/data_manager/DataManager.cpp | 2 + .../source/data_manager/advance/Config.hpp | 1 + .../delay_calculator/DelayCalculator.cpp | 37 +++++++++++++++++++ .../delay_calculator/DelayCalculator.hpp | 2 + .../dc_data_manager/ParasiticArnoldiModel.hpp | 3 ++ 9 files changed, 119 insertions(+), 1 deletion(-) diff --git a/src/interface/python/py_ista/py_ista_utils.cpp b/src/interface/python/py_ista/py_ista_utils.cpp index efdb4510a7..6d10f285ca 100644 --- a/src/interface/python/py_ista/py_ista_utils.cpp +++ b/src/interface/python/py_ista/py_ista_utils.cpp @@ -16,6 +16,7 @@ // *************************************************************************************** #include +#include #include #include "json_parser.h" @@ -53,6 +54,13 @@ bool initStaConfigMapByJSON(const std::string& config, std::map& config_dict, std if (config_dict.count("-timing_path_limit") > 0 && !config_dict["-timing_path_limit"].empty()) { config_map["-timing_path_limit"] = std::stoi(config_dict["-timing_path_limit"]); } + if (config_dict.count("-min_slew_degradation") > 0 && !config_dict["-min_slew_degradation"].empty()) { + const std::string& value = config_dict.at("-min_slew_degradation"); + if (value != "0" && value != "1") { + throw std::invalid_argument("-min_slew_degradation must be 0 or 1"); + } + config_map["-min_slew_degradation"] = int32_t(value == "1"); + } if (config_dict.count("-timing_corner") > 0 && !config_dict["-timing_corner"].empty()) { config_map["-timing_corner"] = config_dict["-timing_corner"]; } diff --git a/src/interface/tcl/tcl_ista/src/tcl_init_sta.cpp b/src/interface/tcl/tcl_ista/src/tcl_init_sta.cpp index e65c9e71ff..b80538c979 100644 --- a/src/interface/tcl/tcl_ista/src/tcl_init_sta.cpp +++ b/src/interface/tcl/tcl_ista/src/tcl_init_sta.cpp @@ -34,6 +34,8 @@ TclInitSTA::TclInitSTA(const char* cmd_name) : TclCmd(cmd_name) _config_list.push_back(std::make_pair("-output_timing_features", ValueType::kInt)); // int32_t timing_path_limit; // optional _config_list.push_back(std::make_pair("-timing_path_limit", ValueType::kInt)); + // int32_t min_slew_degradation; // optional + _config_list.push_back(std::make_pair("-min_slew_degradation", ValueType::kString)); // std::string timing_corner; // optional _config_list.push_back(std::make_pair("-timing_corner", ValueType::kString)); // int32_t max_paths; // optional @@ -56,7 +58,21 @@ unsigned TclInitSTA::exec() return 0; } std::map config_map = TclUtil::getConfigMap(this, _config_list); - STAI.initSTA(config_map); + if (config_map.contains("-min_slew_degradation")) { + std::string value = std::any_cast(config_map.at("-min_slew_degradation")); + if (value != "0" && value != "1") { + Tcl_SetObjResult(ecc::ScriptEngine::getOrCreateInstance()->get_interp(), + Tcl_NewStringObj("-min_slew_degradation must be 0 or 1", -1)); + return 0; + } + config_map["-min_slew_degradation"] = int32_t(value == "1"); + } + try { + STAI.initSTA(config_map); + } catch (const std::exception& error) { + Tcl_SetObjResult(ecc::ScriptEngine::getOrCreateInstance()->get_interp(), Tcl_NewStringObj(error.what(), -1)); + return 0; + } return 1; } diff --git a/src/operation/iSTA/README.md b/src/operation/iSTA/README.md index 1a7884d03e..72678ebcc4 100644 --- a/src/operation/iSTA/README.md +++ b/src/operation/iSTA/README.md @@ -20,3 +20,36 @@ src/operation/iSTA/ Keep command and external API boundaries in `interface/`. Keep shared iSTA state in `source/data_manager/`. Add concrete algorithm stages under `source/module//` only when the stage design is known. + +## Minimum slew during RC propagation + +`init_sta -min_slew_degradation 1` (the default) propagates the calculated +receiver waveform slew. The same integer option is accepted by the C++ config +map and the Python dictionary/JSON bindings. Values other than `0` or `1` are +rejected. As with other Python configuration fields, an empty dictionary/JSON +string leaves the option unspecified. Configure it before starting a run; +changing the internal config after calculation requires reinitializing the +delay calculator and its caches. + +`-min_slew_degradation 0` enables an experimental early-mode approximation for +cell-driven RC trees: when the moment-reduced pi resistance exceeds `0.45` times +the Arnoldi driver resistance, a receiver with matching slew thresholds and +slew derate uses the driver slew. Receivers with different slew conventions +retain their calculated waveform, including the existing library conversion. +The compatibility check applies to each load and transition; equivalent +fractional and percentage thresholds are accepted across library names. +The resistance decision uses the analysis corner and output transition. +RC wire delay and its threshold correction still use the calculated waveform. +Maximum analysis, input-port waveforms, resistance loops, and the DMP fallback +retain their previous behavior. + +This approximation was evaluated against controlled PrimeTime minimum-slew +runs. Restricting it to compatible slew conventions avoids applying the +approximation to unvalidated library conversions, which previously worsened +some SRAM receiver delays. This is a conservative scope limit, not a +requirement of RC physics. The fitted Arnoldi resistance is a heuristic, +not an exact physical resistance derived from the delay/load slope. +Neither the ratio nor this scope limit should be treated as PrimeTime's +complete algorithm. Some paths improve and others regress, so the option +remains experimental. It does not resolve all driver-model or SDF coverage +differences. diff --git a/src/operation/iSTA/interface/STAInterface.cpp b/src/operation/iSTA/interface/STAInterface.cpp index 29788acb61..c4232d5b23 100644 --- a/src/operation/iSTA/interface/STAInterface.cpp +++ b/src/operation/iSTA/interface/STAInterface.cpp @@ -16,6 +16,8 @@ // *************************************************************************************** #include "STAInterface.hpp" +#include + #ifdef __GLIBC__ #include #endif @@ -70,6 +72,12 @@ void STAInterface::destroyInst() void STAInterface::initSTA(std::map config_map) { + if (config_map.contains("-min_slew_degradation")) { + int32_t value = std::any_cast(config_map.at("-min_slew_degradation")); + if (value != 0 && value != 1) { + throw std::invalid_argument("-min_slew_degradation must be 0 or 1"); + } + } Logger::initInst(); // clang-format off STALOG.info(Loc::current(), ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); @@ -257,6 +265,7 @@ void STAInterface::wrapConfig(std::map& config_map) STADM.getConfig().output_timing_reports = STAUTIL.getConfigValue(config_map, "-output_timing_reports", 1); STADM.getConfig().output_timing_features = STAUTIL.getConfigValue(config_map, "-output_timing_features", 1); STADM.getConfig().timing_path_limit = STAUTIL.getConfigValue(config_map, "-timing_path_limit", 20); + STADM.getConfig().min_slew_degradation = STAUTIL.getConfigValue(config_map, "-min_slew_degradation", 1); STADM.getConfig().timing_corner = STAUTIL.getConfigValue(config_map, "-timing_corner", ""); STADM.getConfig().is_path_report_number_specified = STAUTIL.exist(config_map, std::string("-max_paths")) || STAUTIL.exist(config_map, std::string("-max_path")) diff --git a/src/operation/iSTA/source/data_manager/DataManager.cpp b/src/operation/iSTA/source/data_manager/DataManager.cpp index 656280b6d1..62eec18e5d 100644 --- a/src/operation/iSTA/source/data_manager/DataManager.cpp +++ b/src/operation/iSTA/source/data_manager/DataManager.cpp @@ -351,6 +351,8 @@ void DataManager::printConfig() STALOG.info(Loc::current(), STAUTIL.getSpaceByTabNum(2), _config.output_timing_reports); STALOG.info(Loc::current(), STAUTIL.getSpaceByTabNum(1), "output_timing_features"); STALOG.info(Loc::current(), STAUTIL.getSpaceByTabNum(2), _config.output_timing_features); + STALOG.info(Loc::current(), STAUTIL.getSpaceByTabNum(1), "min_slew_degradation"); + STALOG.info(Loc::current(), STAUTIL.getSpaceByTabNum(2), _config.min_slew_degradation); STALOG.info(Loc::current(), STAUTIL.getSpaceByTabNum(1), "timing_path_limit"); STALOG.info(Loc::current(), STAUTIL.getSpaceByTabNum(2), _config.timing_path_limit); // ********** STA ********** // diff --git a/src/operation/iSTA/source/data_manager/advance/Config.hpp b/src/operation/iSTA/source/data_manager/advance/Config.hpp index 4c2391cee7..3e57f00b0c 100644 --- a/src/operation/iSTA/source/data_manager/advance/Config.hpp +++ b/src/operation/iSTA/source/data_manager/advance/Config.hpp @@ -34,6 +34,7 @@ class Config int32_t output_timing_reports = 1; int32_t output_timing_features = 1; int32_t timing_path_limit = 20; + int32_t min_slew_degradation = 1; std::string timing_corner; bool is_path_report_number_specified = false; bool has_timing_report_slack_lesser_than = false; diff --git a/src/operation/iSTA/source/module/delay_calculator/DelayCalculator.cpp b/src/operation/iSTA/source/module/delay_calculator/DelayCalculator.cpp index a9cb261ae9..20012e0710 100644 --- a/src/operation/iSTA/source/module/delay_calculator/DelayCalculator.cpp +++ b/src/operation/iSTA/source/module/delay_calculator/DelayCalculator.cpp @@ -2208,6 +2208,8 @@ ParasiticArnoldiTimingResult DelayCalculator::calcParasiticArnoldiTimingResult(s timing_result.set_effective_capacitance(effective_capacitance); timing_result.set_gate_delay(delay_list.front()); timing_result.set_driver_slew(slew_list.front()); + bool suppress_min_slew = STADM.getConfig().min_slew_degradation == 0 && analysis_type == AnalysisType::kMin + && arnoldi_model.get_pi_resistance() > kMinSlewResistanceThreshold * drive_resistance; for (std::size_t term_idx = 0; term_idx < arnoldi_model.get_term_node_list().size(); term_idx++) { std::string& term_node_name = arnoldi_model.get_term_node_list()[term_idx]; double wire_delay = delay_list[term_idx] - delay_list.front(); @@ -2215,6 +2217,13 @@ ParasiticArnoldiTimingResult DelayCalculator::calcParasiticArnoldiTimingResult(s std::string pin_name = getPinNameByParasiticNodeName(term_node_name); if (term_idx > 0) { adjustParasiticLoadThreshold(timing_arc, pin_name, output_trans_type, wire_delay, load_slew); + if (suppress_min_slew && isParasiticLoadSlewCompatible(timing_arc, pin_name, output_trans_type)) { + // Keep the delay threshold correction from the physical load waveform. + // Convert the replacement slew to the receiver library independently. + load_slew = slew_list.front(); + double unused_wire_delay = 0.0; + adjustParasiticLoadThreshold(timing_arc, pin_name, output_trans_type, unused_wire_delay, load_slew); + } } timing_result.get_wire_delay_map()[term_node_name] = wire_delay; timing_result.get_load_slew_map()[term_node_name] = load_slew; @@ -2225,6 +2234,26 @@ ParasiticArnoldiTimingResult DelayCalculator::calcParasiticArnoldiTimingResult(s return timing_result; } +bool DelayCalculator::isParasiticLoadSlewCompatible(TimingArc& timing_arc, std::string& load_pin, TransType trans_type) +{ + // The optional resistance-ratio approximation is limited to matching slew + // conventions. Preserve the receiver waveform when a library conversion is + // needed; converting the driver slew alone can discard significant RC slew. + TimingCell* load_timing_cell = getThresholdTimingCell(load_pin); + if (load_timing_cell == nullptr) { + return false; + } + double driver_lower_threshold + = trans_type == TransType::kFall ? timing_arc.get_slew_lower_threshold_pct_fall() : timing_arc.get_slew_lower_threshold_pct_rise(); + double driver_upper_threshold + = trans_type == TransType::kFall ? timing_arc.get_slew_upper_threshold_pct_fall() : timing_arc.get_slew_upper_threshold_pct_rise(); + double load_lower_threshold = getTimingCellSlewLowerThreshold(*load_timing_cell, trans_type); + double load_upper_threshold = getTimingCellSlewUpperThreshold(*load_timing_cell, trans_type); + return std::abs(getNormalizedThreshold(driver_lower_threshold) - getNormalizedThreshold(load_lower_threshold)) < STA_ERROR + && std::abs(getNormalizedThreshold(driver_upper_threshold) - getNormalizedThreshold(load_upper_threshold)) < STA_ERROR + && std::abs(timing_arc.get_slew_derate() - load_timing_cell->get_slew_derate_from_library()) < STA_ERROR; +} + void DelayCalculator::adjustParasiticLoadThreshold(TimingArc& timing_arc, std::string& load_pin, TransType trans_type, double& wire_delay, double& load_slew) { TimingCell* load_timing_cell = getThresholdTimingCell(load_pin); @@ -2521,6 +2550,14 @@ void DelayCalculator::updateParasiticArnoldiModel(ParasiticArnoldiModel& arnoldi } bool has_resistance_loop = network_resistance_num + 1 > node_num; + // The moment recurrence is valid for trees; loops retain waveform slew. + if (!has_resistance_loop && STADM.getConfig().min_slew_degradation == 0) { + ParasiticDmpModel pi_model; + buildParasiticDmpPiModel(pi_model, parent_idx_list, resistance_list, capacitance_list); + if (pi_model.get_is_valid()) { + arnoldi_model.set_pi_resistance(pi_model.get_pi_resistance()); + } + } Eigen::SparseMatrix conductance_matrix; Eigen::ConjugateGradient, Eigen::Lower | Eigen::Upper, Eigen::IncompleteCholesky> conductance_solver; if (has_resistance_loop) { diff --git a/src/operation/iSTA/source/module/delay_calculator/DelayCalculator.hpp b/src/operation/iSTA/source/module/delay_calculator/DelayCalculator.hpp index 76e4489a45..cfcd10a60d 100644 --- a/src/operation/iSTA/source/module/delay_calculator/DelayCalculator.hpp +++ b/src/operation/iSTA/source/module/delay_calculator/DelayCalculator.hpp @@ -62,6 +62,7 @@ class DelayCalculator static constexpr double kDriverParameterTolerance = 0.01; static constexpr double kThresholdTimeTolerance = 0.01; static constexpr double kTinyNumber = 1E-20; + static constexpr double kMinSlewResistanceThreshold = 0.45; static constexpr double kGateResistanceCapacitanceStep = 1E-3; static constexpr double kCapacitiveDriverResistance = 1E-5; TimingArc* _timing_arc = nullptr; @@ -251,6 +252,7 @@ class DelayCalculator TransType output_trans_type, double input_slew); ParasiticArnoldiTimingResult calcParasiticArnoldiTimingResult(std::string& output_pin, TimingArc& timing_arc, AnalysisType analysis_type, TransType output_trans_type, double input_slew, double output_load); + bool isParasiticLoadSlewCompatible(TimingArc& timing_arc, std::string& load_pin, TransType trans_type); void adjustParasiticLoadThreshold(TimingArc& timing_arc, std::string& load_pin, TransType trans_type, double& wire_delay, double& load_slew); TimingCell* getThresholdTimingCell(std::string& pin_name); double getTimingCellSlewLowerThreshold(TimingCell& timing_cell, TransType trans_type); diff --git a/src/operation/iSTA/source/module/delay_calculator/dc_data_manager/ParasiticArnoldiModel.hpp b/src/operation/iSTA/source/module/delay_calculator/dc_data_manager/ParasiticArnoldiModel.hpp index fb8462608b..9ede76a1a2 100644 --- a/src/operation/iSTA/source/module/delay_calculator/dc_data_manager/ParasiticArnoldiModel.hpp +++ b/src/operation/iSTA/source/module/delay_calculator/dc_data_manager/ParasiticArnoldiModel.hpp @@ -35,6 +35,7 @@ class ParasiticArnoldiModel bool get_is_valid() const { return _is_valid; } int32_t get_order() const { return _order; } double get_total_capacitance() const { return _total_capacitance; } + double get_pi_resistance() const { return _pi_resistance; } double get_sqrt_total_capacitance() const { return _sqrt_total_capacitance; } std::vector& get_term_node_list() { return _term_node_list; } std::vector& get_term_pin_list() { return _term_pin_list; } @@ -45,6 +46,7 @@ class ParasiticArnoldiModel // setter void set_is_valid(const bool is_valid) { _is_valid = is_valid; } void set_order(const int32_t order) { _order = order; } + void set_pi_resistance(const double pi_resistance) { _pi_resistance = pi_resistance; } void set_total_capacitance(const double total_capacitance) { _total_capacitance = total_capacitance; } void set_sqrt_total_capacitance(const double sqrt_total_capacitance) { _sqrt_total_capacitance = sqrt_total_capacitance; } void set_term_node_list(const std::vector& term_node_list) { _term_node_list = term_node_list; } @@ -59,6 +61,7 @@ class ParasiticArnoldiModel bool _is_valid = false; int32_t _order = 0; double _total_capacitance = 0.0; + double _pi_resistance = 0.0; double _sqrt_total_capacitance = 0.0; std::vector _term_node_list; std::vector _term_pin_list;