Validating that the timing constraints are correct

This page is the last page in a series of pages about timing. The previous pages explained the theory behind timing calculations, showed how to write several timing constraints and discussed the principles of timing closure.


Quite often, the FPGA development tactic is to add functionality, fix whatever doesn't work, and repeat. It's easy to neglect the parts of the design that are possibly wrong, but yet don't pose a visible problem.

It's crucial to understand that an FPGA design may work perfectly fine even if timing constraints are applied incorrectly and even if they're not achieved: Proper usage of timing constraints is one of the key components for ensuring the stable and correct operation of the FPGA. And yet, if this topic is neglected, it doesn't necessarily mean an immediate failure. Rather, improper timing may lead to occasional malfunctions that can be highly confusing.

This page summarizes and reiterates many of the suggestions from this series of pages about timing constraints. The purpose is to highlight some topics that should be kept in mind while looking for problems that are the result of inadequate timing. One can wish to have the wisdom to make a checkup on these topics occasionally for the sake of it, but in real life most of us do this to solve a problem that appears to be a bit like witchcraft.

Read your timing report

All FPGA design tools emit timing reports. The most common reason we open them is when the tools fail to achieve the timing constraints: That's where one can find the failing paths, and hopefully figure out what can be done about them.

However, there is a no less important reason to examine the timing reports: To verify that the timing constraints are interpreted by the tools as intended, and hence that they are properly applied. It's a good habit to make this check occasionally, in particular after adding new timing constraints.

Since each FPGA design tool produces these reports in different structures and formats, it's infeasible to relate to exactly what each part in each reports says. Accordingly, this page outlines the principles, which are common to all tools. As for your own timing reports' formats and capabilities, it's a good idea anyhow to take the time to learn them.

Another possible benefit of a timing report checkup is that it may reveal incorrect usage of logic. For example, if the design includes wrongly used asynchronous logic or depends on clocks for which timing constraints can't be applied, this can become evident in the timing report: Because applying timing constraints to this logic is impossible, it may appear in the report as unconstrained paths.

On a different note, it's worth mentioning that IPs that are included in the design often contribute timing constraints of their own, which the tools add on top of those that you provide. There is usually no point checking the paths that relate to these constraints, but they can add quite some weight to the timing report.

Unconstrained internal paths

For the sake of this discussion, a synchronous element is a flip-flop, block RAM, shift register or any other logic elements which samples its data input and/or changes its data output on the rising edge or falling edge of a clock.

All paths that go from a synchronous element's data output to another synchronous element's data input must be timed, i.e. subject to timing constraints. The only exception is when the possibility of a timing violation is properly handled at the destination's synchronous element, as with unrelated clock domains.

In other words, the path to any synchronous element must be timed, unless there's an explicit resynchronization mechanism in place to cover up for fact that the design tools don't take any responsibility on sampling the input signal in a predictable manner.

Sometimes it's enough with a single-row timing constraint that defines the reference clock, and the FPGA tools take care of the rest. In other cases it requires more than so. In really bad cases, some internal paths remain unconstrained by mistake. There are several possible reasons for this, among others:

FPGA design tools support the creation of timing reports that list unconstrained paths, which are the paths for which no timing constraint was applied. In principle, this list should be empty: The paths that need no constraints should be explicitly defined as false paths in the timing constraint file. Such constraints don't change anything functionally for paths that weren't timed anyhow, but they allow keeping the list of unconstrained paths in the timing report empty. This makes it easy to spot paths that are unintentionally left out. Furthermore, since the number of paths in this list is limited, paths that definitely shouldn't be in this list may be hidden, because harmless paths are listed instead.

But unfortunately, in some timing reports, the section for unconstrained paths may also include paths that have no reason to be covered by timing constraints. For example, the path from the output of a clock buffer to the input of another another clock resource. As a result, the timing report may list a whole lot of paths as unconstrained, and yet that's perfectly fine. This makes it slightly more difficult to deduce the situation from the timing report, but this isn't really a problem, since such paths are commonly listed separately from paths that end at a flip-flop's data input or some other synchronous element. So it boils down to reading the report carefully and paying attention to what each group of listed paths means.

The timing report that is created by default is sometimes not detailed enough to contain this information. Any reasonable FPGA design tool allows the creation of an on-demand timing report that lists unconstrained paths, and the choice of how many such paths to list for each group (10 is a reasonable number, even though the relevant lists should be completely empty).

Paths between clock domains

Timing constraints must be used on internal paths that are synchronous with different clocks at their source and destination, if the clocks are related clocks (or more precisely, if the logic treats them as related). Otherwise, they shouldn't be, as this unnecessary restriction may waste high-quality routing resources and possibly cause the failure to achieve timing. See this page about related clocks vs. unrelated clocks.

Each toolset has its own way to automatically decide whether to consider a pair of clocks as related. Tools that use SDC syntax in their timing constraint files (e.g. Vivado and Quartus) have a set_clock_groups command, which allows defining groups of related clocks and unrelated clocks. There are also other methods to guide the tools on this matter, in particular by virtue of false path timing constraints.

So the question is how to check whether the tools consider clocks as related clocks or not. The unfortunate answer is that each toolset has a different way. Vivado, for example, has a Clock Interaction Report, with a color diagram that shows the situation for each pair of clocks:

Alternatively, custom timing reports that are limited to certain groups of paths can be used to investigate this issue. The paths can be selected according to the clocks involved. This can be done by selecting paths, that begin at logic elements that are synchronous with one clock, and end at another clock. It might also be beneficial to look specifically at groups of paths that they are known to be involved in clock domain crossings.

Even though it might be difficult, it's crucial to make a comprehensive review of which clock domain crossings the tools apply constraints on, and whether they are correct. At the same time, it's an opportunity to review if the logic design has resynchronization logic where it's needed. It's easy to end up with an unsafe clock domain crossing because of a confusion about which clock is used with each signal.

It's important to make this review with each clock's nature in mind. For example, if two clocks from different oscillators have the same intended frequency, and hence have a similar definition in the timing constraints file, the tools might mistakenly consider them related, and calculate the timing on paths that cross between these clocks. Applying constraints on such paths is meaningless, as there's no guarantee whatsoever on the phase relations between the two clocks. By themselves, unnecessary timing constraints only makes it harder for the tools to achieve timing, which can be pretty harmless. The real problem is that the timing report might mislead us into thinking that the clocks are actually related clocks. So by just looking at the constraints and the timing reports, the paths between them might appear to be safe (i.e. not needing protection against timing violations) when in fact they aren't: The two clocks have nothing in common, except for approximately the same frequency. The only way to avoid mistakes of this sort is to understand how each clock is generated.

Unconstrained external paths

Timing constraints should always be applied to paths that start at I/O pins or end at I/O pins. There is a separate page that explains how to do this. The only exception is clock input pins and pins with a special interface, e.g. gigabit transceivers, pins that are connected directly to a hardware processor on the FPGA's silicon etc. If the interface is very slow, it can also be forgivable if timing constraints are not defined. For example, with LEDs, pushbuttons and even I2C wires. But it's much better to assign false path constraints for such pins, as it keeps the list of unconstrained I/O pins empty in the timing report.

Many times it may seem meaningless to apply timing constraints on external paths. For example, if there are several output pins that are all synchronous with the same clock, and the correct timing is ensured by the fact that they all toggle simultaneously. A common way to implement this simultaneous toggling is using the IOB register. This flip-flop delivers the best clock-to-output possible and also an impressively low skew between output pins.

This is however a good example for when timing constraints on output pins is important: By keeping the timing constraints tight, a low skew between these pins is ensured. When the IOB register is used, tight timing constraints can be a way to force the tools to use this flip-flop, or make sure that if the tools fail to do so, it won't go unnoticed: If the tools don't place the flip-flop as desired, timing fails.

For similar reasons, tight timing constraints on input pins should be applied as well.

When using timing constraints for the sake of a low skew between several pins, it's important to constrain I/O pins not just until the reported slack is nearly zero, but to verify that requiring tighter timing leads to a failure to achieve the constraint. This is because the I/O signal path may contain optional delay lines, which the tools may utilize in a counterintuitive manner. For example, Intel FPGA's Quartus may add some delay in the input path if there's timing budget surplus. The tools' choice to do this may be neither desired nor expected.

Incorrect false paths and otherwise relaxed timing

Sometimes timing constraints are applied, but they are too permissive. This is much harder to detect, as the related paths are timed, only with the wrong requirement.

There are several possible reasons for mishaps of this sort, among others:

There is no simple recipe to spot issues of this sort. Reading the timing report from top to bottom carefully is definitely a good idea, however even if the report displays 10 paths for each group, there's no guarantee that the problematic paths will happen to appear there. Or among any number of displayed paths, for that matter.

Another way to tackle this is reviewing the timing constraints as written. Since SDC constraints are written in Tcl, it's possible to evaluate the expressions that select the logic elements (with "from" and "to") in the constraints file as Tcl expressions, and read through the list of endpoints.

So if, for example this line appears in a Vivado .xdc file:

set_false_path -to [ get_pins -hier -filter {name =~ */pclk_i1_bufgctrl.pclk_i1/S*} ]

one can open the implemented design and list the destinations to which the false paths apply:

puts [join [ get_pins -hier -filter {name =~ */pclk_i1_bufgctrl.pclk_i1/S*} ] "\n" ]

The Tcl commands "puts" and "join" make sure that each element is listed in a separate row, so it's easy to read the output (which can be very long).

This kind of review of the constraints is important, but it likewise difficult, as it requires real brainwork.


Making sure that the tools enforce the correct timing limits on the various paths is a tricky task. It's a combination of knowing what the timing constraint statements mean exactly and knowing how to check the timing reports, to increase the chances of spotting mistakes.

But more than anything, it's about having the self-discipline to check and re-check the design, in particular when it appears to work fine.

Copyright © 2021-2024. All rights reserved. (c3c02857)