For asynchronous clocks, there are four ways to write the constraints.
This post provides the pros and cons for each method:
This was the “original” method, used during the early days of PrimeTime.
There are two main disadvantages:
You have to specify it both ways: clocks1 to clock2; and then clock2 to clock1.
The path is not timed, so theoretically, the tool can have as much routing delay as it wants. Effectively, the two flops involved in the crossing could be sitting at two different corners of the device, because there is nothing to tell them that they should meet any specific requirement.
This has been introduced relatively recently in SDC.
There are three main advantages, compared to a):
You have to specify it only once between the pair, saving on the length of your XDC/SDC files.
The path is not timed, the same as when using set_false_path. However, it considers the signal-integrity aspects, while set_false_path does not.
In the world of FPGA based designs, this is immaterial, as signal integrity aspects have been considered while designing the FPGA device.
This one is a little philosophical.
When you use set_false_path, you are just specifying that the path is false. There is no “implicit” understanding as to why the path is false.
For example, take asynchronous clocks. In reality, the path is not false. It exists. Signals will travel across this path. set_false_path is a mechanism used to convey to the tool, that it should not time this path.
However, in the case of set_clock_groups, you are specifying the exact reason, which is that the clock groups are different.
The fact that they should not be timed is a consequence of what you have stated to be the intent. So, this also acts as a note as to why a path should not be timed, and makes it easier to understand the cause of the specific constraint.
Usually, (b) is preferred to (a), mainly because of points (ii) and (iii). Point (i) just acts as an additional convenience.
Around the mid-2000s, the set_false_path users started migrating to set_multicycle_path based constraints.
(a) and (b) do NOT time the path at all – allowing for any amount of delay in the path.
This method provides at least some kind of upper-bound to the time-of-flight across the crossing.
This option can achieve the same effect as (c).
Use of the –datapath_only option is specific to Vivado. This makes it easier for the user who does not have to worry about the clock skews involved.
Also, set_max_delay once again better represents the intent compared to set_multicycle_path.
If I had to choose, I would go in the order:
(d), (b), (c), (a).
Sometimes, I might give higher preference to (b).
For example, if I am working on an Out-Of-Context design-portion, and that design portion can be used with different applications, I will not know what kind of peripheral or device that portion will be implemented on.
In this case, I might not know what would be a good max_delay value to specify, so I might play it safe by going for option (b), rather than (d).
If using Xilinx tools, Vivado allows access to the “period” at which my piece would be operating. In this case, I might specify the delay value in terms of the period, so I would be more likely to use option (d).
However, others might have a different order of preference, based on personal choice/need/familiarity etc.