A scenario is just the active pairing of one mode with one corner โ but the scenario existing in the database says nothing about whether it's actually doing useful work. Setup, hold, or other required analysis can be individually turned off inside a scenario that otherwise looks perfectly configured and named correctly.
Parsing success is the first gate, not the finish line โ an SDC file with zero syntax errors can still leave the design under-constrained or misconstrained. Review the errors and warnings first, then dig into the object-resolution messages specifically โ those tell you whether every `get_pins`/`get_clocks`-style selector actually found real objects, or silently matched nothing.
The hierarchy and instance names you see after synthesis can drift meaningfully from the original design intent โ modules get flattened, renamed, or merged during optimization. When a constraint selector (like a wildcard `get_cells` pattern) was written against the intent-source names, it can silently resolve to the wrong objects โ or to nothing at all โ after that renaming happens.
A wildcard's whole appeal is convenience โ one pattern can select many objects at once โ but that same convenience is exactly what makes it dangerous when the pattern quietly grows past the boundary you actually intended. Before applying a constraint through a wildcard, measure the resulting collection size โ don't just trust that the pattern "looks right."
Two clocks can have the exact same period and still be completely different signals โ the **waveform** (where the rising and falling edges actually sit inside that period) is what determines when your active clock edge really happens. Start with the period: compare what's reported (from `create_clock -period`) against the interface spec for every mode the chip supports โ functional, test, low-power, etc. A clock that's correct in functional mode but wrong in scan mode will pass some checks and silently break others.
A generated clock's SDC declaration is only as trustworthy as the real logic it claims to describe โ everything you report about it (its master, its source pin, its target pin, its divide/multiply factor, its phase, its edge mapping) has to actually match what the netlist implements. Start by confirming the declared master clock is the genuine upstream clock this signal is derived from โ not a plausible-sounding but incorrect clock that happens to share a similar name.
This is fundamentally a connectivity-proof exercise: you're not asking "is timing good?" yet โ you're asking the more basic question "does every flip-flop that's supposed to be clocked actually have a clock signal reaching its CP/CK pin at all?" The standard tool for this is a **no_clock check** โ most STA/PD tools flag any register whose clock pin has no active clock propagating to it, which usually means either a genuine connectivity bug (a broken net, an unintended tie-off) or a legitimate case like a register that's intentionally clocked by a gated/disabled clock in the current mode.
Seeing two clocks land on one register's clock pin isn't automatically a bug โ it can be a completely valid mode-multiplexed design (e.g. a MUX choosing between a functional clock and a test clock). But it can just as easily mean the tool genuinely believes two clocks are simultaneously active at that pin โ which is a real problem, not a modeling quirk.
Every `set_clock_groups` declaration (asynchronous, exclusive, or logically_exclusive/related) directly determines which clock-domain crossings the tool will even attempt to analyze โ get this wrong and you're not analyzing what you think you're analyzing. An "asynchronous" group declaration tells the tool two clocks have no fixed phase relationship, so it won't check timing across that boundary at all โ appropriate for a genuine CDC crossing with proper synchronizers, completely wrong if the two clocks actually do have a real timing relationship in some mode.
Before a clock tree is actually built and routed, there's simply nothing to measure โ no buffers, no real wire delay, no real skew between branches, because CTS hasn't happened yet. Treating the clock as "ideal" (zero or user-specified latency, no built-in skew) isn't a shortcut or a cop-out โ it's an honest reflection of what's actually known at that stage of the flow.
Think of clock latency as having two legs of a journey: **source latency** is the trip *before* the clock even reaches your block โ from the true clock origin (PLL, oscillator, or an off-chip source) to the point where your design's clock port sees it. **Network latency** is the trip *inside* your block โ from that entry point through the clock tree to each flip-flop's clock pin.
Clock uncertainty is a margin, and margins are easy to double-count if you're not disciplined about what each piece is actually covering โ so the review is really about making sure every dollar of margin is spent exactly once. Break it into its real components: **jitter** (cycle-to-cycle timing noise from the clock source/PLL), **modeling/methodology margin** (a deliberate pad added because pre-CTS estimates are inherently imprecise), and the **setup vs. hold split** โ many methodologies apply different uncertainty values for setup checks versus hold checks, since hold margin needs are structurally different.
Clock transition (slew) is just the rise/fall time at a clock pin โ how fast the signal ramps from low to high or high to low โ and it directly drives cell delay calculation for every gate the clock touches. Before CTS exists, there's no real clock tree with real buffers driving real wire capacitance, so any transition value you see pre-CTS is an **assumed input**, typically set via `set_clock_transition`, not something measured from actual routed geometry.
Every timed input port needs both a min and a max input delay โ skipping one isn't "conservative by default," it just leaves that side of the timing check unconstrained. Max input delay models the latest possible external arrival, which drives setup analysis at the internal capture flop; min input delay models the earliest possible arrival, which drives hold analysis.
A clock port isn't ordinary launched data โ it's the time reference that every other path in the design is measured against, so treating it like any other input is a category error, not just a minor constraint mistake. `set_input_delay` says "this signal arrives relative to some clock, with this much delay" โ but applying that to the clock signal itself creates a circular, nonsensical statement: you'd be defining the clock's arrival relative to itself (or another clock) as if it were ordinary data.
`set_output_delay` describes what happens *outside* your block's boundary โ specifically, the receiving device's early and late timing requirements relative to its own reference clock. Min and max values aren't interchangeable defaults โ they represent the receiver's best-case and worst-case requirement, and both need to be explicitly verified, not just one "typical" number.
Both options exist to answer the same question โ "how fast is the signal arriving at this port?" โ but they answer it in fundamentally different ways, and picking the wrong one for your methodology creates inconsistent, hard-to-debug timing. A driving cell (`set_driving_cell`) tells the tool "model the source as if a real library cell of this type is driving this port," which lets the timing engine compute a realistic, load-dependent output slew from that cell's own characterized behavior.
An output load value represents what your block has to drive once its signal leaves the boundary โ the receiver's input capacitance plus whatever the package/board adds on top. A realistic number comes from the actual receiver device and package assumptions, not a rule-of-thumb default carried over from a different interface.
A false path declaration is a powerful tool โ and like any powerful tool, it's easy to misuse as a way to make an inconvenient timing violation disappear rather than to correctly describe a functionally impossible path. Start by checking exact membership: does the `-from`/`-through`/`-to` specification match precisely the path that's actually functionally impossible, or is it broader than intended and quietly swallowing real paths along with it?
A multicycle path is one that's functionally allowed more than one clock cycle to complete โ but `set_multicycle_path` is not a tool for making slow logic pass; it only encodes a cycle relationship that has to be true by design. Start by confirming the functional cycle relationship itself โ how many cycles is this path actually allowed, and why, based on the real protocol (e.g., a configuration register that only changes every N cycles)?
When thousands of registers suddenly report `no_clock`, don't reach for a targeted patch โ treat it as a hard stop and a readiness check on the whole handoff, because a fix at this scale that "just" attaches clocks locally is very likely to be masking a real upstream problem. Systematically test the competing root causes rather than guessing: wrong current mode active in the tool, an empty or misconfigured clock-source selector, a broken generated-clock or gated-clock path somewhere upstream, or a hierarchy mismatch between the SDC's object names and the actual netlist.
The existence of an object named as a generated clock in your database proves only one thing โ that the object was created. It says absolutely nothing about whether it's electrically or logically valid, and treating "it exists" as "it's correct" is exactly how this bug slips through review. A properly formed generated clock has to be derived from another clock through *real* logic โ a divider, a MUX, some genuine circuit path โ its source and master relationship must trace back through actual netlist connectivity, not just a name someone typed into an SDC command.
A low unconstrained-endpoint count sounds like good news, but "unconstrained" and "well-covered" are answering different questions โ an endpoint can have *some* constraint applied and still be poorly covered by real timing analysis. False paths silently remove endpoints from meaningful analysis without making them "unconstrained" โ the constraint exists (`set_false_path`), so the endpoint doesn't show up as missing anything, but it's also not being genuinely checked.
Don't celebrate yet โ a broad false-path declaration removing a lot of violating paths from the report is exactly as likely to mean "we hid a real bug" as "we correctly excluded impossible paths." Clean timing alone doesn't tell you which. The correct response is to treat the improvement as unproven until you've verified exact path membership โ pull up precisely which paths the false-path constraint removed and check each one individually, not just trust the wildcard pattern you used.
`set_case_analysis` pins a control signal to a constant (0 or 1) to model one functional mode โ the validity question is really "does this constant actually match a mode this chip will really operate in?" Trace the constant back to its source object โ is it a real strap pin tied at the board level, a fuse/OTP bit, a scan-mode signal, or something else? Each source type has different confidence: a board-level strap is usually solid; a scan/test-mode signal being case-analyzed in the *functional* SDC is a red flag.
No โ seeing two different clocks arrive at one clock pin isn't automatically a bug. It's exactly what you'd expect at a **clock MUX** (functional clock vs. test clock, or two PLL outputs for different performance modes), where only one input is actually selected at a time in any given operating mode. The tool, though, doesn't know your chip's functional intent by default โ it just sees a pin with two active clock definitions and will report it, sometimes as a DRC-style warning, sometimes just noise in the report.
Not every reported "loop" is a mistake โ some designs genuinely have intentional feedback (a self-enabling latch structure, a deliberate oscillator-like element, certain memory model constructs), so the first job is figuring out which kind you're looking at. Pull the actual netlist path the tool flagged and trace it by hand: does the signal genuinely loop back through purely combinational logic with no register in between (a real, accidental loop โ usually from a synthesis or RTL bug), or does it pass through something the tool is mis-modeling as combinational when it's actually a latch or intentional feedback structure?
Just because an exception (a false path, multicycle path, or case-analysis setting) is sitting in your SDC doesn't mean it's doing anything โ the tool can load it, parse it fine, and still never apply it to a single path. One common reason: it's overridden. SDC exceptions have precedence rules, and a more specific or more recently applied exception on the same path segment can silently take priority over an earlier, broader one.
This is one of the classic SDC traps: declaring a multicycle path fixes the setup check you were chasing, but if you don't also derive the matching hold relationship, hold checking silently goes wrong on the same path. A multicycle path is one that's functionally allowed more than one clock cycle to complete โ but `set_multicycle_path` by default only shifts the setup check; the hold check still assumes the *original* single-cycle relationship unless you explicitly tell it otherwise with the hold-side multicycle specification.
A "disabled arc" just means STA has been told to stop propagating timing through a specific point in a cell or net โ and when that happens on a clock path, an entire downstream clock tree can silently go unanalyzed, which is a lot scarier than a disabled arc on a random data path. The first job isn't to re-enable it blindly โ it's ownership hunting: figure out *who* disabled it and *why*, because the fix is completely different depending on the source.
This is a debugging exercise, and the way you catch it is by comparing what a scenario *actually contains* against what it *should* contain for its declared mode โ a mislabeled scenario usually leaves fingerprints across several categories at once. Start with **scenario-mode association**: does `report_scenarios` or the equivalent actually show test mode pointing at the SDC file/constraints intended for functional mode? This is often the most direct smoking gun โ a filename or mode tag mismatch.
The right question isn't "is one scenario missing" โ it's "does the missing scenario change what we believe about logical or timing readiness." If yes, that missing coverage is a hard stop, full stop, regardless of how much other work seems ready to start. This is fundamentally a readiness check on the coverage matrix (modes x corners x scenarios), not a floorplan-specific problem โ floorplanning just happens to be the stage where the gap becomes consequential.
Functional mode and test mode are not interchangeable checks of the "same thing twice" โ they're genuinely different operating conditions with different clocks, different constants, and different expected behavior, and a clean result in one mode tells you nothing conclusive about the other. Compare clock definitions separately per mode โ test mode very often uses different clock sources, different frequencies, or scan-clock muxing that functional mode never exercises, and vice versa.
Before assuming a real timing bug, rule out the boring explanation first: are both tools even reporting in the same units? A slack reported in "ns" versus "ps" without noticing looks like a 1000x discrepancy that isn't a timing bug at all. Pull each tool's reported units directly โ clock period units, delay units, slew units, capacitive load units โ and compare them side by side rather than assuming they match by convention.
A library mismatch between two tools is one of the sneakiest correlation bugs, because both tools can report plausible-looking numbers while quietly reading different characterization data underneath. First confirm the resolved library *version* each tool actually loaded โ not just the library name, but which release/revision โ since the same library name can point to different `.db`/`.lib` files across environments if search paths differ.
Two tools disagreeing on timing is almost never a "which tool is buggy" question โ it's an "are they analyzing the same thing" question, and clocks are one of the richest places for silent mismatches to hide. Start with clock creation itself: same `create_clock` period/waveform, same source pin, and check any generated clocks (`create_generated_clock`) are derived identically in both environments.
A bulk `set_input_delay [all_inputs]`-style command is a footgun on any design where the clock is also a top-level port, because it accidentally treats the clock pin as ordinary data. The clock port is supposed to be excluded from data-path constraints โ it defines the timing reference itself, it isn't a signal that arrives relative to that reference.
No โ a negative minimum output delay is not automatically a bug; the sign can legitimately follow the receiving interface's own hold convention, so seeing a minus sign shouldn't trigger an automatic "fix" reflex. What actually matters is whether the value was deliberately derived from the interface spec and correctly associated with the right clock and clock edge โ a negative number that's correctly derived is fine; a negative number that just fell out of a copy-pasted constraint template is not.
When a pile of SDC constraints reference objects that synthesis already optimized away, the underlying problem is that the applied SDC and the delivered netlist have drifted out of sync โ and that's a mapping/regeneration problem, not something PD should be quietly patching around. The instinct to write local wildcard fixes ("just widen the pattern match so the constraint applies to *something*") is exactly the wrong move โ it papers over a real handoff defect instead of fixing it.
Signing off a waiver on an undriven/unloaded net or pin is a real engineering decision, not a rubber stamp โ treat it with the same rigor as any other design-affecting approval. Require named-object evidence first: exactly which net, pin, or port is being waived โ never approve a waiver at the level of "there are 40 undriven pins in this category," because that hides individual cases that might not actually be safe.
Default answer: stop โ unless you can point to the specific approved abstraction and confirm the next stage's interface requirements are actually satisfied, and the project has explicitly signed off on accepting that model state. A black box is fine on purpose โ that's the whole point of abstraction, hiding internal logic you don't need to see yet. The problem isn't the black box itself; it's an *unverified* black box.
Zero violations only tells you the paths that were actually analyzed, in the scenarios that were actually active, came back clean โ it says nothing about paths the tool never looked at. A clean report can hide missing clock definitions, exceptions that were never written (so paths are silently over- or under-constrained), tied-off constants that eliminate paths from analysis, inactive modes that weren't checked, or entire absent paths that should exist but don't.
No โ the goal isn't a warning count of zero, it's that every remaining warning has been actually looked at and understood, which is a very different bar. Each warning needs to be sorted into one of two buckets: a real defect that needs fixing, or a justified, intentional structure that happens to trigger the check โ and that sorting decision has to be made explicitly, not assumed.
The dividing line is authority, not difficulty: if the defect lives in functional connectivity, synthesis structure, the library release, interface timing intent, or mode definitions, PD editing it locally is out of scope even if PD technically *could* patch around it. A local PD-side fix to one of these categories risks quietly diverging from the design's actual golden intent โ the netlist or constraint set PD is working from stops matching what synthesis/RTL/architecture actually meant.
This is a broader version of the floorplan gate decision โ it applies whenever a stage handoff (SDC readiness, linking, structure review) needs an accountable "go / no-go" rather than just a pile of passed checks. Proceed only when provenance genuinely matches โ the constraints, netlist, and libraries you're reviewing actually correspond to the version you think they do, not a stale or mismatched set.