A practical guide to FPGA timing: what setup and hold slack mean, how to write clock and I/O constraints, handling clock domain crossings, reading the timing report, and the fixes that actually close timing.
An FPGA design that runs correctly on one board at one temperature may still be wrong. If the tools were never told how fast the clocks are, or how the outside world delivers data, they place and route for something other than your real requirements, and the design passes by luck. Another build, another board, a hotter day or a slightly slower chip, and it fails in ways that look like random glitches. Timing constraints are how you tell the tools what correct means, and timing closure is the work of making the result meet it with margin.
Every register captures its input on a clock edge, and the input must be stable for a short time before the edge (setup) and after it (hold). The tools check both for every path between registers.
The report summarises these as the worst negative slack (WNS), the total negative slack (TNS) and the number of failing endpoints. The goal is positive slack on every path, in every operating condition the tools model.
The constraint formats used by the main vendors are based on the same SDC command set, with small differences, so the ideas carry over from one toolchain to another.
Define every clock that enters the design, with its period. Outputs of clock-management blocks (PLLs and similar) are usually derived automatically, but check the report to see that they are, and define generated clocks yourself for any divider you build in logic.
The tools cannot see outside the chip. For each interface you state how the data arrives and leaves relative to the clock: the largest and smallest delay between the clock edge and the data at the pin, which come from the external device's datasheet and the board delay.
create_clock -name clk_100 -period 10.000 [get_ports clk_100]
# data from an external device, with its datasheet delays
set_input_delay -clock clk_100 -max 3.0 [get_ports data_in*]
set_input_delay -clock clk_100 -min 0.5 [get_ports data_in*]
# data to an external device
set_output_delay -clock clk_100 -max 2.0 [get_ports data_out*]
set_output_delay -clock clk_100 -min -0.5 [get_ports data_out*]
Without these, the tools treat the pins as unconstrained and report nothing about them, and a design with unconstrained interfaces can look clean in the report while failing in the system.
Signals that pass from one clock to an unrelated one cannot meet ordinary timing, and should not be asked to. They must be made safe by design: a two-flip-flop synchroniser for a single-bit level, a handshake for a multi-bit value, an asynchronous FIFO with Gray-coded pointers for a stream. Then tell the tools the clocks are unrelated, so they stop checking those paths as if they were synchronous:
set_clock_groups -asynchronous \
-group [get_clocks clk_100] \
-group [get_clocks clk_adc]
This is the most abused constraint in FPGA design. A false path or an asynchronous group declared on a path that is in fact synchronous hides a real failure, so each one should have a written reason, and the crossings themselves should be reviewed as a separate task.
A multicycle path tells the tools that data is allowed more than one clock period to arrive, for example when a register updates every fourth cycle. It must match what the design really does, including the enable logic.
Try them in this order, since the earlier ones cost less.
We bring up FPGA designs on Lattice, Microchip PolarFire and Xilinx devices, including the fiber signal communication board where a Lattice FPGA handles a 14-bit, 80 MSPS converter and a 16-bit, 50 MSPS converter. In that kind of design the interface timing is the design. If you want constraints, clock-domain crossings and timing closure handled as part of the whole system, see our FPGA design service, and our guide to choosing an FPGA for an embedded product.
Contact us for customized solutions and quotes