Slack is usually quoted at endpoints, but it is defined at every node. That generalisation is what lets a tool say which cells and nets lie on a failing path and where along it the margin is lost. It also explains why every node on one critical path shows the same slack.
arrival(n) = max over predecessors p of ( arrival(p) + delay(p -> n) ) (setup, latest)
required(n) = min over successors s of ( required(s) - delay(n -> s) ) (setup, earliest limit)
slack(n) = required(n) - arrival(n)
at an endpoint: required = capture edge arrival - setup - uncertainty# Graph: ck -> a (0.4), a -> b (0.3), a -> c (0.5), b -> d (0.2), c -> d (0.1), d -> end (0.0)
edges = {("ck","a"):0.4, ("a","b"):0.3, ("a","c"):0.5, ("b","d"):0.2, ("c","d"):0.1, ("d","end"):0.0}
order = ["ck","a","b","c","d","end"]
arr = {"ck": 0.0}
for n in order[1:]:
arr[n] = max(arr[p] + w for (p, m), w in edges.items() if m == n)
T_req = 1.5 # required time at the endpoint
req = {"end": T_req}
for n in reversed(order[:-1]):
req[n] = min(req[m] - w for (p, m), w in edges.items() if p == n)
slack = {n: round(req[n] - arr[n], 6) for n in order}
assert abs(arr["end"] - 1.0) < 1e-9 # 0.4 + 0.5 + 0.1
assert abs(slack["end"] - 0.5) < 1e-9
# nodes on the critical path (ck, a, c, d, end) share the endpoint's slack; b has more
assert all(abs(slack[n] - 0.5) < 1e-9 for n in ("ck", "a", "c", "d", "end"))
assert slack["b"] > slack["c"]
print("PASS: slack per node", slack)The critical route is ck-a-c-d-end with total 1.0 ns, so the endpoint slack is 1.5 - 1.0 = 0.5 ns. Every node on that route has slack 0.5 because required and arrival differ by the same amount along it. Node b is off the critical route: its slack is 0.6 ns, 0.1 ns larger, because the route through b (0.9 ns in total) is 0.1 ns faster than the route through c (1.0 ns).
A path report's slack equals the endpoint slack; per-node slack in a graph view identifies where a fix would help.
Fixing a node on the critical path improves the endpoint slack until another route becomes critical (a path wall).
Hold uses the same structure with earliest arrivals and the hold requirement.
Arrival at b is 0.4 + 0.3 = 0.7. The required time at d is 1.5 (the endpoint requirement, with a zero-delay arc), so required(b) = 1.5 - 0.2 = 1.3 and slack(b) = 1.3 - 0.7 = 0.6 ns, larger than the critical route's 0.5 ns.