Each kind of path is constrained differently. A register-to-register path needs only clocks. An input-to-register path also needs an input delay saying when the outside world presents data. Knowing the four kinds tells you which constraints each endpoint depends on, and which missing constraint explains an unconstrained endpoint.
Path type | Starts at | Ends at | What constrains it |
|---|---|---|---|
Register to register | Flop (or latch) clock pin | Flop data pin | The clock(s) alone |
Input to register | Input port | Flop data pin | Clock plus an input delay relative to a clock |
Register to output | Flop clock pin | Output port | Clock plus an output delay relative to a clock |
Input to output | Input port | Output port | Input and output delays against a (virtual) clock (a combinational feed-through) |
Startpoints: the clock pin of a sequential cell (launching at its active clock edge) and primary input ports. Latch data-to-output arcs also continue paths through a latch, which the time-borrowing lessons cover.
Endpoints: the data pin of a sequential cell (where setup and hold are checked against the capture clock) and primary output ports. Clock gating enable pins and asynchronous reset pins are also checked, with their own rules.
A path is timed only if both its ends are constrained. Combinational logic between an unclocked input and an unconstrained output has no requirement, so STA reports nothing for it.
Tools group paths, typically by the capture clock, so a report can list the worst path of each group. This matters for prioritisation: a fast clock domain can hide behind a slow domain's much larger violation if only the single worst path chip-wide is reported. The path-group lessons in the delay graph section cover the weighting; here the point is that grouping follows the endpoint.
# Classify paths by endpoint kind and report which constraints each depends on.
paths = [
("ff_a/CK", "ff_b/D"), ("in_x", "ff_b/D"), ("ff_a/CK", "out_y"), ("in_x", "out_y"),
]
def kind(p):
s, e = p
s_reg = s.endswith("/CK")
e_reg = e.endswith("/D")
return {(True, True): "reg2reg", (False, True): "in2reg",
(True, False): "reg2out", (False, False): "in2out"}[(s_reg, e_reg)]
needs = {"reg2reg": {"clock"}, "in2reg": {"clock", "input_delay"},
"reg2out": {"clock", "output_delay"}, "in2out": {"clock", "input_delay", "output_delay"}}
kinds = [kind(p) for p in paths]
assert kinds == ["reg2reg", "in2reg", "reg2out", "in2out"]
assert needs["in2out"] == {"clock", "input_delay", "output_delay"}
print("PASS: path kinds", kinds)Find the 60 unconstrained endpoints and explain each: a missing clock, a missing input or output delay, or a deliberate exception. Unconstrained is not the same as passing, and the count must reach zero (or be justified) before signoff.