Hardware Engineering · FPGAs

Timing Constraints: Clock Definitions, Groups, and Exceptions

How a constraint file tells the timing engine what the clocks are and which paths matter, and why an over-generous false path is a correctness bug rather than an optimization.

The timing engine knows only what the constraint file tells it. Synthesis and implementation have no notion of which clock is fast, which input pins belong to the same interface, or which paths are structurally impossible. Without constraints the tool either analyses every path against an assumed period or analyses almost nothing, and the resulting report is silent in a way that looks like success. Constraints are therefore the specification of intent, and a design is only as good as the intent they encode.

Define the clock before anything else

A clock constraint names a source and states its period and waveform. It applies through the clock network to every register the network reaches, so one declaration covers thousands of registers. Nothing else in the file is meaningful until this exists: setup and hold analysis both derive their budgets from the declared period, and a register whose clock is never declared has no requirement at all.

# Primary input at 100 MHz: period, and 50 percent duty cycle.
create_clock -period 10.000 -waveform {0.000 5.000} [get_ports sys_clk_p]

# Clock produced inside the device from the same pin, 4x the source rate.
create_generated_clock -name clk_core -source [get_ports sys_clk_p] \
    -multiply_by 4 [get_pins pll_inst/CLKOUT0]

# Input and output delays are relative to a declared clock.
set_input_delay  -clock sys_clk_p -max 2.0 [get_ports {data_in[*]}]
set_output_delay -clock sys_clk_p -min 0.5 [get_ports {data_out[*]}]

Generated clocks are where most incomplete files begin to drift. A clock multiplied or divided by a phase-locked loop, or produced by a divider in the fabric, must be tied to its source. If it is declared as a new, independent clock, the tool treats every path between it and the primary clock as a crossing between unrelated domains: either the paths are analysed with an unattainably large budget, or they are excluded as asynchronous. Neither matches the hardware, where the two clocks have an exact, known phase relationship.

The same care applies to clocks that enter through ordinary pins and are routed through the fabric. They must be declared with their actual source latency and any board-level delay that shifts the edge relative to the device’s reference.

What the input and output delays actually mean

The term invites confusion because it sounds like a property of the FPGA. It is not. set_input_delay describes the external device: how long after the reference clock edge the data arrives at the pin. The timing engine then subtracts the device’s internal delay from the pin to the capturing register and checks whether the result fits inside the period. The external number is a property of the board and the other chip, and the FPGA has no way to discover it.

The consequence is that the external numbers must be derived from the interface specification, not chosen. A source-synchronous interface with a forwarded clock, a double-data-rate memory bus, and a serial link each have a different derivation, and each uses a different reference. Writing a plausible number into the file and never revisiting it is how an interface that works on one board fails on another, because the board’s trace length and the partner device’s output timing are the quantity being constrained and they differ between boards.

Output delays work symmetrically: set_output_delay states the setup and hold requirement of the receiving device, relative to the clock edge, and the tool checks that the FPGA’s own output timing meets it. For source-synchronous outputs, the forwarded clock’s relationship to the data is itself the design’s responsibility, and the constraint must describe the relationship the design produces, not the one that would be convenient.

Clock groups and asynchronous boundaries

Two clocks with no defined phase relationship should not be analysed against each other. Declaring them asynchronous is not an optimization; it is a statement that the crossing is handled structurally, by a synchronizer or by a protocol that tolerates delay, and that the timing engine’s edge-to-edge arithmetic would produce a number without meaning.

The declaration is usually set_clock_groups -asynchronous with the two clock sets listed. Structurally derived clocks that share a source but pass through a phase-locked loop at unrelated configurations are a common case: the two outputs may be related at their sources and unrelated at the registers if the loop is reconfigured at runtime. The constraint should describe the worst case that the design tolerates, and if the design relies on a relationship at runtime, the constraint must describe that relationship instead.

# Two independent reference oscillators: no phase relationship exists.
set_clock_groups -asynchronous \
    -group [get_clocks -include_generated_clocks clk_sys] \
    -group [get_clocks -include_generated_clocks clk_aux]

Clock groups have a wide effect: every path between the two sets is unconstrained. If the enumeration is wrong, for example because a clock was accidentally included in both or a generated clock was omitted, paths that matter lose their requirement without any warning. Reviewing the clock group declarations alongside the list of clocks the tool actually knows about is the only way to catch this.

Exceptions: false paths and multicycle paths

An exception removes or changes a path’s requirement. A false path declares that the path cannot be sensitized: the combination of conditions required to propagate a transition through it never occurs. A multicycle path declares that the path legitimately has more than one period to propagate, usually because both endpoints are enabled only on alternate cycles or because the data is captured by a slower enable.

Multicycle declarations are the more delicate of the two because they change the arithmetic in both directions. A path declared as two cycles gets an extra period of setup budget, which is correct for a path whose destination register only samples every second cycle. The same declaration, applied to a path that does not have that property, produces a design that fails at the second cycle’s edge while the report claims a comfortable margin. The hold check is affected as well: relaxing setup for a multicycle path tightens hold, because the capturing edge that now matters is later and the data must not be overwritten in between.

The safest construction is to declare multicycle paths from the register’s perspective using the -setup and -hold options together, and to make the RTL structure that justifies the claim explicit, so that the relationship between the enable logic and the exception is visible in the source rather than only in the constraint file.

False paths are worse, because a wrong false path removes a check rather than adjusting it. A path declared false that is in fact sensitizable has no timing requirement at all, and the design can fail in hardware at any frequency while the report is clean. The failure is intermittent and depends on data, which makes it expensive to diagnose.

The legitimate uses are narrow: paths through a register that is permanently disabled after configuration, paths through logic that only operates while a clock is stopped, and paths through vendor primitives whose internal structure makes them impossible. Structural claims of the form “this path can never be taken” are usually better expressed by removing the logic or by adding a synchronizer, because the logic change is visible in the netlist and the constraint is not.

Constraint completeness

The timing engine’s summary report includes counts of unconstrained paths, paths with no clock, and endpoints without an input or output delay. These counts are the audit trail, and a clean set of them is a precondition for trusting any slack number. Practical review proceeds in a fixed order: every clock source declared and generated clocks tied to their parents; every input and output port covered by a delay; every crossing between unrelated clocks either declared asynchronous or handled by a synchronizer described in clock domain crossing; and every exception documented with the structural reason it holds.

Constraints also interact with implementation, not just analysis. The tool uses timing requirements to drive placement and routing; a path with no requirement gets no effort, and a path with an impossible requirement distorts the placement of everything near it. An over-constrained file is not merely pessimistic, it is a source of worse results, because the placer spends its optimization budget chasing something unreachable and spreads logic to do so. The placement problem that this feeds into is covered in placement, routing, and congestion, and the arithmetic the constraints feed is in static timing analysis.

One further property distinguishes FPGA constraints from their ASIC counterparts: the constraint file is also consumed by the clock network insertion. Buffers and routing for a clock are chosen based on the declared clock and its fanout, so a clock declared at the wrong frequency or with the wrong description of its network can lead to a suboptimal clock distribution decision, in addition to wrong analysis. The declaration is an input to the implementation, not merely to the report.

References

  1. Vivado Design Suite User Guide: Using Constraints (UG903) — Xilinx / AMD
  2. Quartus Prime Standard Edition User Guide: Timing Analyzer — Intel
  3. Synopsys Design Constraints (SDC) — Synopsys