1# Copyright 2020 Cypress Semiconductor Corporation 2# SPDX-License-Identifier: Apache-2.0 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16# From https://wiki.tcl-lang.org/page/constants 17proc const {name value} { 18 uplevel 1 [list set $name $value] 19 uplevel 1 [list trace var $name w {error constant} ] 20} 21 22const ARG_MODE_IDX 0 23 24const ARG_AUTO_IDX_SRC_FREQ 1 25const ARG_AUTO_IDX_TARGET_FREQ 2 26const ARG_AUTO_IDX_WCO_SRC 3 27const ARG_AUTO_IDX_SRC_ACCURACY 4 28 29const ARG_MANUAL_IDX_SRC_FREQ 1 30const ARG_MANUAL_IDX_MULT 2 31const ARG_MANUAL_IDX_DIV 3 32const ARG_MANUAL_IDX_LOCK_TOLERANCE 4 33const ARG_MANUAL_IDX_EN_OUT_DIV 5 34const ARG_MANUAL_IDX_WCO_SRC 6 35const ARG_MANUAL_IDX_SRC_ACCURACY 7 36 37const MIN_CCO_OUTPUT_FREQ 48 38const MIN_OUTPUT_FREQ [expr {$::MIN_CCO_OUTPUT_FREQ / 2}] 39const MAX_OUTPUT_FREQ 100 40const SRC_CCO_FREQ_RATIO 2.2 41 42const OUTPUT_DIV_ENABLED 1 43 44const CCO_RANGE [list "CY_SYSCLK_FLL_CCO_RANGE0" "CY_SYSCLK_FLL_CCO_RANGE1" "CY_SYSCLK_FLL_CCO_RANGE2" \ 45 "CY_SYSCLK_FLL_CCO_RANGE3" "CY_SYSCLK_FLL_CCO_RANGE4"] 46const TRIM_STEPS [list 0.0011034 0.001102 0.0011 0.0011 0.00117062] 47const F_MARGIN [list 43600000.0 58100000.0 77200000.0 103000000.0 132000000.0] 48 49const CONFIG_OUTPUT_MODE "CY_SYSCLK_FLLPLL_OUTPUT_OUTPUT" 50 51# igain and pgain bitfield values correspond to: 1/256, 1/128, ..., 4, 8 52const IGAINS_PGAINS [list 0.00390625 0.0078125 0.015625 0.03125 0.0625 0.125 \ 53 0.25 0.5 1.0 2.0 4.0 8.0] 54 55const SUCCESS 0 56const ERROR_ARG_COUNT 1 57const ERROR_ID 2 58const ERROR_ARG_VALUE 3 59 60set channelName stdout 61 62if {[chan names ModusToolbox] eq "ModusToolbox"} { 63 set channelName ModusToolbox 64} 65 66proc solve_fll {} { 67 if {$::argc < $::ARG_MODE_IDX + 1} { 68 print_arg_count_error 69 return $::ERROR_ARG_COUNT 70 } 71 if {[string compare -nocase [lindex $::argv $::ARG_MODE_IDX] "Auto"] == 0} { 72 return [parse_and_run_auto] 73 } 74 if {[string compare -nocase [lindex $::argv $::ARG_MODE_IDX] "Manual"] == 0} { 75 return [parse_and_run_manual] 76 } 77 return $::ERROR_ARG_VALUE 78} 79 80proc parse_and_run_auto {} { 81 if {$::argc != $::ARG_AUTO_IDX_SRC_ACCURACY + 1} { 82 print_arg_count_error 83 return $::ERROR_ARG_COUNT 84 } 85 set srcFreqMHz [lindex $::argv $::ARG_AUTO_IDX_SRC_FREQ] 86 set targetFreqMHz [lindex $::argv $::ARG_AUTO_IDX_TARGET_FREQ] 87 set wcoIsSource [string is true [lindex $::argv $::ARG_AUTO_IDX_WCO_SRC]] 88 set srcAccuracyPercent [lindex $::argv $::ARG_AUTO_IDX_SRC_ACCURACY] 89 if {![string is double $srcFreqMHz] || ![string is double $targetFreqMHz] || ![string is double $srcAccuracyPercent]} { 90 error "Unable to parse argument values: either $srcFreqMHz or $targetFreqMHz or $srcAccuracyPercent is not a floating-point number." 91 return $::ERROR_ARG_VALUE 92 } 93 set srcFreqMHz [expr {double($srcFreqMHz)}] 94 set targetFreqMHz [expr {double($targetFreqMHz)}] 95 set srcAccuracyPercent [expr {double($srcAccuracyPercent)}] 96 set retVal [verify_desired_frequency $srcFreqMHz $targetFreqMHz $::OUTPUT_DIV_ENABLED] 97 if {$retVal != $::SUCCESS} { 98 return $retVal 99 } 100 return [solve_auto $srcFreqMHz $targetFreqMHz $wcoIsSource [expr {abs($srcAccuracyPercent / 100.0)}]] 101} 102 103proc parse_and_run_manual {} { 104 if {$::argc != $::ARG_MANUAL_IDX_SRC_ACCURACY + 1} { 105 print_arg_count_error 106 return $::ERROR_ARG_COUNT 107 } 108 set srcFreqMHz [lindex $::argv $::ARG_MANUAL_IDX_SRC_FREQ] 109 set mult [lindex $::argv $::ARG_MANUAL_IDX_MULT] 110 set div [lindex $::argv $::ARG_MANUAL_IDX_DIV] 111 set lockTol [lindex $::argv $::ARG_MANUAL_IDX_LOCK_TOLERANCE] 112 set enOutDiv [lindex $::argv $::ARG_MANUAL_IDX_EN_OUT_DIV] 113 set wcoIsSource [string is true [lindex $::argv $::ARG_MANUAL_IDX_WCO_SRC]] 114 set srcAccuracyPercent [lindex $::argv $::ARG_MANUAL_IDX_SRC_ACCURACY] 115 if {![string is double $srcFreqMHz] || ![string is double $srcAccuracyPercent]} { 116 error "Unable to parse argument values: either $srcFreqMHz or $srcAccuracyPercent is not a floating-point number." 117 return $::ERROR_ARG_VALUE 118 } 119 if {![string is integer $mult] || ![string is integer $div] || ![string is integer $lockTol]} { 120 error "Unable to parse argument values: either $mult or $div or $lockTol is not an integer." 121 return $::ERROR_ARG_VALUE 122 } 123 set srcFreqMHz [expr {double($srcFreqMHz)}] 124 set srcAccuracyPercent [expr {double($srcAccuracyPercent)}] 125 126 set expectedFreq [expr {$srcFreqMHz * $mult / $div / ($::OUTPUT_DIV_ENABLED ? 2 : 1)}] 127 set retVal [verify_desired_frequency $srcFreqMHz $expectedFreq $::OUTPUT_DIV_ENABLED] 128 if {$retVal != $::SUCCESS} { 129 return $retVal 130 } 131 return [solve_manual $srcFreqMHz $mult $div $lockTol $enOutDiv $wcoIsSource [expr {abs($srcAccuracyPercent / 100.0)}]] 132} 133 134proc solve_auto {srcFreqMHz targetFreqMHz wcoIsSource srcAccuracy} { 135 # constants indexed by ccoIdx 136 # 1. Output division by 2 is always required. 137 set configEnableOutputDiv $::OUTPUT_DIV_ENABLED 138 # 2. Compute the target CCO frequency from the target output frequency and output division. 139 set ccoFreqMHz [expr {$targetFreqMHz * ($configEnableOutputDiv ? 2 : 1)}] 140 # 3. Compute the CCO range value from the CCO frequency. 141 set ccoIdx [get_cco_range_idx $ccoFreqMHz] 142 set configCcoRange [lindex $::CCO_RANGE $ccoIdx] 143 # 4. Compute the FLL reference divider value. refDiv is a constant if the WCO is the FLL source. 144 set configRefDiv [expr {$wcoIsSource ? 19 : int(ceil(($srcFreqMHz / $targetFreqMHz) * 250.0))}] 145 # 5. Compute the FLL multiplier value. 146 set configFllMult [expr {int(ceil($ccoFreqMHz / ($srcFreqMHz / $configRefDiv)))}] 147 # 6. Compute the lock tolerance. We assume CCO accuracy is 0.25%. 148 set CCO_ACCURACY 0.0025 149 set configLockTolerance [expr {int(ceil($configFllMult * (1.5 * ((1.0 + $CCO_ACCURACY) / (1.0 - $srcAccuracy) - 1.0))))}] 150 # 7. Compute the CCO igain and pgain. 151 set kcco [expr {([lindex $::TRIM_STEPS $ccoIdx] * [lindex $::F_MARGIN $ccoIdx]) / 1000.0}] 152 set ki_p [expr {850.0 / ($kcco * ($configRefDiv / $srcFreqMHz))}] 153 # 7a. Find the largest IGAIN value that is less than or equal to ki_p. 154 set configIgain [expr {[llength $::IGAINS_PGAINS] - 1}] 155 while {([lindex $::IGAINS_PGAINS $configIgain] > $ki_p) && ($configIgain != 0)} { 156 incr configIgain -1 157 } 158 # 7b. Decrement igain if the WCO is the FLL source. 159 if {$wcoIsSource && ($configIgain > 0)} { 160 incr configIgain -1 161 } 162 # 7c. Find the largest PGAIN value that is less than or equal to ki_p - IGAINS_PGAINS[igain] 163 set configPgain [expr {[llength $::IGAINS_PGAINS] - 1}] 164 while {([lindex $::IGAINS_PGAINS $configPgain] > ($ki_p - [lindex $::IGAINS_PGAINS $configIgain]) && ($configPgain != 0))} { 165 incr configPgain -1 166 } 167 # 7d. Decrement pgain if the WCO is the FLL source. 168 if {$wcoIsSource && ($configPgain > 0)} { 169 incr configPgain -1 170 } 171 # 8. Compute the CCO_FREQ bits in CLK_FLL_CONFIG4 register. 172 set configCcoFreq [expr {int(floor(log($ccoFreqMHz * 1000000 / [lindex $::F_MARGIN $ccoIdx]) / log(1.0 + [lindex $::TRIM_STEPS $ccoIdx])))}] 173 # 9. Compute the settling count, using a 1-usec settling time. Use a constant if the WCO is the FLL source. 174 set configSettlingCount [get_settling_count $wcoIsSource $srcFreqMHz $ccoFreqMHz $configRefDiv] 175 set accuracy [calculate_fll_accuracy $ccoFreqMHz $srcFreqMHz [lindex $::TRIM_STEPS $ccoIdx] $configRefDiv $configLockTolerance] 176 set accuracyString [format "%.04f" [expr {$accuracy * 100.0}]] 177 set enableOutputDiv [expr {$configEnableOutputDiv ? "true" : "false"}] 178 179 puts $::channelName "param:fllMult=$configFllMult" 180 puts $::channelName "param:refDiv=$configRefDiv" 181 puts $::channelName "param:ccoRange=$configCcoRange" 182 puts $::channelName "param:enableOutputDiv=$enableOutputDiv" 183 puts $::channelName "param:lockTolerance=$configLockTolerance" 184 puts $::channelName "param:igain=$configIgain" 185 puts $::channelName "param:pgain=$configPgain" 186 puts $::channelName "param:settlingCount=$configSettlingCount" 187 puts $::channelName "param:outputMode=$::CONFIG_OUTPUT_MODE" 188 puts $::channelName "param:cco_Freq=$configCcoFreq" 189 puts $::channelName "param:accuracy=$accuracyString" 190 return $::SUCCESS 191} 192 193proc solve_manual {srcFreqMHz configFllMult configRefDiv configLockTolerance configenOutDiv wcoIsSource srcAccuracy} { 194 # constants indexed by ccoIdx 195 # 1. Output division by 2 is always required. 196 set configEnableOutputDiv [expr {$configenOutDiv ? 1 : 0}] 197 set outputDiv [expr {$configEnableOutputDiv ? 2 : 1}] 198 set actualFreq [expr {(($srcFreqMHz * $configFllMult) / $configRefDiv) / $outputDiv}] 199 # 2. Compute the target CCO frequency from the target output frequency and output division. 200 set ccoFreqMHz [expr {$actualFreq * $outputDiv}] 201 # 3. Compute the CCO range value from the CCO frequency. 202 set ccoIdx [get_cco_range_idx $ccoFreqMHz] 203 set configCcoRange [lindex $::CCO_RANGE $ccoIdx] 204 # 7. Compute the CCO igain and pgain. 205 set kcco [expr {([lindex $::TRIM_STEPS $ccoIdx] * [lindex $::F_MARGIN $ccoIdx]) / 1000.0}] 206 set ki_p [expr {850.0 / ($kcco * ($configRefDiv / $srcFreqMHz))}] 207 # 7a. Find the largest IGAIN value that is less than or equal to ki_p. 208 set configIgain [expr {[llength $::IGAINS_PGAINS] - 1}] 209 while {([lindex $::IGAINS_PGAINS $configIgain] > $ki_p) && ($configIgain != 0)} { 210 incr configIgain -1 211 } 212 # 7b. Decrement igain if the WCO is the FLL source. 213 if {$wcoIsSource && ($configIgain > 0)} { 214 incr configIgain -1 215 } 216 # 7c. Find the largest PGAIN value that is less than or equal to ki_p - IGAINS_PGAINS[igain] 217 set configPgain [expr {[llength $::IGAINS_PGAINS] - 1}] 218 while {([lindex $::IGAINS_PGAINS $configPgain] > ($ki_p - [lindex $::IGAINS_PGAINS $configIgain]) && ($configPgain != 0))} { 219 incr configPgain -1 220 } 221 # 7d. Decrement pgain if the WCO is the FLL source. 222 if {$wcoIsSource && ($configPgain > 0)} { 223 incr configPgain -1 224 } 225 # 8. Compute the CCO_FREQ bits in CLK_FLL_CONFIG4 register. 226 set configCcoFreq [expr {int(floor(log($ccoFreqMHz * 1000000 / [lindex $::F_MARGIN $ccoIdx]) / log(1.0 + [lindex $::TRIM_STEPS $ccoIdx])))}] 227 # 9. Compute the settling count, using a 1-usec settling time. Use a constant if the WCO is the FLL source. 228 set configSettlingCount [get_settling_count $wcoIsSource $srcFreqMHz $ccoFreqMHz $configRefDiv] 229 set accuracy [calculate_fll_accuracy $ccoFreqMHz $srcFreqMHz [lindex $::TRIM_STEPS $ccoIdx] $configRefDiv $configLockTolerance] 230 set accuracyString [format "%.04f" [expr {$accuracy * 100.0}]] 231 set enableOutputDiv [expr {$configEnableOutputDiv ? "true" : "false"}] 232 233 puts $::channelName "param:fllMult=$configFllMult" 234 puts $::channelName "param:refDiv=$configRefDiv" 235 puts $::channelName "param:ccoRange=$configCcoRange" 236 puts $::channelName "param:enableOutputDiv=$enableOutputDiv" 237 puts $::channelName "param:lockTolerance=$configLockTolerance" 238 puts $::channelName "param:igain=$configIgain" 239 puts $::channelName "param:pgain=$configPgain" 240 puts $::channelName "param:settlingCount=$configSettlingCount" 241 puts $::channelName "param:outputMode=$::CONFIG_OUTPUT_MODE" 242 puts $::channelName "param:cco_Freq=$configCcoFreq" 243 puts $::channelName "param:accuracy=$accuracyString" 244 return $::SUCCESS 245} 246 247proc get_settling_count {isWco srcFreqMHz ccoFreqMHz configRefDiv} { 248 set ttref [expr {$configRefDiv / ($srcFreqMHz * 1000.0)}] 249 set testval [expr {6.0 / ($ccoFreqMHz * 1000.0)}] 250 set divval [expr {ceil($srcFreqMHz)}] 251 set altval [expr {ceil(($divval / $ttref) + 1.0)}] 252 return [expr {int($isWco ? 200 : (($ttref > $testval) ? $divval : (($divval > $altval) ? $divval : $altval)))}] 253} 254 255proc get_cco_range_idx {ccoFreqMHz} { 256 if {$ccoFreqMHz >= 150.3392} { 257 return 4 258 } elseif {$ccoFreqMHz >= 113.00938} { 259 return 3 260 } elseif {$ccoFreqMHz >= 94.9487} { 261 return 2 262 } elseif {$ccoFreqMHz >= 63.8556} { 263 return 1 264 } else { 265 return 0 266 } 267} 268 269proc calculate_fll_accuracy {targetCcoFreq sourceRefFreq trimStep refDiv lockTolerance} { 270 set measure [expr {(1.0 / $refDiv) * ($sourceRefFreq / $targetCcoFreq)}] 271 set ss_2 [expr {$trimStep / 2.0}] 272 set fllPrecision [expr {($measure > $ss_2) ? $measure : $ss_2}] 273 return [expr {$fllPrecision * ($lockTolerance + 2)}] 274} 275 276proc verify_desired_frequency {sourceFreq targetFreq outputDiv} { 277 set ratio [expr {$outputDiv ? ($::SRC_CCO_FREQ_RATIO / 2) : $::SRC_CCO_FREQ_RATIO}] 278 if {$targetFreq < $::MIN_OUTPUT_FREQ || $::MAX_OUTPUT_FREQ < $targetFreq} { 279 error "Invalid FLL target frequency '$targetFreq'. Must be within the range $::MIN_OUTPUT_FREQ-$::MAX_OUTPUT_FREQ." 280 return $::ERROR_ARG_VALUE 281 } 282 if {($sourceFreq * $ratio) >= $targetFreq} { 283 error "Invalid FLL source/target frequency ratio. Target must be at least $ratio faster than the source." 284 return $::ERROR_ARG_VALUE 285 } 286 return $::SUCCESS 287} 288 289proc print_arg_count_error {} { 290 error "FLL Solver requires 5 arguments in automatic mode: 291\t\"auto\" automatic - Use automatic solver based on desired frequency 292\tdouble srcFreqMHz - Source clock frequency for the FLL in MHz. 293\tdouble targetFreqMHz - Output frequency of the FLL in MHz. 294\tboolean wcoIsSource - Is the WCO sourcing the FLL? 295\tdouble sourceAccuracy - FLL reference clock accuracy (%)? 296FLL Solver requires 7 arguments in manual mode: 297\t\"manual\" automatic - Use automatic solver based on desired frequency 298\tdouble srcFreqMHz - Source clock frequency for the FLL in MHz. 299\tint Mult - Clock frequency multiplier. 300\tint Div - Clock frequency divider. 301\tint lockTolerance - FLL lock tolerance. 302\tboolean wcoIsSource - Is the WCO sourcing the FLL? 303\tdouble sourceAccuracy - FLL reference clock accuracy (%)?" 304} 305 306solve_fll 307