Timing constraints and clock domain crossing

This page belongs to 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. This page explains how to define timing constraints that are related to a clock domains.


Equipped with the knowledge on how to find specific logic elements and how to define paths, we shall now begin looking at timing constraints that make use of this knowledge.

The first thing we shall look at is the relation between timing constraints and clock domains. These two topics are closely related, and it's impossible to discuss one of them without involving to the other. I therefore suggest reading through this series of pages about clock domains, if this topic is new to you. Among others, this is necessary for understanding the terminology that I shall use below.

The logic defines the relation between clocks

Let's look at this example of Verilog code:

module top(
    input clk,
    input foo,
    output reg bar_reg,
    output reg baz	   
    reg foo_reg;
    reg bar;
    reg baz_metaguard;
    wire pll_clk_8, pll_clk_6;
   clk_wiz_1 pll_i

always @(posedge pll_clk_8)
  foo_reg <= foo;
always @(posedge pll_clk_6)
    bar <= !foo_reg;
    bar_reg <= bar;

always @(posedge clk)
    baz_metaguard <= bar;
    baz <= baz_metaguard;

This is almost the same as the example with a PLL, which we've seen earlier. The difference is that there is new clock domain crossing: The value of @bar is copied into @baz through a metastability guard.

So there are two types of clock domain crossings in this example:

I've looked at only one criterion to determine the type of the clock domain crossing: The existence or the absence of a metastability guard. If the logic expects unrelated clocks, it uses the necessary safety mechanisms. Therefore nothing else matters: Even if it's possible to ensure the timing requirements on the paths between these clocks, there is no reason to do this.

And vice versa: If the logic expects related clocks, there no protection against timing violations. Therefore the timing requirements must be met on all paths between these clocks.

So in conclusion, the logic always makes assumptions on which clocks are related clocks and which are not. These assumptions are reflected in the existence or absence of protection mechanisms. But it's the FPGA tools that make sure that these assumptions become real. In particular, the tools ensure that the timing requirements are met on paths between related clocks.

So we need to ensure that the tools know about the logic's assumptions on the clocks.

Note that in the example above, the tools have no way to know about the logic's expectations regarding @pll_clk_6 and @clk: The logic may assume that these are related clocks and it may assume the opposite. In fact, I used a similar example earlier (see "Idea #9") to demonstrate a path between related clocks. In the example above, these two clocks are treated as unrelated clocks.

Maybe the tools could make an intelligent guess from the name of @baz_metaguard. Maybe the typical structure of a metastability guard could offer a hint. But that's not enough to make a decision on such an important issue.

Telling the tools about the clocks

In the examples until now, there was only one timing constraint:

create_clock -period 4.000 -name clk [get_ports clk]

The first question that comes to mind is how the tools treat the clock domain crossing from @pll_clk_6 to @clk by default. If this is the only timing constraint, will the tools enforce anything on the path between @bar and @baz_metaguard?

The answer is that it depends on which FPGA tool is used. Even though virtually all FPGA tools will consider @pll_clk_6 and @pll_clk_8 as related clocks, it's not obvious what happens with other pairs of clocks. The tools usually treat all clocks as related clocks by default, but it's not safe to rely on that.

Many FPGA tools support the set_clock_groups command. This is the best option for defining the relation between clocks. Because of the importance of this command, it's a good idea to refer to the tools' documentation before using it in a design.

For the example above, this command could be used like this on Vivado (or similar with other tools):

set_clock_groups -asynchronous \
  -group [list \
     [get_clocks -of_objects [get_pins pll_i/clk_out1] ] \
     [get_clocks -of_objects [get_pins pll_i/clk_out2] ] ] \
  -group [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]

This usage of get_clocks and get_pins was explained before: Each get_clock command finds a clock object according to the name of the relevant port of clk_wiz_1 (note that the name of clk_wiz_1's instantiation is pll_i). For example, the last get_clock command gets the clock object for @clk.

This example shows how set_clock_groups defines groups of clocks. In this case, one group consists of @pll_clk_6 and @pll_clk_8, and the second group consists of @clk only. This constraint tells the tools that all clocks that belong to the same group are related clocks. Likewise, if two clocks belong to different groups, the tools consider them unrelated clocks.

In other words, the tools enforce timing constraints on a path if and only if the clocks on both sides of the path belong to the same group.

Multiple set_clock_groups commands are allowed in a design's timing constraints. But it's best when one set_clock_groups command is used to divide all clocks into groups. This avoids contradictions, and also helps prevent confusion: The main advantage of this command is that it's a concise description of the relations between the clocks. Short, concise and mathematical. That's the way we want it.

Note that set_clock_groups should be used after all create_clock commands, because the clocks objects that are mentioned should already exist.

Inconsistent relations between clocks

If the clocks are considered to be related clocks in some places in the design, and unrelated clocks in other places, it's not possible to use set_clock_groups.

For example, @clk and @pll_clk_6 are treated as unrelated clocks in the Verilog code above. But in theory, there could have been additional logic that treats them as related clocks. Even though it's not a good idea, it is possible to enforce timing constraints between @pll_clk_6 and @clk for this additional logic.

If set_clock_groups can't be used for this reason, first ask yourself if you don't want to change the logic, so set_clock_groups can be used. It's not just because this command is so good: Without a simple rule that says which clocks are related clocks and which are not, it's easy to make mistakes with clock domain crossings. If you still don't want to make such changes in the logic, the solution is to define false paths.

Briefly about the set_false_path command

There are two primary commands for declaring false paths: set_clock_groups and set_false_paths.

The set_clock_groups command was presented above: Any path between two clocks that belong to different groups is considered as a false path. But set_clock_groups can't always be used for all paths that should be declared as false path. In some cases, the selection of paths must be more specific than defining groups of clocks. The set_false_path command solves this problem by allowing a specific selection of paths.

For example, let's replace the set_clock_groups command from above with a set_false_path command. The only thing that needs fixing is the path from @bar to @baz_metaguard. The timing requirements for all other paths are enforced correctly as is. So this is one way to write the set_false_path command for this path. But don't learn from this example:

set_false_path -from [get_cells bar_reg__0] -to [get_cells baz_metaguard_reg]

This command uses get_cells to select the path's beginning and the path's end. This requires knowing the names of the cell objects on both sides. In this case we have the name "bar_reg__0", which demonstrates the problem with relying on names: This name should have been "bar_reg" but as already explained, it ended up with "bar_reg__0" because of a coincidence.

So this example shows one of the problems with set_false_path: The detailed selection of logic elements in the design often requires a reliance on the object's names. This problem and possible solutions have been previously discussed.

This command can be simplified: Since @baz_metaguard is a metastability guard, it doesn't matter where the paths to this register come from. So why not ignore all paths to this register?

set_false_path -to [get_cells baz_metaguard_reg]

The effect of this command is exactly the same.

set_false_path instead of set_clock_groups

Another possibility is to define the paths according to the clocks. In fact, the set_clock_groups command above can be replaced with these constraints:

set_false_path -from [get_clocks -of_objects [get_pins pll_i/clk_out1]] \
               -to [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]

set_false_path -from [get_clocks -of_objects [get_pins pll_i/clk_out2] ] \
               -to [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]

set_false_path -from [get_clocks -of_objects [ get_pins pll_i/clk_in1] ] \
               -to [get_clocks -of_objects [get_pins pll_i/clk_out1] ]

set_false_path -from [get_clocks -of_objects [ get_pins pll_i/clk_in1] ] \
               -to [get_clocks -of_objects [get_pins pll_i/clk_out2] ]

This is the long and detailed way to create the same false paths. Instead of dividing the clocks into groups, there are two set_false_path commands for each pair of unrelated clocks: One command for each direction. It's quite obvious how easy it is to make a mistake with so many constraints. And this is a simple example with three clocks.

Nevertheless, set_false_path is often used like this instead of set_clock_groups. This is most likely because someone copied the timing constraints from somewhere.

Example of how a mistake can happen

Let's look at a simple example of a possible mistake: From the discussion above, it's clear that there is only one path that needs to be declared as a false path: From @bar to @baz_metaguard. Therefore, out of the four set_false_path commands in the last example, only this one has a significance:

set_false_path -from [get_clocks -of_objects [get_pins pll_i/clk_out2] ] \
               -to [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]

The three other set_false_path commands don't cover any paths. But wait, how about simplifying the constraint even more? Maybe all paths that end at @clk should be false paths? How about this?

set_false_path -to [get_clocks -of_objects [ get_pins pll_i/clk_in1] ]

Actually, because the create_clock command that defines "clk" is just above the set_false_path command, this can be written instead:

set_false_path -to [get_clocks clk]

This constraint is short and elegant. Unfortunately, it's horribly wrong: I suggested that all paths that end at @clk should be false path. But what about the path from @baz_metaguard to @baz? That's a path from @clk to @clk. This path should surely not be a false path, of course. But the two last set_false_path commands include all paths that end at @clk, even if the path begins at @clk.

It's easy to make mistakes of this sort, in particular if the constraints for false path are written without the precision of a mathematical mindset. It may be helpful to create special timing reports, which can reveal which paths are false paths and which are not.

This concludes the practical discussion about timing constraints for paths that are inside the FPGA. The next page explains multi-cycle paths, which also belongs to this topic. However, multi-cycle path constraints are usually not recommended. Hence it's OK to skip to the introduction to I/O constraints instead.

Copyright © 2021-2024. All rights reserved. (6f913017)