proc test_TIE_cases {} { # Setup which tests you want to run. # 1 test is enabled # 0 test is disabled # # By default all are enabled: # set run_test_1a 1 set run_test_1b 1 set run_test_1cd 1 set run_input_test 1 set run_output_test 1 puts "#######################################################" puts "# " puts "# Design features: Designs involving synchronous clock domain crossing between clocks from" puts "# a. MMCM and a PLL ( Including MMCM->PLL cascade and MMCM)" puts "# b. Two MMCMs in different banks" puts "# c. Two PLLs in different banks" puts "# d. MMCM->PLL cascade and PLL, even if in the same bank" puts "#" puts "# Mitigation steps:" puts "# 1. Consolidate the critical paths to use only one clock " puts "# modification element, if possible - one PLL clocking the entire path" puts "# 2. If MMCM is used, switch to PLL." puts "# 3. Get rid of MMCM->PLL cascade, if any" puts "# 4. Increase input clock frequency to the maximum frequency possible" puts "# 5. Increase VCO frequency to the maximum possible" puts "#" puts "#######################################################" ####################################################### # Get the TIE Values associated with each of the clocks ####################################################### array set ClkTIE_Values [Get_TIE_Values] if {$run_test_1a == 0 } { puts "\n\n Test Case 1a is disabled" } else { puts "\n\nRunning Test Case 1a" puts "Checking for CDCs between MMCMs and PLLs\n\n" # Find all MMCMs and all PLLs set MMCMs [get_cells -quiet -hier -filter {ref_name =~ *MMCM*ADV*}] set PLLs [get_cells -quiet -hier -filter {ref_name =~ *PLL*ADV*}] if {([llength $PLLs] == 0) || ([llength $MMCMs] == 0)} { puts "No need for case 1.a, design does not have MMCMs and PLLs" } else { # Create two arrays that will hold all of the clocks array set mmcmClks {}; array set pllClks {}; # Fill the array with all of the clocks foreach mmcm $MMCMs { set mmcmClks($mmcm) [get_clocks -of [get_pins -filter {direction == out} -of $mmcm]] } foreach pll $PLLs { set pllClks($pll) [get_clocks -of [get_pins -filter {direction == out} -of $pll]] } # Check all CDCs that can go between MMCMs and PLLs. foreach mmcm $MMCMs { foreach pll $PLLs { # check for paths from the MMCM to the PLL set paths_mmcm_to_pll [llength [get_timing_paths -quiet -from [get_clocks $mmcmClks($mmcm)] -to [get_clocks $pllClks($pll)]]] set paths_pll_to_mmcm [llength [get_timing_paths -quiet -from [get_clocks $pllClks($pll)] -to [get_clocks $mmcmClks($mmcm)]]] if {[llength $paths_mmcm_to_pll] > 0 || [llength $paths_pll_to_mmcm] > 0} { foreach mclk $mmcmClks($mmcm) { foreach pclk $pllClks($pll) { if {[return_if_related $mclk $pclk "1a"] == 1} { [check_for_tie $mclk $pclk $ClkTIE_Values($mclk)] } if {[return_if_related $pclk $mclk "1a"] == 1} { [check_for_tie $pclk $mclk $ClkTIE_Values($pclk)] } } } } else { puts "There are no timing paths between $mmcm and $pll"; } } } } } if {$run_test_1b == 0 } { puts "\n\n Test Case 1a ib disabled" } else { puts "\n\nRunning Test Case 1b" puts "Checking for CDCs between two MMCMs in different banks\n\n" array set checked {}; # Create array that will hold all of the clocks if {[llength $MMCMs] > 1} { array set mmcmClks {}; #Fill the array with all of the clocks foreach mmcm $MMCMs { set mmcmClks($mmcm) [get_clocks -of [get_pins -filter {direction == out} -of $mmcm]] } foreach mmcm1 $MMCMs { foreach mmcm2 $MMCMs { if {$mmcm1 != $mmcm2} { if {[info exists checked($mmcm1$mmcm2)] == 0} { set checked($mmcm1$mmcm2) 1; set checked($mmcm2$mmcm1) 1; # check for paths from the MMCM to the PLL set paths_mmcm1_to_mmcm2 [llength [get_timing_paths -quiet -from [get_clocks $mmcmClks($mmcm1)] -to [get_clocks $mmcmClks($mmcm2)]]] set paths_mmcm2_to_mmcm1 [llength [get_timing_paths -quiet -from [get_clocks $mmcmClks($mmcm2)] -to [get_clocks $mmcmClks($mmcm1)]]] if {[llength $paths_mmcm1_to_mmcm2] > 0 || [llength $paths_mmcm2_to_mmcm1] > 0} { foreach mclk1 $mmcmClks($mmcm1) { foreach mclk2 $mmcmClks($mmcm2) { if {[return_if_related $mclk1 $mclk2 "1b"] == 1} { [check_for_tie $mclk1 $mclk2 $ClkTIE_Values($mclk1)] } if {[return_if_related $mclk2 $mclk1 "1b"] == 1} { [check_for_tie $mclk2 $mclk1 $ClkTIE_Values($mclk2)] } } } } else { puts "There are no timing paths between $mmcm1 and $mmcm2"; } } } } } } else { puts "There are [llength $MMCMs] MMCM in the design. Case 1.b is not applicable" } } if {$run_test_1cd == 0 } { puts "\n\n Test Case 1c 1d is disabled" } else { puts "\n\n Running Test Case 1c 1d \n\n" array set checked {}; set PLLs [get_cells -quiet -hier -filter {ref_name =~ *PLL*ADV*}] # Create array that will hold all of the clocks array set pllClks {}; #Fill the array with all of the clocks foreach pll $PLLs { set pllClks($pll) [get_clocks -of [get_pins -filter {direction == out} -of $pll]] } foreach pll1 $PLLs { foreach pll2 $PLLs { if {$pll1 != $pll2} { #check to see if the check has already been done if {[info exists checked($pll1$pll2)] == 0} { set checked($pll1$pll2) 1; set checked($pll2$pll1) 1; #check for clock region set CR1 [get_property CLOCK_REGION $pll1]; set CR2 [get_property CLOCK_REGION $pll2]; #Also check to see if there is an MMCM on the path of the PLL. If so we check for TIE regardless of if #in the same Clock region set mmcm_is_present [check_for_mmcm_on_path $pll1 $pll2]; if {$CR1 != $CR2 || $mmcm_is_present == 1} { set paths_pll1_to_pll2 [llength [get_timing_paths -quiet -from [get_clocks $pllClks($pll1)] -to [get_clocks $pllClks($pll2)]]] set paths_pll2_to_pll1 [llength [get_timing_paths -quiet -from [get_clocks $pllClks($pll2)] -to [get_clocks $pllClks($pll1)]]] if {[llength $paths_pll1_to_pll2] > 0 || [llength $paths_pll2_to_pll1] >0} { foreach pclk1 $pllClks($pll1) { foreach pclk2 $pllClks($pll2) { if {[return_if_related $pclk1 $pclk2 "1c"] == 1} { [check_for_tie $pclk1 $pclk2 $ClkTIE_Values($pclk1)] } if {[return_if_related $pclk2 $pclk1 "1c"] == 1} { [check_for_tie $pclk2 $pclk1 $ClkTIE_Values($pclk2)] } } } } else { puts "There are no timing paths between $pll1 and $pll2"; } } else { puts "$pll1 and $pll2 are in the same clock region and they have no MMCM in path..." } } } } } } if {$run_input_test == 0 } { puts "\n\n Test Case for TIE on inputs is disabled" } else { puts "\n\n#######################################################" puts "# Use Case #2:" puts "# Design features: System Synchronous (or synchronous " puts "# phase unknown) or Asynchronous Select I/O receiver " puts "# interfaces." puts "#" puts "# This factor should be applied as additional error " puts "# (Data Valid window closure) for the receiver margin " puts "# calculations. If there is enough receiver margin left " puts "# with the additional error, the receiver interface is " puts "# immune to post-CRC read back induced jitter and no " puts "# further actions are needed. If not, please refer to " puts "# the mitigation section." puts "#######################################################" #Find all inputs ports and determine the associated clock and corresponding TIE value puts "\n\nFinding all Input ports of the design and listing TIE value."; puts "User must go in and see if the interfaces have a potential issue or not\n\n" array set InputPortClks {} set inputs [get_ports -filter {direction == in && IS_GT_TERM == 0}]; foreach port $inputs { set clock [get_property -quiet ENDPOINT_CLOCK [get_timing_paths -quiet -from [get_ports $port]]] if {[info exists InputPortClks($clock)] == 0} { #Doesn't exist so start the list set InputPortClks($clock) $port; } else { set InputPortClks($clock) "$InputPortClks($clock) $port" } } puts "Input Port, Clock, TIE Value" foreach {Clk Ports} [array get InputPortClks] { #Check to see if there is an associated TIE value for the clock if {[info exists ClkTIE_Values($Clk)] == 0} { set TIE "NA"; } else { set TIE $ClkTIE_Values($Clk) } foreach input $Ports { if {[llength $Clk] == 0} { puts "$input, Async, $TIE" } else { puts "$input, $Clk, $TIE" } } } } if {$run_output_test == 0 } { puts "\n\n Test Case for TIE on outputs is disabled" } else { puts "#######################################################" puts "# Use Case #3:" puts "#" puts "# Design features: Transmit I/O interfaces with no " puts "# associated forwarded clock (System synchronous or " puts "# asynchronous interface transmitters)" puts "#" puts "# This factor should be applied as additional transmit" puts "# error (Data Valid window closure) to the far-end " puts "# receiver margin calculations. If there is enough " puts "# receiver margin left, the system is immune to post-CRC " puts "# read back induced jitter and no further actions are needed." puts "#" puts "#######################################################" set output_ports [get_ports -filter {direction == out && IS_GT_TERM == 0}] array set PortClks {} foreach port $output_ports { set clock [get_property -quiet STARTPOINT_CLOCK [get_timing_paths -quiet -to [get_ports $port]]] if {[info exists PortClks($clock)] == 0} { #Doesn't exist so start the list set PortClks($clock) $port; } else { set PortClks($clock) "$PortClks($clock) $port" } } foreach {Clk Ports} [array get PortClks] { #Check to see if there is an associated TIE value for the clock if {[info exists ClkTIE_Values($Clk)] == 0} { set TIE "NA"; } else { set TIE $ClkTIE_Values($Clk) } if {[llength $Clk] == 0} { puts "\nOutput Clock: ASYNC_OUTPUTS" } else { puts "\nOutput Clock: $Clk TIE Jitter is: $TIE" } foreach output $Ports { puts " Port $output" } } puts "\n\n" puts "#######################################################" puts "# Use Case #4:" puts "#" puts "# Design features: Source Synchronous Transmit I/O " puts "# interfaces with data/clock spanning more than 1 bank" puts "# that are clocked by clock modification elements" puts "# (MMCMs or PLLs) from multiple banks" puts "#" puts "# This factor should be applied as additional transmit" puts "# error (Data Valid window closure) for the far-end " puts "# receiver margin calculations. If there is enough " puts "# receiver margin left, the system is immune to post-CRC" puts "# read back induced jitter and no further actions are" puts "# needed. If not, please refer to the mitigation section." puts "#######################################################" foreach {Clk Ports} [array get PortClks] { #Check to see if there is an associated TIE value for the clock if {[info exists ClkTIE_Values($Clk)] == 0} { set TIE "NA"; } else { set TIE $ClkTIE_Values($Clk) } foreach output $Ports { puts " Port $output, IOBANK [get_property IOBANK [get_ports $output]], TIE $TIE" } } } } proc get_cascaded_pll {CLK} { set PLL [get_cells -quiet -of [get_pins -leaf -of [get_nets -of [get_clocks $CLK]]] -filter {ref_name =~ *PLL*ADV}] if {[llength $PLL] == 0} { return 0; } else { return $PLL; } } proc Get_TIE_Values {} { # Step 1: Find all MMCMs and start building an Array for TIE values for each clock # Also look for any cascaded PLL which will take on the MMCM TIE value set MMCMs [get_cells -quiet -hier -filter {ref_name =~ *MMCM*ADV*}] array set Clock_TIE_value {}; array set CascadedPLLs {}; foreach mmcm $MMCMs { set MMCM_TIE [return_tie $mmcm]; set mmcm_clocks [get_clocks -of $mmcm]; #Apply the TIE value to each associated clock foreach clk $mmcm_clocks { set Clock_TIE_value($clk) $MMCM_TIE; #check for cascaded PLL on each MMCM clock #returns 0 if no PLL, otherwise returns PLL name #if there is a PLL, it takes the MMCM TIE value set PLL [get_cascaded_pll $clk] if {$PLL != 0} { set CascadedPLLs($PLL) $MMCM_TIE; } } } # Step 2: Find all PLLs, if it is cascaded then it takes on the MMCM TIE value set PLLs [get_cells -quiet -hier -filter {ref_name =~ *PLL*ADV*}] foreach pll $PLLs { set PLL_TIE [return_tie $pll]; # In MMCM to PLL cascade scenario, add the TIE values which is pessimistic if {[info exists CascadedPLLs($pll)] == 1} { set PLL_TIE [expr $CascadedPLLs($pll) + $PLL_TIE]; } set pll_clocks [get_clocks -of $pll]; foreach pclk $pll_clocks { set Clock_TIE_value($pclk) $PLL_TIE; } } puts "\n\nHere are the different TIE values that will be taken into account for this design:" parray Clock_TIE_value; puts "\n\n" return [array get Clock_TIE_value]; } proc return_tie { CMT } { set mmcm [string match MMCM* [get_property ref_name [get_cells $CMT]]] set pll [string match PLL* [get_property ref_name [get_cells $CMT]]] set part [get_property PART [get_designs]] #7 series is different than the VU or KU devices. This proc can be updated as needed. set v7 [string match xc7* $part] set vu [string match xcv* $part] set ku [string match xck* $part] set CLKIN_PERIOD [get_property CLKIN1_PERIOD $CMT] set MULT [get_property CLKFBOUT_MULT_F $CMT] ############################################################# # TIE jitter estimation numbers # Values updated: 21 Nov 2018 (entered values from PPT # (Nokia_Post CRC Readback_PLL_Aggression_rev8.pptx) ############################################################## if {$v7 == 1} { set MMCM_TIE_HIGH_FREQ 0.0; # 400ps if CLKIN is >25MHz set MMCM_TIE_LOW_FREQ 0.4; # 400ps if CLKIN is <25MHz set PLL_TIE_LOW_FREQ 1.0; # 1000ps if CLKIN is <25MHz set PLL_TIE_MED_FREQ 0.4; # 400ps if CLKIN between 25MHz and 50MHz set PLL_TIE_HIGH_FREQ 0.2; # 200ps if CLKIN is >50MHz } elseif {$vu ==1 || $ku == 1} { set MMCM_400MHz 1.0; # 1000ps if Clkin > 400MHz set MMCM_150_400MHz 1.25; # 1250ps if Clkin 150 to 400MHz set MMCM_50_150MHz 1.5; # 1500ps if Clkin 50 to 150MHz set MMCM_50_M 1.5; # 1500 if Clkin <50MHz and M is not 32 or 64 set MMCM_50_M_32 2.3; # 2300 if Clkin <50MHz and M is 32 set MMCM_50_M_64 3.0; # 3000 if Clkin <50MHz and M is 64 set PLL_400MHz 0.25; # 250ps if CLKIN is >400MHz set PLL_200_400MHz 0.35; # 350ps if CLKIN between 200 to 400MHz set PLL_100_200MHz 0.475; # 475ps if CLKIN between 100 to 200MHz set PLL_100_M16 0.375; # 375ps if CLKIN <=100MHz and M=16 set PLL_100 0.525; # 525ps if CLKIN <=100MHz and M!=16 } if {$mmcm == 1} { if {$v7 == 1} { if {$CLKIN_PERIOD < 40 } { set TIE $MMCM_TIE_HIGH_FREQ; } else { set TIE $MMCM_TIE_LOW_FREQ; } } elseif {$vu ==1 || $ku == 1} { if {$CLKIN_PERIOD < 2.5 } { set TIE $MMCM_400MHz; } elseif {$CLKIN_PERIOD < 6.67} { set TIE $MMCM_150_400MHz; } elseif {$CLKIN_PERIOD < 20 } { set TIE $MMCM_50_150MHz; } elseif {$MULT == 32} { set TIE $MMCM_50_M_32; } elseif {$MULT == 64} { set TIE $MMCM_50_M_64; } else { set TIE $MMCM_50_M; } } } if {$pll == 1} { if {$v7 == 1} { if {$CLKIN_PERIOD < 20 } { set TIE $PLL_TIE_HIGH_FREQ; } elseif {$CLKIN_PERIOD < 40 } { set TIE $PLL_TIE_MED_FREQ; } else { set TIE $PLL_TIE_LOW_FREQ; } } elseif {$vu ==1 || $ku == 1} { if {$CLKIN_PERIOD < 2.5 } { set TIE $PLL_400MHz; } elseif {$CLKIN_PERIOD < 5} { set TIE $PLL_200_400MHz; } elseif {$CLKIN_PERIOD < 10} { set TIE $PLL_100_200MHz; } elseif {$MULT == 16} { set TIE $PLL_100_M16; } else { set TIE $PLL_100; } } } return $TIE; } proc check_for_tie {CLKA CLKB TIE} { puts "Checking Slack & Hold for paths between $CLKA and $CLKB" #get setup & hold slack set setup_slack [get_property -quiet SLACK [get_timing_paths -quiet -from [get_clocks $CLKA] -to [get_clocks $CLKB] -delay_type max]] set hold_slack [get_property -quiet SLACK [get_timing_paths -quiet -from [get_clocks $CLKA] -to [get_clocks $CLKB] -delay_type min]] if {[llength $setup_slack] > 0} { #check to see if we have more slack than TIE jitter if {$setup_slack > $TIE} { puts "Current Setup slack of $setup_slack is greater than TIE jitter $TIE" } else { puts "Current Setup slack: $setup_slack, TIE jitter $TIE" puts "***** ERROR: Need extra margin between $CLKA and $CLKB domains ****** \n" } } else { puts "Exception on Setup path: [get_property -quiet exception [get_timing_paths -quiet -from [get_clocks $CLKA] -to [get_clocks $CLKB]]]" } if {[llength $hold_slack] > 0} { #check to see if we have more slack than TIE jitter if {$hold_slack > $TIE} { puts "Current Hold slack of $hold_slack is greater than TIE jitter $TIE" } else { puts "\n ***** ERROR: Need extra margin between these clock domains ****** \n" puts "Current Hold slack: $hold_slack, TIE jitter $TIE" } } else { puts "Exception on Hold path: [get_property -quiet exception [get_timing_paths -quiet -from [get_clocks $CLKA] -to [get_clocks $CLKB]]]" } } proc return_if_related { clkA clkB tst} { puts "\nChecking paths between $clkA and $clkB" set paths_A_to_B [get_timing_paths -quiet -from [get_clocks $clkA] -to [get_clocks $clkB] -max 100000 -delay_type min_max] if {[llength $paths_A_to_B] > 0} { set paths_exist 1; puts "There are [llength $paths_A_to_B] timing paths from $clkA to $clkB"; [report_timing -quiet -from [get_clocks $clkA] -to [get_clocks $clkB] -max [llength $paths_A_to_B] -name ${tst}_$clkA$clkB] } else { set paths_exist 0; puts "No Timing Paths between $clkA and $clkB" } return $paths_exist; } proc check_for_mmcm_on_path { PLL1 PLL2} { set CLKIN_PIN [get_pll_pin_name]; set pins [get_pins "$PLL1/$CLKIN_PIN $PLL2/$CLKIN_PIN"] set MMCM [get_cells -quiet -of [get_timing_paths -quiet -to $pins -max 100] -filter {ref_name =~ *MMCM*ADV*}] if {[llength $MMCM] == 0} { puts "No cascaded MMCMs found"; return 0; } else { puts "Found $MMCM cascaded into one of the PLLs" return 1; } } proc get_pll_pin_name {} { #Find out what device this is set part [get_property PART [get_designs]] #7 series is different than the VU or KU devices. This proc can be updated as needed. set v7 [string match xc7* $part] set vu [string match xcv* $part] set ku [string match xck* $part] if {$v7 == 1} { return "CLKIN1"; } elseif {($vu == 1) || ($ku == 1)} { return "CLKIN"; } else { return "ERROR" } }