XDC and SDC constraints cheat sheet
The timing and pin constraints you write every week in Vivado XDC and Quartus SDC, with Lattice equivalents, worked patterns and a table of common mistakes.
This sheet is for engineers who know what a constraint is for and need the exact syntax. Vivado reads XDC, which is SDC plus set_property. Quartus Prime reads SDC for timing and a separate QSF file for pins. Lattice Radiant reads SDC-like timing in a .pdc file, Diamond uses .lpf. Read the vendor manuals linked at the end for the full option lists.
Clocks
| Task | XDC (Vivado) | SDC (Quartus Prime) |
|---|---|---|
| Primary clock on a pin | create_clock -period 10.000 -name clk_in [get_ports clk_in] |
create_clock -period 10.0 -name clk_in [get_ports clk_in] |
| Clock with a 25 % duty cycle | add -waveform {0 2.5} |
same |
| Clocks from a PLL or MMCM | Derived automatically | derive_pll_clocks (Standard) or automatic (Pro) |
| Manual generated clock | create_generated_clock -name clk_div2 -source [get_pins u_div/clk] -divide_by 2 [get_pins u_div/q_reg/Q] |
same syntax |
| Clock on a recovered or external source | create_clock -period 8.000 [get_pins u_gt/RXOUTCLK] |
same |
| Report what the tool found | report_clocks |
report_clocks |
| Jitter and uncertainty | set_clock_uncertainty -setup 0.100 [get_clocks clk_in] |
derive_clock_uncertainty (do this once, always) |
# Vivado: a 100 MHz board clock and a clock-enable style divided clock
create_clock -period 10.000 -name sys_clk [get_ports sys_clk_p]
create_generated_clock -name clk_div4 -source [get_pins u_div/clk_i] \
-divide_by 4 [get_pins u_div/div_reg[1]/Q]
Define a generated clock only for clocks made with logic (a toggling flip-flop). Clocks that come out of a PLL, MMCM or clock buffer are derived by the tool. Prefer clock enables over divided clocks in the first place.
I/O delays
External delays are specified relative to a clock. The tool then checks the path from pin to the first register (input) or from the last register to the pin (output).
| Task | XDC and SDC (same syntax) |
|---|---|
| Input, system-synchronous | set_input_delay -clock sys_clk -max 2.5 [get_ports data_in[*]] and -min 0.5 |
| Input, DDR, both edges | add -clock_fall -add_delay for the falling-edge set |
| Output | set_output_delay -clock sys_clk -max 1.0 [get_ports data_out[*]] and -min -0.5 |
| Source-synchronous input | create_clock on the forwarded clock pin, then set_input_delay -clock fwd_clk |
| Source-synchronous output | create_generated_clock on the forwarded clock port, then set_output_delay -clock fwd_clk_out |
# Source-synchronous ADC interface, data centred on the forwarded clock
create_clock -period 8.000 -name adc_clk [get_ports adc_clk]
set_input_delay -clock adc_clk -max 1.200 [get_ports adc_d[*]]
set_input_delay -clock adc_clk -min 0.300 [get_ports adc_d[*]]
set_input_delay -clock adc_clk -max 1.200 -clock_fall -add_delay [get_ports adc_d[*]]
set_input_delay -clock adc_clk -min 0.300 -clock_fall -add_delay [get_ports adc_d[*]]
The -max value is the latest the data can arrive after the clock edge (setup side) and -min is the earliest it can change (hold side). For outputs, -max is the external setup requirement and -min is minus the external hold requirement. See /reference/timing-formulae/ for the arithmetic.
Exceptions
| Task | Command | Use when |
|---|---|---|
| Unrelated clock domains | set_clock_groups -asynchronous -group [get_clocks clk_a] -group [get_clocks clk_b] |
Both domains cross through proper synchronisers |
| One path, ignore timing | set_false_path -from [get_cells u_a/cfg_reg*] -to [get_cells u_b/sync_reg[0]] |
Static configuration bits sampled long after they settle |
| Reset release | set_false_path -from [get_ports rst_n] |
Asynchronous input going into a reset synchroniser |
| Slow path, N cycles | set_multicycle_path -setup 2 -from [get_cells a_reg*] -to [get_cells b_reg*] plus set_multicycle_path -hold 1 ... |
Data is only used every N cycles because of a clock enable |
| Bound a crossing | set_max_delay -datapath_only 5.000 -from [get_cells src_reg*] -to [get_cells dst_reg*] (Vivado) |
Bus crossing where skew between bits matters |
| Bus skew | set_bus_skew -from [get_cells gray_reg*] -to [get_cells sync_reg*] 2.000 (Vivado 2018.1 and later) |
Gray pointer crossings in async FIFOs |
# Vivado: a two-cycle path driven by a clock enable
set_multicycle_path -setup 2 -from [get_cells u_dsp/acc_reg*] -to [get_cells u_dsp/out_reg*]
set_multicycle_path -hold 1 -from [get_cells u_dsp/acc_reg*] -to [get_cells u_dsp/out_reg*]
Quartus Prime accepts set_max_delay without -datapath_only. The clock skew is still counted, so pair it with set_false_path -hold or use set_net_delay for the same effect. In Quartus, hold multicycle values are counted differently from Vivado: -hold 1 here means “end minus one”, which usually matches what you want after -setup 2.
Pins and I/O standards
| Task | Vivado XDC | Quartus QSF |
|---|---|---|
| Pin location | set_property PACKAGE_PIN W5 [get_ports clk] |
set_location_assignment PIN_AF14 -to clk |
| I/O standard | set_property IOSTANDARD LVCMOS33 [get_ports clk] |
set_instance_assignment -name IO_STANDARD "3.3-V LVCMOS" -to clk |
| Both at once | set_property -dict { PACKAGE_PIN W5 IOSTANDARD LVCMOS33 } [get_ports clk] |
two lines |
| Differential pair | IOSTANDARD LVDS_25 on the _p port; the _n pin follows |
assign both pins, standard LVDS |
| Pull-up | set_property PULLUP true [get_ports btn] |
set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to btn |
| Drive and slew | set_property DRIVE 8 ... and set_property SLEW FAST ... |
-name CURRENT_STRENGTH_NEW 8MA and -name SLEW_RATE 1 |
| Unused pins | set_property BITSTREAM.CONFIG.UNUSEDPIN PULLDOWN [current_design] |
set_global_assignment -name RESERVE_ALL_UNUSED_PINS_WEAK_PULLUP "AS INPUT TRI-STATED WITH WEAK PULL-UP" |
| Configuration voltage | set_property CFGBVS VCCO [current_design] and CONFIG_VOLTAGE 3.3 |
not needed |
# Vivado: a bus of eight LEDs, one line per pin
set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports { led[0] }]
set_property -dict { PACKAGE_PIN E19 IOSTANDARD LVCMOS33 } [get_ports { led[1] }]
Lattice equivalents
| Task | Radiant PDC | Diamond LPF |
|---|---|---|
| Clock | create_clock -period 12.0 -name clk [get_ports clk] |
FREQUENCY PORT "clk" 83.33 MHz ; |
| Pin | ldc_set_location -site {A9} [get_ports clk] |
LOCATE COMP "clk" SITE "A9" ; |
| I/O type | ldc_set_port -iobuf {IO_TYPE=LVCMOS33} [get_ports clk] |
IOBUF PORT "clk" IO_TYPE=LVCMOS33 ; |
| False path | set_false_path -from [get_clocks a] -to [get_clocks b] |
BLOCK PATH FROM CLKNET "a" TO CLKNET "b" ; |
| Multicycle | set_multicycle_path 2 -from ... |
MULTICYCLE FROM CELL "a*" TO CELL "b*" 2 X ; |
The open-source flow uses a .pcf file for iCE40 (set_io led A5) and .lpf for ECP5 with nextpnr, with clock targets given on the command line or via set_frequency in the pre-pack script.
Mistakes and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| No timing paths reported, everything passes | No create_clock, so nothing is constrained |
Add clocks first, then run check_timing (Vivado) or look at the “unconstrained paths” report |
| Thousands of failing paths between two clocks you consider unrelated | Missing set_clock_groups |
Declare the groups, after confirming every crossing has a synchroniser |
| Hold violations on I/O after a clean setup run | Only -max delay given |
Always give -min too, including negative values for outputs |
| Setup failure on a path that only matters every other cycle | Missing multicycle | Add -setup N and -hold N-1 together, scoped by cells not by clocks |
| Timing passes but the board is flaky | I/O delays guessed, not taken from the datasheet | Rebuild the budget from the datasheet numbers and the board trace lengths |
| Constraint silently ignored | Object name wrong after synthesis renamed it | Use get_cells -hierarchical -filter, check with report_property, and keep constraints after the netlist exists |
| Generated clock reported as unconstrained | -source points to the wrong pin |
Point -source at the clock input pin of the dividing register, and the target at its output |
Vivado warns that set_max_delay has no effect |
Path already covered by a false path or clock group | Remove the broader exception; the most specific exception wins only within a type |
| Pin assignment fails with a bank voltage conflict | Mixed I/O standards in one bank | Group standards by bank; check VCCO on the schematic |
| Quartus reports “no paths” for an SDC exception | Pro uses full hierarchical names, Standard accepts wildcards differently | Use get_registers with full paths in Pro |
Sources
- UG903: Vivado Design Suite User Guide, Using Constraints
- UG949: UltraFast Design Methodology Guide for FPGAs and SoCs
- UG912: Vivado Design Suite Properties Reference
- Quartus Prime Pro Edition User Guide: Timing Analyzer (document 683243)
- Lattice Radiant Software documentation
- nextpnr constraints documentation