1#!/usr/bin/env python3 2 3import argparse 4import sys 5 6# Fixed hardware parameters 7fbdiv_range = range(16, 320 + 1) 8postdiv_range = range(1, 7 + 1) 9ref_min = 5 10refdiv_min = 1 11refdiv_max = 63 12 13def validRefdiv(string): 14 if ((int(string) < refdiv_min) or (int(string) > refdiv_max)): 15 raise ValueError("REFDIV must be in the range {} to {}".format(refdiv_min, refdiv_max)) 16 return int(string) 17 18parser = argparse.ArgumentParser(description="PLL parameter calculator") 19parser.add_argument("--input", "-i", default=12, help="Input (reference) frequency. Default 12 MHz", type=float) 20parser.add_argument("--ref-min", default=5, help="Override minimum reference frequency. Default 5 MHz", type=float) 21parser.add_argument("--vco-max", default=1600, help="Override maximum VCO frequency. Default 1600 MHz", type=float) 22parser.add_argument("--vco-min", default=750, help="Override minimum VCO frequency. Default 750 MHz", type=float) 23parser.add_argument("--cmake", action="store_true", help="Print out a CMake snippet to apply the selected PLL parameters to your program") 24parser.add_argument("--cmake-only", action="store_true", help="Same as --cmake, but do not print anything other than the CMake output") 25parser.add_argument("--cmake-executable-name", default="<program>", help="Set the executable name to use in the generated CMake output") 26parser.add_argument("--lock-refdiv", help="Lock REFDIV to specified number in the range {} to {}".format(refdiv_min, refdiv_max), type=validRefdiv) 27parser.add_argument("--low-vco", "-l", action="store_true", help="Use a lower VCO frequency when possible. This reduces power consumption, at the cost of increased jitter") 28parser.add_argument("output", help="Output frequency in MHz.", type=float) 29args = parser.parse_args() 30 31refdiv_range = range(refdiv_min, max(refdiv_min, min(refdiv_max, int(args.input / args.ref_min))) + 1) 32if args.lock_refdiv: 33 print("Locking REFDIV to", args.lock_refdiv) 34 refdiv_range = [args.lock_refdiv] 35 36best = (0, 0, 0, 0, 0, 0) 37best_margin = args.output 38 39for refdiv in refdiv_range: 40 for fbdiv in fbdiv_range: 41 vco = args.input / refdiv * fbdiv 42 if vco < args.vco_min or vco > args.vco_max: 43 continue 44 # pd1 is inner loop so that we prefer higher ratios of pd1:pd2 45 for pd2 in postdiv_range: 46 for pd1 in postdiv_range: 47 out = vco / pd1 / pd2 48 margin = abs(out - args.output) 49 vco_is_better = vco < best[5] if args.low_vco else vco > best[5] 50 if ((vco * 1000) % (pd1 * pd2)): 51 continue 52 if margin < best_margin or (abs(margin - best_margin) < 1e-9 and vco_is_better): 53 best = (out, fbdiv, pd1, pd2, refdiv, vco) 54 best_margin = margin 55 56best_out, best_fbdiv, best_pd1, best_pd2, best_refdiv, best_vco = best 57 58if best[0] > 0: 59 cmake_output = \ 60f"""target_compile_definitions({args.cmake_executable_name} PRIVATE 61 PLL_SYS_REFDIV={best_refdiv} 62 PLL_SYS_VCO_FREQ_HZ={int((args.input * 1_000_000) / best_refdiv * best_fbdiv)} 63 PLL_SYS_POSTDIV1={best_pd1} 64 PLL_SYS_POSTDIV2={best_pd2} 65 SYS_CLK_HZ={int((args.input * 1_000_000) / (best_refdiv * best_pd1 * best_pd2) * best_fbdiv)} 66) 67""" 68 if not args.cmake_only: 69 print("Requested: {} MHz".format(args.output)) 70 print("Achieved: {} MHz".format(best_out)) 71 print("REFDIV: {}".format(best_refdiv)) 72 print("FBDIV: {} (VCO = {} MHz)".format(best_fbdiv, args.input / best_refdiv * best_fbdiv)) 73 print("PD1: {}".format(best_pd1)) 74 print("PD2: {}".format(best_pd2)) 75 if best_refdiv != 1: 76 print( 77 "\nThis requires a non-default REFDIV value.\n" 78 "Add the following to your CMakeLists.txt to apply the REFDIV:\n" 79 ) 80 elif args.cmake or args.cmake_only: 81 print("") 82 if args.cmake or args.cmake_only or best_refdiv != 1: 83 print(cmake_output) 84else: 85 sys.exit("No solution found") 86