01signal.com

Timing exceptions

This page belongs to a series of pages about timing. The previous pages explained the theory behind timing calculations, discussed the clock period constraint, showed the principles of timing closure, and introduced a few important Tcl commands. This page explains how to define timing constraints for specific paths.

Introduction

The goal for writing timing constraints is that they should be short, concise, and easy to understand. This reduces the risk for confusions and mistakes. If possible, the timing constraints for the FPGA's internal paths should consist only of two types: clock period constraints (e.g. create_clock) and clock group constraints (e.g. set_clock_groups).

But often, specific paths need specific rules. These specific rules are called Timing Exceptions. As their name implies, these timing constraints are primarily intended to override the existing timing constraints. They can also be used to specify the timing requirements for paths that wouldn't have any such requirements otherwise.

The commands for these purposes in the SDC syntax are primarily these:

Tools that use a different syntax are likely to have similar commands.

These commands' arguments define (among others) which paths should be affected. This is explained below.

There are two related topics which are discussed on other pages:

False Paths

A false path is a path that the tools ignore as a result of a timing constraint that requests this. In other words, the tools deliberately don't enforce any timing requirements on a false path, because this is what we told them to do.

This should not be confused with unconstrained paths: These are paths that the tools don't have any timing constraint for. Just like false paths, the tools don't enforce any timing requirements on these paths, but the reason is different: This lack of enforcement is not a choice, but the tools don't know what requirement to apply.

There should be no unconstrained paths in an FPGA design. That said, there are often paths that we want the tools to ignore. These paths should be marked as false path by virtue of timing constraints. The rationale: When we read the timing report, we should always see that the number of unconstrained paths is zero. If this number isn't zero, we should look for a mistake. If we get used to accepting a non-zero number of unconstrained paths, it's easier to overlook problems with timing constraints.

So there are two possible reasons for declaring false paths:

Practically speaking, it doesn't matter which of these scenarios is in effect. If no timing requirements are necessary on a path, it should be declared as a false path.

It's a common mistake to use set_false_path in relation with clock domain crossings. This topic is discussed in more detail later. In fact, there are not many situations where it's correct to use set_false_path, except for with I/O ports.

Selecting paths for set_false_path

There are many ways to select the which paths the command applies to. Despite the wide range of possibilities, the selection of paths is usually done with two arguments: -from and -to. For example:

set_false_path -from [get_cells source_reg] -to [get_cells dest_reg]

In this example, the set_false_path command is applied to the path that starts at a register with the name @source and ends at the register with the name @dest.

Note that get_cells provides -from and -to with objects that represent logic elements. The other commands that were discussed in the previous page can be used as well: get_pins, get_nets, get_ports and get_clocks. However, each FPGA tool has its own rules regarding what is acceptable for use with -from and with -to.

Be aware that the tools may not interpret the reference to objects as one would naturally expect. The tools may also ignore some or all of the objects without issuing a warning. In fact, if no objects are found by the get_* command (e.g. because of a mistake in the search pattern), no path is included in the timing exception. Even this can happen without a warning.

It's therefore important to use the timing report to verify that the timing exceptions are applied as intended. This means that the correct paths are affected, and that the timing calculation fulfills its accurate purpose (no enforcement of timing requirements).

There is another useful argument for selecting paths: -through. As its name implies, this argument selects paths that go through the specific logic elements.

It's worth taking the time to read the official documentation in order to be aware of all features. For example, it's also possible to limit the effect of the command to only rising clock edges or falling clock edges. Another possibility is to limit the command to the analysis of only tsetup or only thold.

The set_false_path command requires at least one of -from, -to or -through as an argument (or a different argument with similar meaning). When there is more than one argument, the timing exception applies only to the paths that meet the conditions of all arguments.

The danger with set_false_path

The problem with false paths is how easy it is to make mistakes. In particular, there's a risk of including more paths than intended. When this happens, there are sequential elements that may not operate properly: We mistakenly expect that the tools guarantee their requirements for tsu and thold. But in reality, the tools consider the paths to these sequential elements as false paths. Hence the tools don't bother making any timing calculations for them. This is a dangerous situation, because it creates an illusion that everything is OK when the tools say that the timing constraints were achieved. But in reality there are flip-flops and other sequential elements that are exposed to timing violations without any protection.

To make things worse, if a path is declared as a false path, this declaration usually takes highest priority. Hence if a path is accidentally declared as a false path, this will probably overrule other timing constraints that require the opposite. This is usually true even if set_false_path appears before the other timing constraint. So set_false_path is interpreted as "these are false paths, regardless of what I required before or will require later".

set_min_delay and set_max_delay

I started with discussing false paths, which eliminate the timing requirements for a group of selected paths. Often, the need is to specify the timing requirements, rather than to cancel them. This is done with set_min_delay and set_max_delay. For example, consider this Verilog code:

reg x, y;

always @(posedge clk)
  y <= x;

And the timing constraints:

set_max_delay -from [get_cells x_reg] -to [get_cells y_reg] 3
set_min_delay -from [get_cells x_reg] -to [get_cells y_reg] 1

So what's the meaning of these commands? Let's start with set_max_delay. The short explanation for this timing exception is that it's similar to clock period constraint (create_clock) that is intended for specific paths only.

Recall that when a clock period constraint is defined, the tools automatically enforce the necessary timing requirements: The maximal delays of the relevant paths are restricted in order to meet the tsetup requirement. Likewise, the minimal delays are restricted on behalf of thold.

In the example above, the value that appears in the set_max_delay command is 3. This is equivalent to a create_clock command for a clock period that equals 3 ns. But set_max_delay's influence is limited to a specific group of paths. That's the difference.

Regarding set_min_delay, it's a bit more complicated, so more about that below.

Note that the name of the set_max_delay command is misleading: This command doesn't directly define the maximal delay of the path itself. The delay of the affected paths is not limited to 3 ns by the command in the example above. The 3 ns value is applied as the allowed time difference between clock edges. And this time difference is not measured at the synchronous elements' clock inputs, but rather at these clocks' origin. This means that the delays of the PLLs, the clock buffers and the clock routing are included in the calculations: Similar to a clock period constraint, the clock delays are taken into consideration in the timing analysis.

The meaning of -from, -to, -through and other arguments is the same as set_false_path. But generally speaking, set_min_delay and set_max_delay are intended as adjustments for a clock period constraint. Accordingly, it is usually assumed that there are sequential elements on both sides of the path. Therefore, -from and -to should represent synchronous elements. There are many ways to refer to these: A cell object that represents the synchronous element itself, a clock object, and so on.

Regarding set_min_delay, there is a similar story: Another consequence of a clock period constraint is that the tools must meet thold requirements. For this purpose, the tools enforce minimal delays on all paths between the same clock or between related clocks. If the timing calculation for a path is the result of create_clock only (with the same clock on both sides), this is equivalent to a set_min_delay with the value zero. This reflects the idea that the same clock edge arrives to both sequential elements. However, this doesn't mean that this clock edge arrives at the same time to these sequential elements. Rather, the clock edges for each sequential element starts at the same time at the clock's origin. The delay from each clock's origin to the sequential element is taken into account in the calculation (similar to how the calculation is done for tsetup).

set_min_delay allows to adjust the time of the clock edge that arrives to the second sequential element. For example, the command above makes this clock edge arrive later by 1 ns at the second flip-flop. This makes the timing requirement for thold harder to meet: See the example of a timing report below.

There are two possible reasons for using set_min_delay and set_max_delay:

When these two commands are used, always check the timing calculations of the paths carefully in the timing reports. In particular, pay attention to how the clock path plays a role in the calculation.

There are examples of timing reports at the bottom of this page, which show the results of set_min_delay and set_max_delay.

More accurate control of the delay

The level of control that is provided by set_max_delay and set_min_delay is not significantly better than a clock period constraint (as presented so far). But what if we don't want the clock path delay to interfere with our definition of the delay? What if we want to control the delay of a specific segment of the path?

Some FPGA tools don't provide any solution for these needs. For example, Quartus offers no such possibility (as far as I know). Vivado, on the other hand, has a few features, which I shall discuss briefly.

One such feature is the datapath_only option: It's sometimes desired to use set_max_delay so that the timing calculation will not include the clock paths. For example, if a path is between unrelated clocks, it's meaningless to take the clock paths into account.

When datapath_only is used, the timing is calculated as if the clock path delay of both synchronous elements was zero. Hence the calculation consists only of the delays of the logic elements of the path. The sum of these delays is required to be smaller than the number that is given in the set_max_delay command (see example of timing report below).

So when set_max_delay is used with datapath_only, its meaning is closer to what this command's name implies. Note however that the path must still start and end at a synchronous element.

Also, datapath_only has a few peculiarities: If it is used, the tools will not enforce any timing requirement regarding the thold of the relevant paths. In other words, the tools behave as if there was a false path constraint that was applied only to the thold scenario. Even if a set_min_delay command is added for the relevant paths, this command are ignored. It's not clear why Vivado refuses to enforce a minimal delay on the paths that are affected by datapath_only.

datapath_only can't be used as an argument to set_min_delay in any scenario.

Misleading features

As the title of this section implies, these are a few possibilities that are unlikely to do any good. Feel free to skip to the next section.

Vivado allows an even higher level of control: It's possible to enforce restrictions on the delay of a specific segment of a path. For example, what happens if logic elements that aren't sequential elements are used with with -from and -to? How about pins and nets? What will set_min_delay and set_max_delay do?

That depends on which FPGA tool is used, of course. The tools may silently ignore such constraints, because no paths match the requirement. But Vivado doesn't ignore such constraints, although the result is not what one would naturally expect: Vivado considers this situation as "path segmentation" and issues a critical warning in response. In most cases, using this feature is not worth the complications that it causes. Refer to the documentation for more about this.

And here's another feature that can be misleading: Quartus has a command called set_net_delay. It allows to define a minimal and maximal delay between different logic elements in the design. However, it appears like the tools don't attempt to achieve these timing requirements during the implementation. Instead, it's possible to generate a report with "report_net_delay" (or with the GUI). It's hence possible to deduce from this report if the conditions that were defined with set_net_delay were met. So set_net_delay can be used to obtain information, but it's not useful as a timing constraint. In other words: set_max_delay and set_min_delay are effective as timing constraints, but set_net_delay is not.

In summary, it doesn't get much better than the plain way of using set_max_delay and set_min_delay. Seemingly, the only interesting feature is Vivado's datapath_only.

Precedence order of timing constraints

The main problem with complicated timing constraints is that there are often paths that are affected by more than one constraint. These constraints usually have contradicting timing requirements for these paths. So which constraint wins?

Each FPGA tool has its own rules on how to resolve conflicts of this sort. It usually depends primarily on the type of the constraint. Other factors may also play a role, in particular the specificity of the paths' selection.

For timing constraints that are based upon SDC, the precedence depends on the type of the constraint. For example, Vivado resolves a conflict according to this order of precedence. The commands are listed from highest precedence to lowest:

Note that the first two commands (set_clock_groups and set_false_path) create false paths, and they have the highest priority. It's therefore extra important to verify that no path is affected by mistake by these commands. Such a mistake silently turns off all enforcement of timing requirements on the relevant paths.

When the same command is used more than once for the same path, the most specific command wins. For example, if one command has "-from" and "-to", but the second command has just "-from", the first command wins. Another example: If one command defines the paths based upon logic elements (e.g with get_cells), and the second command defines the paths based upon clocks (with get_clocks), the first command wins.

If two commands are so similar that there is no difference in priority, the order of appearance resolves the conflict: The last command wins.

Similar precedence rules are used by Quartus and other tools that are based upon SDC.

But regardless of which tool you use, it's best to avoid any contradiction between timing constraints (except for overruling create_clock). Complicated timing constraints are the place where devastating bugs can thrive.

With some FPGA tools, it's possible to work around the priority rules: By using the -reset_path option, the current command overrules the previous timing exceptions on all paths that it applies to. For example:

set_max_delay -reset_path -from [get_cells source_reg] 2

Conclusion

I started this page with saying that timing constraints should be short, simple and concise. By adding timing exceptions, the timing constraints become not only longer, but more complicated and difficult to understand. So use timing exceptions when necessary, but write them with care and keep them tidy.

Try to write the timing exception commands in a way that reflects their purpose and expresses the idea behind them. The FPGA tools usually offer GUI wizards for creating timing constraints. They can be useful as a starting point. But don't use a timing constraint that has been created by a wizard before thinking it through carefully. Even if this constraint is correct when it is created, will it continue to work faithfully even when the logic design evolves, and new logic is added?

Always read the timing reports for the paths that are affected by a timing exception. If there are more than one timing constraints for a path, this is even more important. Also create specific timing reports for paths that have a potential of having incorrect timing requirements.

Remember: Creating and reading timing reports is not a waste of time. No matter how careful you read the documentation (which is always a good idea), there is always a chance for mistakes. In particular, false paths can occur where they're least expected.

Bonus: Examples of timing reports

In order to help understanding set_min_delay and set_max_delay, these are a few timing reports that were created with Vivado.

Recall from above that the Verilog code behind these reports is:

always @(posedge clk)
  y <= x;

And the timing constraints:

set_max_delay -from [get_cells x_reg] -to [get_cells y_reg] 3
set_min_delay -from [get_cells x_reg] -to [get_cells y_reg] 1

The report for tsetup:

Slack (MET) :             0.360ns  (required time - arrival time)
  Source:                 x_reg/C
                            (rising edge-triggered cell FDRE clocked by clk  {rise@0.000ns fall@2.000ns period=4.000ns})
  Destination:            y_reg/D
                            (rising edge-triggered cell FDRE clocked by clk  {rise@0.000ns fall@2.000ns period=4.000ns})
  Path Group:             clk
  Path Type:              Setup (Max at Slow Process Corner)
  Requirement:            3.000ns  (MaxDelay Path 3.000ns)
  Data Path Delay:        2.617ns  (logic 0.139ns (5.311%)  route 2.478ns (94.689%))
  Logic Levels:           0  
  Clock Path Skew:        -0.053ns (DCD - SCD + CPR)
    Destination Clock Delay (DCD):    2.644ns
    Source Clock Delay      (SCD):    3.224ns
    Clock Pessimism Removal (CPR):    0.527ns
  Clock Uncertainty:      0.035ns  ((TSJ^2 + TIJ^2)^1/2 + DJ) / 2 + PE
    Total System Jitter     (TSJ):    0.071ns
    Total Input Jitter      (TIJ):    0.000ns
    Discrete Jitter          (DJ):    0.000ns
    Phase Error              (PE):    0.000ns
  Clock Net Delay (Source):      1.392ns (routing 0.002ns, distribution 1.390ns)
  Clock Net Delay (Destination): 1.216ns (routing 0.002ns, distribution 1.214ns)
  Timing Exception:       MaxDelay Path 3.000ns

    Location             Delay type                Incr(ns)  Path(ns)    Netlist Resource(s)
  -------------------------------------------------------------------    -------------------
                         (clock clk rise edge)        0.000     0.000 r  
    AG12                                              0.000     0.000 r  clk (IN)
                         net (fo=0)                   0.000     0.000    clk_IBUF_inst/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.738     0.738 r  clk_IBUF_inst/INBUF_INST/O
                         net (fo=1, routed)           0.105     0.843    clk_IBUF_inst/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.049     0.892 r  clk_IBUF_inst/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.839     1.731    clk_IBUF
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.101     1.832 r  clk_IBUF_BUFG_inst/O
    X2Y0 (CLOCK_ROOT)    net (fo=4, routed)           1.392     3.224    clk_IBUF_BUFG
    SLICE_X49Y57         FDRE                                         r  x_reg/C
  -------------------------------------------------------------------    -------------------
    SLICE_X49Y57         FDRE (Prop_DFF_SLICEL_C_Q)
                                                      0.139     3.363 r  x_reg/Q
                         net (fo=2, routed)           2.478     5.841    x
    SLICE_X49Y57         FDRE                                         r  y_reg/D
  -------------------------------------------------------------------    -------------------

                         max delay                    3.000     3.000    
    AG12                                              0.000     3.000 r  clk (IN)
                         net (fo=0)                   0.000     3.000    clk_IBUF_inst/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.515     3.515 r  clk_IBUF_inst/INBUF_INST/O
                         net (fo=1, routed)           0.066     3.581    clk_IBUF_inst/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.034     3.615 r  clk_IBUF_inst/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.722     4.337    clk_IBUF
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.091     4.428 r  clk_IBUF_BUFG_inst/O
    X2Y0 (CLOCK_ROOT)    net (fo=4, routed)           1.216     5.644    clk_IBUF_BUFG
    SLICE_X49Y57         FDRE                                         r  y_reg/C
                         clock pessimism              0.527     6.171    
                         clock uncertainty           -0.035     6.136    
    SLICE_X49Y57         FDRE (Setup_EFF2_SLICEL_C_D)
                                                      0.065     6.201    y_reg
  -------------------------------------------------------------------
                         required time                          6.201    
                         arrival time                          -5.841    
  -------------------------------------------------------------------
                         slack                                  0.360

Note that the delay's value (3 ns) is used as the start time for the clock path of the second flip-flop. In other words, it's exactly as if there was a period constraint for 3 ns.

Also note that the net in data path has a huge delay: 2.478 ns. This is the result of the set_min_delay command: The tools are forced to insert a long delay on this net in order to meet the thold requirement.

Speaking of which, this is the timing report for thold:

Slack (MET) :             0.057ns  (arrival time - required time)
  Source:                 x_reg/C
                            (rising edge-triggered cell FDRE clocked by clk  {rise@0.000ns fall@2.000ns period=4.000ns})
  Destination:            y_reg/D
                            (rising edge-triggered cell FDRE clocked by clk  {rise@0.000ns fall@2.000ns period=4.000ns})
  Path Group:             clk
  Path Type:              Hold (Min at Fast Process Corner)
  Requirement:            1.000ns  (MinDelay Path 1.000ns)
  Data Path Delay:        1.141ns  (logic 0.049ns (4.294%)  route 1.092ns (95.706%))
  Logic Levels:           0  
  Clock Path Skew:        0.029ns (DCD - SCD - CPR)
    Destination Clock Delay (DCD):    1.684ns
    Source Clock Delay      (SCD):    1.258ns
    Clock Pessimism Removal (CPR):    0.398ns
  Clock Net Delay (Source):      0.502ns (routing 0.002ns, distribution 0.500ns)
  Clock Net Delay (Destination): 0.585ns (routing 0.002ns, distribution 0.583ns)
  Timing Exception:       MinDelay Path 1.000ns

    Location             Delay type                Incr(ns)  Path(ns)    Netlist Resource(s)
  -------------------------------------------------------------------    -------------------
                         (clock clk rise edge)        0.000     0.000 r  
    AG12                                              0.000     0.000 r  clk (IN)
                         net (fo=0)                   0.000     0.000    clk_IBUF_inst/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.339     0.339 r  clk_IBUF_inst/INBUF_INST/O
                         net (fo=1, routed)           0.025     0.364    clk_IBUF_inst/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.015     0.379 r  clk_IBUF_inst/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.350     0.729    clk_IBUF
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.027     0.756 r  clk_IBUF_BUFG_inst/O
    X2Y0 (CLOCK_ROOT)    net (fo=4, routed)           0.502     1.258    clk_IBUF_BUFG
    SLICE_X49Y57         FDRE                                         r  x_reg/C
  -------------------------------------------------------------------    -------------------
    SLICE_X49Y57         FDRE (Prop_DFF_SLICEL_C_Q)
                                                      0.049     1.307 r  x_reg/Q
                         net (fo=2, routed)           1.092     2.399    x
    SLICE_X49Y57         FDRE                                         r  y_reg/D
  -------------------------------------------------------------------    -------------------

                         min delay                    1.000     1.000    
    AG12                                              0.000     1.000 r  clk (IN)
                         net (fo=0)                   0.000     1.000    clk_IBUF_inst/I
    AG12                 INBUF (Prop_INBUF_HRIO_PAD_O)
                                                      0.595     1.595 r  clk_IBUF_inst/INBUF_INST/O
                         net (fo=1, routed)           0.042     1.637    clk_IBUF_inst/OUT
    AG12                 IBUFCTRL (Prop_IBUFCTRL_HRIO_I_O)
                                                      0.022     1.659 r  clk_IBUF_inst/IBUFCTRL_INST/O
                         net (fo=1, routed)           0.409     2.068    clk_IBUF
    BUFGCE_X1Y0          BUFGCE (Prop_BUFCE_BUFGCE_I_O)
                                                      0.031     2.099 r  clk_IBUF_BUFG_inst/O
    X2Y0 (CLOCK_ROOT)    net (fo=4, routed)           0.585     2.684    clk_IBUF_BUFG
    SLICE_X49Y57         FDRE                                         r  y_reg/C
                         clock pessimism             -0.398     2.286    
    SLICE_X49Y57         FDRE (Hold_EFF2_SLICEL_C_D)
                                                      0.055     2.341    y_reg
  -------------------------------------------------------------------
                         required time                         -2.341    
                         arrival time                           2.399    
  -------------------------------------------------------------------
                         slack                                  0.057

Once again, note that the delay's value (1 ns) is used as the start time for the clock path of the second flip-flop. When there is no set_min_delay command (i.e. just a clock period constraint), this is 0 ns.

The delay of the net in the data path is 1.092 ns. This is just enough to meet the thold requirement. Why was it 2.478 ns before? Because the worst-case calculation for tsetup was done for "Max at Slow Process Corner". So 2.478 ns is the longest possible delay for the relevant net, and 1.092 ns is the shortest possible delay ("Min at Fast Process Corner"). See the discussion about Multi-corner timing analysis for more about this.

The third example demonstrates datapath_only, so the constraint is:

set_max_delay -datapath_only -from [get_cells x_reg] -to [get_cells y_reg] 3

There is no set_min_delay, because it's ignored anyhow (even if it's written after the set_max_delay command).

The report for tsetup is now:

Slack (MET) :             2.482ns  (required time - arrival time)
  Source:                 x_reg/C
                            (rising edge-triggered cell FDRE clocked by clk  {rise@0.000ns fall@2.000ns period=4.000ns})
  Destination:            y_reg/D
                            (rising edge-triggered cell FDRE clocked by clk  {rise@0.000ns fall@2.000ns period=4.000ns})
  Path Group:             clk
  Path Type:              Setup (Max at Slow Process Corner)
  Requirement:            3.000ns  (MaxDelay Path 3.000ns)
  Data Path Delay:        0.583ns  (logic 0.139ns (23.842%)  route 0.444ns (76.158%))
  Logic Levels:           0  
  Timing Exception:       MaxDelay Path 3.000ns -datapath_only

    Location             Delay type                Incr(ns)  Path(ns)    Netlist Resource(s)
  -------------------------------------------------------------------    -------------------
    SLICE_X49Y57                                      0.000     0.000 r  x_reg/C
    SLICE_X49Y57         FDRE (Prop_DFF_SLICEL_C_Q)
                                                      0.139     0.139 r  x_reg/Q
                         net (fo=2, routed)           0.444     0.583    x
    SLICE_X49Y57         FDRE                                         r  y_reg/D
  -------------------------------------------------------------------    -------------------

                         max delay                    3.000     3.000    
    SLICE_X49Y57         FDRE (Setup_EFF2_SLICEL_C_D)
                                                      0.065     3.065    y_reg
  -------------------------------------------------------------------
                         required time                          3.065    
                         arrival time                          -0.583    
  -------------------------------------------------------------------
                         slack                                  2.482

Note that the structure of this timing report is like before, but everything that is related to the clock paths has been removed.

The delay of the net is small, because the tools didn't have a reason to insert a long delay: There is no thold requirement.

And I'm not showing the timing report for thold, because there is no timing requirement (the minimal delay is treated as a false path).


This concludes the general discussion about timing exceptions. The next page continues with the timing exceptions that are necessary for a clock domain crossing.

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