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_IDX_SRC_FREQ 0
23const ARG_IDX_TARGET_FREQ 1
24const ARG_IDX_LF_MODE 2
25const ARG_IDX_MIN_POWER 3
26
27const FEEDBACK_DIV_KEY "feedbackDiv"
28const REFERENCE_DIV_KEY "referenceDiv"
29const OUTPUT_DIV_KEY "outputDiv"
30
31const MIN_IN_FREQ 4
32const MAX_IN_FREQ 64
33const PFD_MIN 4
34const PFD_MAX 8
35
36# PLL OUTPUT_DIV bitfield allowable range
37const OUT_MIN 2
38const OUT_MAX 16
39
40# PLL REFERENCE_DIV bitfield allowable range
41const REF_MIN 1
42const REF_MAX 18
43
44# PLL FEEDBACK_DIV bitfield allowable ranges, LF and normal modes
45const FB_MAX_LF 56
46const FB_MIN 22
47const FB_MAX_NORM 112
48
49# PLL Fvco range allowable ranges, LF and normal modes
50const VCO_MIN_LF 170
51const VCO_MAX_LF 200
52const VCO_MIN_NORM 200
53const VCO_MAX_NORM 400
54
55const DOUBLE_COMPARE_EPSILON 0.00001
56const MAX_DOUBLE 1.0e308
57
58const SUCCESS 0
59const ERROR_ARG_COUNT 1
60const ERROR_ID 2
61const ERROR_ARG_VALUE 3
62
63set channelName stdout
64
65if {[chan names ModusToolbox] eq "ModusToolbox"} {
66    set channelName ModusToolbox
67}
68
69proc solve_pll {} {
70    if {$::argc != $::ARG_IDX_MIN_POWER + 1} {
71        error "PLL Solver requires 4 arguments:
72\tdouble srcFreqMHz - Source clock frequency for the PLL in MHz.
73\tdouble targetFreqMHz - Output frequency of the PLL in MHz.
74\tboolean lfMode - CLK_PLL_CONFIG register, PLL_LF_MODE bit.
75\tboolean minPower - Optimize for min power rather min jitter."
76        return $::ERROR_ARG_COUNT
77    }
78    set srcFreqMHz [lindex $::argv $::ARG_IDX_SRC_FREQ]
79    set targetFreqMHz [lindex $::argv $::ARG_IDX_TARGET_FREQ]
80    set lfMode [string is true [lindex $::argv $::ARG_IDX_LF_MODE]]
81    set minPower [string is true [lindex $::argv $::ARG_IDX_MIN_POWER]]
82    if {![string is double $srcFreqMHz] || ![string is double $targetFreqMHz]} {
83        error "Unable to parse argument values: either $srcFreqMHz or $targetFreqMHz is not a floating-point number."
84        return $::ERROR_ARG_VALUE
85    }
86    set srcFreqMHz [expr {double($srcFreqMHz)}]
87    set targetFreqMHz [expr {double($targetFreqMHz)}]
88    if {$srcFreqMHz < $::MIN_IN_FREQ || $::MAX_IN_FREQ < $srcFreqMHz} {
89        error "Invalid PLL input frequency '$srcFreqMHz'. Must be within the range $::MIN_IN_FREQ-$::MAX_IN_FREQ."
90        return $::ERROR_ARG_VALUE
91    }
92    return [solve_pll_internal $srcFreqMHz $targetFreqMHz $lfMode $minPower]
93}
94
95proc solve_pll_internal {srcFreqMHz targetFreqMHz lfMode minPower} {
96    set divFb $::FB_MIN
97    set divRef $::REF_MIN
98    set divOut $::OUT_MIN
99
100    set freqRatio [expr {$srcFreqMHz / $targetFreqMHz}]
101
102    set leastErrorFound $::MAX_DOUBLE
103    set fbMax [expr {$lfMode ? $::FB_MAX_LF : $::FB_MAX_NORM}]
104    for {set p $::FB_MIN} {$p <= $fbMax} {incr p} {
105        for {set q $::REF_MIN} {$q <= $::REF_MAX} {incr q} {
106            set pfdRefFreq [expr {$srcFreqMHz / $q}]
107            set vcoFreq [expr {$pfdRefFreq * $p}]
108
109            if {![is_pfdref_in_range $pfdRefFreq] || ![is_vco_in_range $vcoFreq $lfMode]} {
110                continue
111            }
112            for {set o $::OUT_MIN} {$o <= $::OUT_MAX} {incr o} {
113                set error [expr {abs(1 - (($freqRatio * $p) / ($q * $o)))}]
114                # If quality is equal, then minPower means minimize divOut, and not minPower means max divOut
115                if {$error < $leastErrorFound ||
116                    ([double_equals $error $leastErrorFound] &&
117                     (($divOut > $o) == $minPower))} {
118                    set leastErrorFound $error
119                    set divFb $p
120                    set divRef $q
121                    set divOut $o
122                }
123            }
124        }
125    }
126    if {$leastErrorFound == $::MAX_DOUBLE} {
127        return $::ERROR_ID
128    }
129    puts $::channelName "param:$::FEEDBACK_DIV_KEY=$divFb"
130    puts $::channelName "param:$::REFERENCE_DIV_KEY=$divRef"
131    puts $::channelName "param:$::OUTPUT_DIV_KEY=$divOut"
132    return $::SUCCESS
133}
134
135proc is_vco_in_range {vcoFreq lfMode} {
136    return [expr {$lfMode ? ($::VCO_MIN_LF <= $vcoFreq && $vcoFreq <= $::VCO_MAX_LF)
137                  : ($::VCO_MIN_NORM <= $vcoFreq && $vcoFreq <= $::VCO_MAX_NORM)}]
138}
139
140proc is_pfdref_in_range {pfdRefFrequency} {
141    return [expr {$::PFD_MIN <= $pfdRefFrequency && $pfdRefFrequency <= $::PFD_MAX}]
142}
143
144proc double_equals {a b} {
145    return [expr {abs($a - $b) < $::DOUBLE_COMPARE_EPSILON}]
146}
147
148solve_pll
149