Intel® Quartus® Prime Standard Edition User Guide: Timing Analyzer

ID 683068
Date 2/21/2024
Document Table of Contents set_clock_groups Constraint Tips

When you use derive_pll_clocks to create clocks, it can be time consuming to determine all the clock names to include in set_clock_groups constraints. However, you can use the following technique to somewhat automate clock constraint creation, even if you do not know all of the clock names.

  1. Create a basic .sdc file that contains the Recommended Initial SDC Constraints, except omit the set_clock_groups constraint for now.
  2. To add the .sdc to the project, click Assignments > Settings > Timing Analyzer. Specify the .sdc file under SDC files to include in the project.
  3. To open the Timing Analyzer, click Tools > Timing Analyzer.
  4. In the Task pane, double-click Report Clocks. The Timing Analyzer reads your .sdc, applies the constraints (including derive_pll_clocks), and reports all the clocks.
  5. From the Clocks Summary report, copy all the clock names that appear in the first column. The report lists the clock names in the correct format for recognition in the Timing Analyzer.
  6. Open .sdc file and the paste the clock names into the file, one clock name per line.
  7. Format the list of clock names list into the set_clock_groups command by cutting and pasting clock names into appropriate groups. Next, paste the following template into the .sdc file:
    set_clock_groups -asynchronous -group { \  
    } \
     -group { \ 
    } \  
    -group  { \
    } \ 
    -group { \
  8. Cut and paste the clock names into groups to define their relationship, adding or removing groups as necessary. Format the groups to make the code readable.
    Note: This command can be difficult to read on a single line. You can use the Tcl line continuation character "\" to make this more readable. Place a space after the last character, and then place the "\" character at the end of the line. This characters escapes, Be careful not to include any spaces after the escape character, otherwise the space becomes the escape character, rather than the end-of-line character).
    set_clock_groups -asynchronous \  
        -group {adc_clk \ 
           the_adc_pll|altpll_component_autogenerated|pll|clk[0] \
           the_adc_pll|altpll_component_autogenerated|pll|clk[1] \
           the_adc_pll|altpll_component_autogenerated|pll|clk[2] \
        } \
        -group {sys_clk \ 
           the_system_pll|altpll_component_autogenerated|pll|clk[0] \
           the_system_pll|altpll_component_autogenerated|pll|clk[1] \
        } \  
        -group {the_system_pll|altpll_component_autogenerated|pll|clk[2] \
Note: The last group has a PLL output system_pll|..|clk[2] while the input clock and other PLL outputs are in different groups. If you use PLLs, and the input clock frequency does not relate to the frequency of the PLL's outputs, you must treat the PLLs asynchronously. Usually most outputs of a PLL relate and are in the same group, but this is not a requirement.

For designs with complex clocking, creating clock groups can be an iterative process. For example, a design with two DDR3 cores and high-speed transceivers can have thirty or more clocks. In such cases, you start by adding the clocks that you manually create. Since the Timing Analyzer assumes that the clocks not appearing in the command relate to every clock, this conservatively groups the known clocks. If there are still failing paths in the design between unrelated clock domains, you can start add the new clock domains as necessary. In this case, a large number of the clocks are not in the set_clock_groups command, since they are either cut in the .sdc file for the IP core (such as the .sdc files that the DDR3 cores generate), or they connect only to related clock domains.

For many designs, that is all that's necessary to constrain the core. Some common core constraints that this section does not describe in detail are:

  • Adding multicycles between registers for analysis at a slower rate than the default analysis, increasing the time when data can be read. For example, a 10 ns clock period has a 10 ns setup relationship. If the data changes at a slower rate, or perhaps the registers switch at a slower rate due to a clock enable, then you can apply a multicycle that relaxes the setup relationship (opens the window so that valid data can pass). This is a multiple of the clock period, making the setup relationship 20 ns, 40 ns, and so on, while keeping the hold relationship at 0 ns. You generally apply these types of multicycles to paths.
  • You can also use multicycle when you want to advance the cycle in which data is read, shifting the timing window. This generally occurs when your design performs a small phase-shift on a clock. For example, if your design has two 10 ns clocks exiting a PLL, but the second clock has a 0.5 ns phase-shift, the default setup relationship from the main clock to the phase-shift clock is 0.5 ns and the hold relationship is -9.5 ns. Meeting a 0.5 ns setup relationship is nearly impossible, and most likely you intend the data to transfer in the next window. By adding a multicycle from the main clock to the phase-shift clock, the setup relationship becomes 10.5 ns and the hold relationship becomes 0.5 ns. You generally apply this multicycle between clocks.
  • Add a create_generated_clock to ripple clocks. When a register's output drives the clk port of another register, that is a ripple clock. Clocks do not propagate through registers, so you must apply the create_generated_clock constraint to all ripple clocks for correct analysis. Unconstrained ripple clocks appear in the Report Unconstrained Paths report, so you can easily recognize them. In general, avoid ripple clocks. Use a clock enable instead.
  • Add a create_generated_clock to clock mux outputs. Without this clock, all clocks propagate through the mux and are related. The Timing Analyzer analyzes paths downstream from the mux where one clock input feeds the source register and the other clock input feeds the destination, and vice-versa. Although this behavior can be valid, this is typically not the behavior you want. By applying create_generated_clock constraints on the mux output, which relates them to the clocks coming into the mux, you can correctly group these clocks with other clocks.