A stage's delay depends on its input slew, which was produced by the previous stage, which depends on its input slew, and so on. The calculation therefore has an order. Knowing it explains why a small change early in a path can change delays all the way to the endpoint, and why results are reproducible when inputs are the same.
# Two-stage chain with a simple illustrative model: delay = d0 + k_load*load + k_slew*slew_in
# slew_out = s0 + m_load*load + m_slew*slew_in
def stage(slew_in, load, d0=0.05, k_load=4.0, k_slew=0.30, s0=0.04, m_load=6.0, m_slew=0.20):
return d0 + k_load*load + k_slew*slew_in, s0 + m_load*load + m_slew*slew_in
slew0 = 0.10
d1, s1 = stage(slew0, load=0.020) # first gate drives 20 fF
d2, s2 = stage(s1, load=0.030) # second gate drives 30 fF, sees the slew produced by the first
assert abs(d1 - 0.160) < 1e-9 and abs(s1 - 0.180) < 1e-9
assert abs(d2 - 0.224) < 1e-9
# a worse first-stage load degrades the slew and therefore the second stage's delay too
d1b, s1b = stage(slew0, load=0.040)
d2b, _ = stage(s1b, load=0.030)
assert d2b > d2
print("PASS: stage 2 delay", round(d2, 3), "->", round(d2b, 3), "when stage 1 load doubles")The second stage is identical in both runs, yet its delay changes (0.224 to a larger value) because its input slew changed: the first stage's load drove its output slew. The effect is the reason sizing and buffering decisions are made on whole paths, not stage by stage.
Results are reproducible: the same netlist, parasitics, library and constraints produce the same numbers; a difference between two runs has an identifiable input change.
Slew and delay limits (maximum transition, capacitance, fanout) are clean, since violations indicate the calculation is outside the characterised range.
Iterative effects (coupling, covered in the crosstalk section) are converged before numbers are trusted.
Its input slew is the first stage's output slew, which got worse when the first stage's load increased. Cell delay depends on input slew, so the same cell is slower.