Writing XDC Clock Constraints for Vivado
This guide explains how to properly constrain a digital design with multiple clocks in an XDC (Xilinx Design Constraints) file, specifically for use in Vivado batch mode. We’ll cover primary clocks, generated clocks, and the relationships between them.
The Foundation: Primary Clocks
A primary clock is a clock that enters the FPGA from an external source. It’s the root of a clock tree. You must define all primary clocks first.
Differential Clock Input:
For an external differential clock, you only need to constrain one of the pins (usually the positive ‘P’ side). The create_clock
command defines its period, waveform, and attaches it to a port on your design.
-
Command:
create_clock -period <period_in_ns> -name <clock_name> [get_ports <port_name>]
-
Example: If you have a 100 MHz differential clock coming in on ports
sys_clk_p
andsys_clk_n
:# Create a 100 MHz (10.0 ns period) primary clock create_clock -period 10.0 -name sys_clk [get_ports sys_clk_p]
It is not necessary to do the same for
sys_clk_n
, since the differential pairs are hardwired in the device. -
-period 10.0
: The clock period in nanoseconds (1/100MHz = 10ns). -
-name sys_clk
: A logical name for this clock. This is the name you will use to refer to this clock in other constraints. -
[get_ports sys_clk_p]
: Associates this clock definition with the physical input port sys_clk_p.
Internally Generated Clocks
FPGAs commonly use clock management resources like MMCMs (Mixed-Mode Clock Manager) or PLLs (Phase-Locked Loop) to generate new clocks from a primary clock. These are called generated clocks.
You should not use create_clock
for these. Instead, use create_generated_clock
. Vivado’s timing engine needs to understand the phase and frequency relationship between the primary clock and the generated clocks. The create_generated_clock command does this automatically by tracing the clock path from a source.
-
Command:
create_generated_clock -name <new_clock_name> \ -source <source_pin_or_port> \ -divide_by <factor> | -multiply_by <factor> \ <pin_of_generated_clock>
-
Best Practice: The Vivado tool is smart. If you use a clocking wizard to generate an MMCM or PLL, it will usually create an XDC file that automatically defines the generated clocks for you. You can often just include this file. However, it’s crucial to understand how it’s done.
-
Manual Example: Let’s say you have an MMCM instance named
mmcm_inst
that takessys_clk
(100 MHz) and generatesclk_200
(200 MHz) andclk_50_shifted
(50 MHz with a 90-degree phase shift).# Generated clock 1: 200 MHz on CLKOUT0 output of the MMCM create_generated_clock -name clk_200 \ -source [get_pins mmcm_inst/CLKIN1] \ -multiply_by 2 [get_pins mmcm_inst/CLKOUT0] # Generated clock 2: 50 MHz on CLKOUT1 output of the MMCM create_generated_clock -name clk_50 \ -source [get_pins mmcm_inst/CLKIN1] \ -divide_by 2 [get_pins mmcm_inst/CLKOUT1]
-
-name
: Logical name for the new generated clock. -
-source
: The input pin of the clock-modifying block (e.g., the MMCM’s input clock pin). You use get_pins to find this point in the design hierarchy. -
-multiply_by
/-divide_by
: Defines the frequency relationship to the source clock. -
The final argument
([get_pins ...])
is the pin where this new generated clock emerges (e.g., an output pin of the MMCM).
Asynchronous Clock Domains
If your design has clocks that are truly unrelated (e.g., two different oscillators), the timing analyzer will try to find paths between them by default. This leads to timing failures that are impossible to fix because there is no synchronous relationship.
You must tell Vivado to ignore timing analysis between these domains.
-
Command:
set_clock_groups -asynchronous -group <clock_name_1> -group <clock_name_2>
-
Example: Imagine you have
sys_clk
and another unrelated primary clock calledadc_clk
.# Prevents timing analysis between the `sys_clk` domain (and # its generated clocks) and the `adc_clk` domain. set_clock_groups -asynchronous \ -group {sys_clk clk_200 clk_50} \ -group {adc_clk}
Note: Vivado automatically considers clocks generated from the same MMCM/PLL to be in the same synchronous group. The above example is for illustration; often, you just need to group the primary clocks.
A simpler form for the above would be:
set_clock_groups -asynchronous \
-group [get_clocks sys_clk] \
-group [get_clocks adc_clk]
This command is powerful because it tells the tool that sys_clk
and any clocks generated from it are one group, and adc_clk
(and any clocks generated from it) are another. The tool will not time any paths that cross from one group to the other. You must handle these crossings with proper synchronizer circuits (e.g., multi-flop synchronizers).
Clock Uncertainty
Clock jitter (from the oscillator) and internal FPGA jitter affect the timing budget. The set_clock_uncertainty command models this.
-
Intra-Clock: Jitter on a single clock.
-
Inter-Clock: Jitter/skew between two different synchronous clocks.
Often, the default system jitter calculated by Vivado is sufficient, but for high-speed designs, you might need to specify it.
-
Command:
set_clock_uncertainty -from <from_clk> -to <to_clk> <value_in_ns>
-
Example:
# Set an uncertainty of 0.2 ns for paths within the clk_200 domain set_clock_uncertainty 0.200 [get_clocks clk_200] # Set uncertainty for paths going from sys_clk to clk_200 set_clock_uncertainty -from sys_clk -to clk_200 0.300
Summary for Vivado Batch Mode
When running Vivado in batch mode (e.g., with a Tcl script), you will source your XDC file. A typical flow is:
- Read in your HDL files.
- Synthesize the design (
synth_design
). - Read in your XDC file (
read_xdc
). - Implement the design (
place_design
,route_design
). - Generate timing reports (
report_timing_summary
).
The constraints are critical for the place and route steps, as the tools work to meet the timing you have defined.