Skip to content

dbSta: Adds iteration free delay model to STA#10975

Open
QuantamHD wants to merge 1 commit into
The-OpenROAD-Project:masterfrom
QuantamHD:new_delay_model
Open

dbSta: Adds iteration free delay model to STA#10975
QuantamHD wants to merge 1 commit into
The-OpenROAD-Project:masterfrom
QuantamHD:new_delay_model

Conversation

@QuantamHD

@QuantamHD QuantamHD commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

This new delay model removes all iteration from DmpCeffTwoPole series of delay models. This is accomplished via two different methods.

This commit replaces the computationally expensive Newton-Raphson iteration loop in the DMPCeff algorithm with a fully feed-forward, O(1) rational function (Padé approximant).

The new technique pre-characterizes the Pi-model shielding factor (k) offline and evaluates it at runtime using a 18-parameter polynomial optimized for FMA instructions.

To ensure the surrogate model is universal (PDK-agnostic) and strictly respects physical boundaries at extreme asymptotes, the Pi-model physicals are mapped into a dimensionless 3D parameter space:

  • x = R_pi / R_d : The resistance ratio. Dictates the primary shielding effect. Squaring this term in the denominator guarantees complete shielding (k=0) as wire resistance approaches infinity.
  • y = C_2 / (C_1 + C_2) : The capacitance ratio. Represents the far-end fraction of the total load, strictly bounded between 0.0 and 1.0.
  • z = t_r / (R_d * C_tot) : The normalized slew ratio. Maps the driver's unshielded transition time against the intrinsic RC time constant of the network.

The surrogate model evaluates the shielding factor as k = f(x,y,z), allowing effective capacitance to be resolved instantly via: C_eff = C_1 + k * C_2

The resulting C_eff is subsequently passed to a Boost Lambert W (W0) analytical solver to compute the exact timing threshold crossings without any runtime iteration.

Type of Change

  • New feature

Impact

OpenSTA Delay Calculator API Total Runtime (150,000 calls) Avg Latency / Call Cumulative Delay Sum
DmpCeffTwoPoleDelayCalc::gateDelay (OpenSTA internal NR loop) 69.41 ms ~462.7 ns 1.458 µs
DmpCeffLambertWDelayCalc::gateDelay (Iteration-free Padé surrogate) 19.34 ms ~128.9 ns 1.458 µs

Verification

  • [ x ] I have verified that the local build succeeds (./etc/Build.sh).
  • [ x ] I have run the relevant tests and they pass.
  • [ x ] My code follows the repository's formatting guidelines.
  • [ x ] I have included tests to prevent regressions.
  • [ x ] I have signed my commits (DCO).

This new delay model removes all iteration from DmpCeffTwoPole
series of delay models. This is accomplished via two different
methods.

This commit replaces the computationally expensive Newton-Raphson
iteration loop in the DMPCeff algorithm with a fully feed-forward, O(1)
rational function (Padé approximant).

The legacy iterative approach suffers from branch mispredictions and
cache misses, bottlenecking physical synthesis inner loops. The new
technique pre-characterizes the Pi-model shielding factor (k) offline
and evaluates it at runtime using a 12-parameter polynomial optimized
for FMA instructions.

To ensure the surrogate model is universal (PDK-agnostic) and strictly
respects physical boundaries at extreme asymptotes, the Pi-model
physicals are mapped into a dimensionless 3D parameter space:

* x = R_pi / R_d : The resistance ratio. Dictates the primary shielding
  effect. Squaring this term in the denominator guarantees complete
  shielding (k=0) as wire resistance approaches infinity.
* y = C_2 / (C_1 + C_2) : The capacitance ratio. Represents the far-end
  fraction of the total load, strictly bounded between 0.0 and 1.0.
* z = t_r / (R_d * C_tot) : The normalized slew ratio. Maps the driver's
  unshielded transition time against the intrinsic RC time constant of
  the network.

The surrogate model evaluates the shielding factor as k = f(x,y,z),
allowing effective capacitance to be resolved instantly via:
C_eff = C_1 + k * C_2

The resulting C_eff is subsequently passed to a Boost Lambert W (W0)
analytical solver to compute the exact timing threshold crossings without
any runtime iteration.

Signed-off-by: Ethan Mahintorabi <ethanmoon@google.com>
@QuantamHD
QuantamHD requested review from a team as code owners July 23, 2026 01:56

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new delay calculator, DmpCeffLambertWDelayCalc, which utilizes a Lambert W function solver and a bivariate Padé approximation for Ceff shielding calculations. It also adds a training data generator, a Python fitting script, and corresponding unit tests. The review feedback highlights two critical issues in DmpCeffLambertWDelayCalc.cc: first, the real parts of the poles returned by poleResidue must be negated to prevent exponential overflow and NaN values in the mathematical derivations; second, adding 1e-15 to a single-precision float capacitance value will underflow, causing a division-by-zero error when calculating rd.

Comment on lines +207 to +210
double p1 = pole1.real();
double k1 = residue1.real();
double p2 = pole2.real();
double k2 = residue2.real();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

In stable RC networks, the real parts of the poles returned by poleResidue are negative (i.e., pole1.real() < 0 and pole2.real() < 0). However, the mathematical derivations and formulas implemented in loadDelaySlew and loadDelay (such as std::exp(-p1 * tt) in line 225, arg_vth in line 230, and std::exp(p1 * tt) - 1.0 in line 276) strictly assume that p1 and p2 are positive values representing the absolute magnitudes of the poles.

If p1 and p2 are negative:

  • std::exp(-p1 * tt) will grow exponentially and overflow.
  • std::exp(p1 * tt) - 1.0 will be negative, causing std::log(arg) to return NaN.

To fix this, define p1 and p2 as positive by negating the real parts of the poles. This also aligns the runtime behavior with the unit tests (which generated positive poles).

Suggested change
double p1 = pole1.real();
double k1 = residue1.real();
double p2 = pole2.real();
double k2 = residue2.real();
double p1 = -pole1.real();
double k1 = residue1.real();
double p2 = -pole2.real();
double k2 = residue2.real();

Comment on lines +43 to +49
float cap1 = c1 + c2;
float cap2 = cap1 + 1e-15;
float d1, d2, s1, s2;
gate_model->gateDelay(pvt, in_slew, cap1, d1, s1);
gate_model->gateDelay(pvt, in_slew, cap2, d2, s2);
double vth = cell->libertyLibrary()->outputThreshold(rf);
float rd = -std::log(vth) * std::abs(d1 - d2) / (cap2 - cap1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Adding 1e-15 to cap1 in single-precision float (which has only 24 bits of mantissa, ~7 decimal digits of precision) will underflow to 0.0 for typical capacitance values (e.g., 1.0 to 100.0 fF or pF). This results in cap2 being exactly equal to cap1, leading to cap2 - cap1 == 0.0f and causing a division by zero (producing NaN) when calculating rd.

To prevent this, use a relative delta or a safe minimum absolute delta that is representable in float relative to cap1 (e.g., cap1 * 1e-4f or 1e-4f).

Suggested change
float cap1 = c1 + c2;
float cap2 = cap1 + 1e-15;
float d1, d2, s1, s2;
gate_model->gateDelay(pvt, in_slew, cap1, d1, s1);
gate_model->gateDelay(pvt, in_slew, cap2, d2, s2);
double vth = cell->libertyLibrary()->outputThreshold(rf);
float rd = -std::log(vth) * std::abs(d1 - d2) / (cap2 - cap1);
float cap1 = c1 + c2;
float delta = std::max(cap1 * 1e-4f, 1e-4f);
float cap2 = cap1 + delta;
float d1, d2, s1, s2;
gate_model->gateDelay(pvt, in_slew, cap1, d1, s1);
gate_model->gateDelay(pvt, in_slew, cap2, d2, s2);
double vth = cell->libertyLibrary()->outputThreshold(rf);
float rd = -std::log(vth) * std::abs(d1 - d2) / delta;

@maliberty
maliberty requested a review from dsengupta0628 July 24, 2026 13:31
Comment thread MODULE.bazel
pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
pip.parse(
extra_pip_args = [
"--index-url=https://pypi.org/simple",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this just to make the default explicit?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Google uses an internal proxy by default that doesn't seem to be compatible with this bazel module because it requires authentication. This ensures that it uses the correct upstream url which is the default. The other option would be to turn off the isolated setting which would let it pick up the authentication tokens required connected to the google default url

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants