A driver produces a signal; a load consumes it. An undriven object has no valid source feeding it, and an unloaded output has nowhere meaningful to go. Neither is automatically a bug โ both can be legitimate at a design boundary (an unused output pin, an intentionally tied-off input) or they can be evidence that connectivity got lost somewhere during synthesis or netlist editing.
A **dangling connection** is a hard disconnect: a pin or port that simply isn't wired to anything on one side โ think of an unplugged cable end. It usually shows up as an unconnected pin in `report_design` or a floating net in LVS. A **no-load net** is more subtle: the net has a real driver and is properly connected at the netlist level, but nothing downstream actually consumes the signal in a meaningful way โ like a wire that's plugged in but runs to a dead outlet.
Two active drivers fighting over one ordinary net is a real structural problem, not a style nitpick โ they can demand different logic values on the same wire at the same time. That's exactly why a "multiple driver" message from the tool deserves immediate review rather than being waived through.
A constant value (a pin permanently tied to logic 0 or logic 1) isn't automatically suspicious โ some are completely intentional, like a mode-select strap that's hardwired for this particular chip variant or configuration. But a constant can *also* be evidence that something went wrong upstream โ synthesis optimized away logic it thought was redundant, a clock got accidentally tied off, or an enable signal that should be dynamic got hardcoded because of a missing connection or a constraint bug.
Think of this as taking inventory after a shipment arrives โ you're not re-verifying every connection, you're checking that the counts on the packing slip match what actually showed up. Compare ports, hierarchical cell instances, sequential cell counts, combinational cell counts, memory/macro counts, net counts, and unresolved reference counts against what the synthesis handoff reported.
SDC commands like `create_clock`, `set_input_delay`, or `set_false_path` all target objects by path โ and those paths only exist if they match what ICC2 actually loaded, not what the RTL author originally wrote. Synthesis routinely renames, flattens, or removes hierarchy: a module boundary that existed in RTL might be gone after optimization, or an instance name might have picked up a synthesis-tool suffix.
Syntax just asks: does this command follow the language's grammar? Meaning asks something completely different: does it actually select the clocks, ports, pins, modes, and values you intended? A file can parse cleanly and still be functionally wrong โ for example, a port name that changed during synthesis means the command runs without error but silently selects nothing.
An empty collection means your selector (something like `get_ports`, `get_cells`, `get_pins`) matched zero objects in the current design โ the command didn't error, it just quietly found nothing. This is one of the most dangerous "silent failures" in SDC loading, because a constraint applied to an empty collection isn't an error โ it just does nothing, and your design proceeds as if that constraint never existed.
A primary clock originates at a real design source, typically an input port โ it's not derived from any other clock in the design. Verification means checking its source object and name are correct, its period and waveform are as intended, and that it's visible in the right modes.
A virtual clock is a timing reference that has no physical source anywhere inside your block โ it's not an input port, not a generated clock off internal logic, nothing you can point to on the die. It exists purely to describe timing behavior happening outside the block boundary โ commonly used to model an external device's launch or capture clock when you don't have (and don't need) that device's actual clock network inside your design.
A primary clock is the true starting point of your timing reference โ it begins at a real design source, usually an input port, and its period and waveform define the base time reference that everything downstream gets measured against. A generated clock, by contrast, doesn't originate independently โ it's *derived* from a master clock through actual logic in your design, like a clock divider or a mode-select MUX, and its timing characteristics (period, phase, edges) come from that master-source relationship, not from a fresh, independent definition.
Input and output delays describe time spent *outside* your block โ everything happening before a signal reaches your input pin, or after it leaves your output pin, relative to a reference clock. They are explicitly not the delay of the block's own internal input receiver or output buffer โ a common beginner mistake is thinking `set_output_delay` models the output driver's own speed, when really it models whatever sits downstream (the receiving device's setup/hold requirement plus any board/interconnect delay).
These three settings define the electrical "weather" around a port โ without them, the timing engine has no idea how fast a signal is realistically switching or how much it has to drive, so any delay number it calculates is essentially a guess. The driving cell (or an explicit `set_input_transition`) tells the tool how sharp or sluggish the incoming edge is โ a slow input transition ripples forward and makes every downstream cell look artificially slower too.
`check_timing` is not a timing-quality check (it doesn't report slack) โ it's a setup/coverage health check that runs before you should trust any slack numbers at all. It looks specifically for missing or inconsistent timing intent: clock pins with no clock reaching them ("no-clock" points), output/register endpoints that never got constrained (unconstrained endpoints), timing loops (combinational cycles that break static analysis), and generated-clock definition problems (a derived clock missing its source or divide-by relationship).
Being "ready" here isn't a feeling โ it's a specific checklist: matched provenance (netlist/SDC revisions agree with the handoff record), resolved linking (no unexpected unresolved references), classified structural findings (undriven/unloaded/multi-driver nets reviewed and understood, not just silenced), applied and reviewed SDC, complete clock/I-O intent, confirmed active scenario coverage, and any waivers explicitly owned by someone. Every one of these should produce a concrete, named result โ this clock exists with this period, this reference is resolved to this library cell โ not a generic "looks fine."
Every sanity check depends on facts established by the check before it โ run them out of order and a later "pass" can be meaningless, because it was built on unverified ground. Start with identity and libraries: confirm you're on the right design and the right library set before doing anything else.
Don't hardcode command names or default behaviors into a sanity script โ both can silently change between tool releases, and a script that assumes yesterday's defaults can pass or fail for the wrong reasons. A dependable script asks the installed tool what it actually supports before running: use `get_design_checks` to confirm which check bundles exist in this release.
It's tempting to treat `dp_pre_floorplan` as a green light that says "your design is floorplan-ready" โ it isn't. It's a narrow, specific technology-readiness check, and confusing "passed dp_pre_floorplan" with "ready to floorplan" is a common and costly mistake. Concretely, it checks technology-file information and routing-layer directions โ including confirming that both horizontal and vertical routing layers actually exist in your tech setup.
Don't compare one giant total from synthesis against one giant total from ICC2 and call it a day โ that hides exactly where the discrepancy is coming from. Compare like-for-like scope and object class: registers against registers, combinational logic against combinational logic, memories against memories, macros against macros.
A single "total cell count" number hides a lot โ 50,000 total cells could be almost all combinational logic, or it could include a handful of huge memory macros that dominate area and power while contributing almost nothing to that count. Splitting into sequential, combinational, memory, and macro inventories lets you catch specific problems: a suspiciously low sequential count might mean a missing scan chain or an unlinked clock domain; a memory count that doesn't match the architecture spec might mean a missing or duplicated memory instance.
Not every unconnected pin is a bug โ some are deliberately left open by design intent, a tie-off policy, or a black-box boundary that simply doesn't drive that signal in this configuration. The test isn't "is it open?" โ it's "can I trace this open back to a documented, recorded reason?" An intentional open should have a paper trail: interface documentation, a tie-off strategy, or a synthesis log entry explaining why that pin is unconnected.
A "waiver" to keep a macro as a black box isn't just a verbal agreement โ it needs to produce concrete artifacts that another engineer could pick up and verify independently. At minimum you need: approved pin identity (an agreed, versioned pinlist โ not a draft that might still change), a documented timing-boundary treatment (how the interface is constrained โ as a real .lib, an estimated wireload, or a placeholder budget), and confirmation of which logical/physical views the next stage actually requires (do you need a .lib only, or also a LEF/abstract for placement?).
Trusting an instance's reference-cell text string ("this is a NAND2X4") is not the same as verifying it's actually pointing at the NAND2X4 you think it is โ the same name can resolve differently depending on reference-library search order. Correlate the actual reference-library resolution order used at the time this instance was placed/optimized โ a change in `search_path` or `set_ref_libs` order between two runs can cause the same cell name to bind to a different library version.
Unit mismatches are a sneaky bug class because everything still "runs" โ the tool doesn't error out, it just silently computes with the wrong scale factor, and you only notice when timing numbers look absurdly good or absurdly bad. Start at the source: check `set_units` in the SDC (time, capacitance, resistance, voltage, current, power) against the corresponding unit declarations in each Liberty (.lib) file โ SDC commonly uses nanoseconds while some libraries are characterized in picoseconds, and a 1000x factor slipping through unnoticed is exactly the kind of thing that turns a real violation into apparent huge positive slack (or vice versa).
This is really about knowing the MCMM (multi-mode multi-corner) vocabulary well enough to file each constraint in the right bucket โ get the bucket wrong and you either apply a constraint where it shouldn't count, or fail to apply it where it should. **Modes** capture functional intent โ things like clock definitions, generated-clock relationships, I/O timing, and functional exceptions (false paths, multicycle paths) that describe *what the chip is doing* in this operating state (functional, test, low-power, etc.).