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 DEFAULT_RESOLUTION 12
23const MIN_SAMPLE_TIME_NS 83.0
24const OTHER_ADC_CLOCKS 2
25
26# Actual sample time is 1/2 clock less than specified
27# for PSoC4 and 1 clock less for PSoC 6.
28# See SAR_v2 SAMPLE_TIME01 register description.
29const ADC_CLOCKS_NOT_SAMPLING 1
30
31const APERTURE_TIMER_COUNT 4     ;# There are four timers in the SAR
32const APERTURE_TIMER_MIN 2       ;# The minimum value for each timer is 2 clocks
33const APERTURE_TIMER_MAX 1023
34
35# ADC clock constants. These are the outer hardware limits.
36# Actual limits depend on the configuration and may be tighter.
37# See AdcClockFreqMin_Hz and AdcClockFreqMax_Hz properties.
38const ADC_CLOCK_MAX_HZ 60000000
39const ADC_CLOCK_MIN_HZ 1000000
40
41const MIN_ADC_CLOCK_DIV 2
42
43const MAX_NUM_CHANNELS 16
44
45const ARG_IDX_HF_CLK_RATE 0
46const ARG_IDX_IS_FIXED_CLOCK 1
47const ARG_IDX_CLK_RATE 2
48const ARG_IDX_MIN_CLK_RATE 3
49const ARG_IDX_MAX_CLK_RATE 4
50const ARG_IDX_IS_SINGLE_SHOT 5
51const ARG_IDX_SAMPLE_RATE 6
52const ARG_IDX_NUM_CHANNELS 7
53const ARG_IDX_CHANNEL_INFO 8
54
55const NUM_ARGS_PER_CHANNEL 2
56
57const SUCCESS 0
58const ERROR_ARG_COUNT 1
59const ERROR_ID 2
60const ERROR_ARG_VALUE 3
61const ERROR_FAIL_SCHEDULING 4
62
63array set SAMPLE_PER_SCAN_MAP {
64    "CY_SAR_AVG_CNT_2" {2}
65    "CY_SAR_AVG_CNT_4" {4}
66    "CY_SAR_AVG_CNT_8" {8}
67    "CY_SAR_AVG_CNT_16" {16}
68    "CY_SAR_AVG_CNT_32" {32}
69    "CY_SAR_AVG_CNT_64" {64}
70    "CY_SAR_AVG_CNT_128" {128}
71    "CY_SAR_AVG_CNT_256" {256}
72}
73
74# Indices for the list-based implementation of the CyTimingInfo structure
75const TIMING_IDEAL_SAMPLE_ADC_CLOCK 0
76const TIMING_SAMPLE_COUNT 1
77const TIMING_IS_ENABLED 2
78const TIMING_CHANNEL_NUM 3
79
80proc construct_timing_info {minAcqAdcClocksNeeded samplesPerScan enabled chanNum} {
81    # Note: the order of this list must match the above indices
82    return [list $minAcqAdcClocksNeeded $samplesPerScan $enabled $chanNum]
83}
84
85# Indices for the list-based implementation of the CySchedChan structure
86# Input properties
87const CHAN_MIN_ACQ_TIME_NS 0
88const CHAN_SAMPLES_PER_SCAN 1
89const CHAN_IS_ENABLED 2
90const CHAN_RESOLUTION 3
91# Output properties
92const CHAN_TIMER 4
93const CHAN_ACHIEVED_ACQ_TIME_NS 5
94const CHAN_ACHIEVED_SAMPLE_TIME_NS 6
95const CHAN_MIN_ACQ_ADC_CLOCKS_NEEDED 7    ;# Minimum number of ADC clocks to acquire the channel
96const CHAN_MIN_CONV_ADC_CLOCKS_NEEDED 8   ;# Minimum number of ADC clocks needed for a conversion
97const CHAN_MIN_TOTAL_ADC_CLOCKS_NEEDED 9  ;# Minimum number of ADC clocks needed for sampling (acquisition + conversion)
98const CHAN_MAPPED_ACQ_ADC_CLOCKS 10       ;# Actual number of ADC clocks to acquire the channel
99const CHAN_MAPPED_TOTAL_ADC_CLOCKS 11     ;# Actual number of ADC clocks needed for sampling (acquisition + conversion)
100
101proc construct_sched_chan {acqTime samples} {
102    # Note: the order of this list must match the above indices
103    return [list [expr {max2($acqTime, $::MIN_SAMPLE_TIME_NS)}] $samples [expr {$samples != 0}] $::DEFAULT_RESOLUTION \
104                0 0.0 0.0 0 [expr {$::DEFAULT_RESOLUTION + $::OTHER_ADC_CLOCKS}] 0 0 0]
105}
106
107set hfClockRate 0.0
108set isFixedClock false
109set fixedAdcClockRate 0.0
110set minClockRate 0.0
111set maxClockRate 0.0
112set isSingleShot False
113set targetSampleRate 0.0
114set numChannels 0
115set channels {}
116set adcClockDivider 0.0
117
118set aperturesAdcClock [lrepeat $::APERTURE_TIMER_COUNT $::APERTURE_TIMER_MIN]
119set achievedSampleRate 0
120set achievedScanPeriod_us 0.0
121set cost 0
122
123set min_aperture $::APERTURE_TIMER_MAX
124set min_sample_time_sel 0
125set inj_sample_time_sel ""
126set injMinAcqTimeNs 0
127set injSamplesPerScan 0
128set inj_achieved_acq_time 0.0
129set inj_achieved_sample_time 0.0
130
131set channelName stdout
132
133if {[chan names ModusToolbox] eq "ModusToolbox"} {
134    set channelName ModusToolbox
135}
136
137namespace eval tcl {
138    namespace eval mathfunc {
139        proc min2 {arg1 arg2} {
140            if {$arg1 < $arg2} {
141                return $arg1
142            } else {
143                return $arg2
144            }
145        }
146
147        proc max2 {arg1 arg2} {
148            if {$arg1 > $arg2} {
149                return $arg1
150            } else {
151                return $arg2
152            }
153        }
154    }
155}
156
157proc schedule_sar {} {
158    if {$::argc < $::ARG_IDX_NUM_CHANNELS + 1} {
159        error "Not enough base arguments."
160        return $::ERROR_ARG_COUNT
161    }
162    set ::hfClockRate [lindex $::argv $::ARG_IDX_HF_CLK_RATE]
163    set ::isFixedClock [string is true [lindex $::argv $::ARG_IDX_IS_FIXED_CLOCK]]
164    set ::fixedAdcClockRate [lindex $::argv $::ARG_IDX_CLK_RATE]
165    set ::minClockRate [lindex $::argv $::ARG_IDX_MIN_CLK_RATE]
166    set ::maxClockRate [lindex $::argv $::ARG_IDX_MAX_CLK_RATE]
167    set ::isSingleShot [string is true [lindex $::argv $::ARG_IDX_IS_SINGLE_SHOT]]
168    set ::targetSampleRate [lindex $::argv $::ARG_IDX_SAMPLE_RATE]
169    set ::numChannels [lindex $::argv $::ARG_IDX_NUM_CHANNELS]
170    if {![string is double $::hfClockRate] || ![string is double $::fixedAdcClockRate] ||
171        ![string is double $::minClockRate] || ![string is double $::maxClockRate] ||
172        ![string is double $::targetSampleRate]} {
173        error "Unable to parse argument values: One of $::hfClockRate, $::fixedAdcClockRate, $::minClockRate, $::maxClockRate, $::targetSampleRate
174is not a floating-point number."
175        return $::ERROR_ARG_VALUE
176    }
177    set ::hfClockRate [expr {double($::hfClockRate)}]
178    set ::fixedAdcClockRate [expr {double($::fixedAdcClockRate)}]
179    set ::minClockRate [expr {double($::minClockRate)}]
180    set ::maxClockRate [expr {double($::maxClockRate)}]
181    set ::targetSampleRate [expr {double($::targetSampleRate)}]
182    if {![string is integer $::numChannels]} {
183        error "Unable to parse argument values: $::numChannels is not an integer."
184        return $::ERROR_ARG_VALUE
185    }
186    if {$::argc < $::ARG_IDX_CHANNEL_INFO + $::numChannels * $::NUM_ARGS_PER_CHANNEL + $::NUM_ARGS_PER_CHANNEL} {
187        error "Not enough channel information."
188        return $::ERROR_ARG_COUNT
189    }
190    for {set i 0} {$i < $::numChannels} {incr i} {
191        set minAcqTimeNs [lindex $::argv [expr {$::ARG_IDX_CHANNEL_INFO + $i * $::NUM_ARGS_PER_CHANNEL}]]
192        set samplesPerScan [get_samples_per_scan [lindex $::argv [expr {$::ARG_IDX_CHANNEL_INFO + $i * $::NUM_ARGS_PER_CHANNEL + 1}]]]
193        if {![string is double $minAcqTimeNs]} {
194            error "Unable to parse argument values: $minAcqTimeNs is not a floating-point number."
195            return $::ERROR_ARG_VALUE
196        }
197        set minAcqTimeNs [expr {double($minAcqTimeNs)}]
198        lappend ::channels [construct_sched_chan $minAcqTimeNs $samplesPerScan]
199    }
200
201    set ::injMinAcqTimeNs [lindex $::argv [expr {$::ARG_IDX_CHANNEL_INFO + 16 * $::NUM_ARGS_PER_CHANNEL}]]
202    set ::injSamplesPerScan [get_samples_per_scan [lindex $::argv [expr {$::ARG_IDX_CHANNEL_INFO + 16 * $::NUM_ARGS_PER_CHANNEL + 1}]]]
203
204    set adcClockRate $::fixedAdcClockRate
205    if {!$::isFixedClock} {
206        set ::adcClockDivider [select_adc_clock_divider]
207        set adcClockRate [expr {$::hfClockRate / $::adcClockDivider}]
208    }
209    if {$::isSingleShot} {
210        set ::solution [schedule_adc_one_shot $adcClockRate]
211    } else {
212        set ::solution [schedule_adc_only_with_fixed_adc_clock $adcClockRate]
213    }
214
215    # If scan period <= 0, scheduling did not succeed.
216    set scanPeriodAdcClock [find_mapped_scan_adc_clocks]
217    if {$scanPeriodAdcClock <= 0} {
218        error "Scheduling failed."
219        return $::ERROR_FAIL_SCHEDULING
220    }
221    set ::achievedSampleRate [expr {int(round($adcClockRate / $scanPeriodAdcClock))}]
222    set ::achievedScanPeriod_us [expr {1e6 / $::achievedSampleRate}]
223    for {set chanNum 0} {$chanNum < [llength $::channels]} {incr chanNum} {
224        set adcClocks [expr {[lindex $::channels $chanNum $::CHAN_MAPPED_ACQ_ADC_CLOCKS] - $::ADC_CLOCKS_NOT_SAMPLING}]
225        lset ::channels $chanNum $::CHAN_ACHIEVED_ACQ_TIME_NS [expr {round(1e9 * $adcClocks / $adcClockRate)}]
226        lset ::channels $chanNum $::CHAN_ACHIEVED_SAMPLE_TIME_NS [expr {round(1e9 * [lindex $::channels $chanNum $::CHAN_MAPPED_TOTAL_ADC_CLOCKS] / $adcClockRate)}]
227    }
228
229    for {set sampleTime 0} {$sampleTime < [llength $::aperturesAdcClock]} {incr sampleTime} {
230        set aperture [lindex $::aperturesAdcClock $sampleTime]
231        if {$aperture >= $::APERTURE_TIMER_MIN} {
232            puts $::channelName [format "param:sample_time_%d=%d" $sampleTime $aperture]
233            if {$aperture < $::min_aperture} {
234                set ::min_aperture $aperture
235                set ::min_sample_time_sel $sampleTime
236            }
237        } else {
238        puts $::channelName [format "param:sample_time_%d=%d" $sampleTime $::APERTURE_TIMER_MIN]
239        }
240    }
241
242    puts $::channelName [format "param:inj_aperture=%d" $::min_aperture]
243
244    set injMappedTotalAdcClocks [expr {($::min_aperture + [expr {$::DEFAULT_RESOLUTION + $::OTHER_ADC_CLOCKS}]) * $::injSamplesPerScan}]
245    set ::inj_achieved_acq_time [expr {round(1e9 * [expr {$::min_aperture - $::ADC_CLOCKS_NOT_SAMPLING}] / $adcClockRate)}]
246    set ::inj_achieved_sample_time [expr {round(1e9 * $injMappedTotalAdcClocks / $adcClockRate)}]
247
248    write_output
249    return $::SUCCESS
250}
251
252proc get_samples_per_scan {arg} {
253    if {[info exists ::SAMPLE_PER_SCAN_MAP($arg)]} {
254        return $::SAMPLE_PER_SCAN_MAP($arg)
255    } elseif {[string is integer $arg]} {
256        return $arg
257    } else {
258        error "Unable to parse argument values: $arg is not an integer."
259        exit $::ERROR_ARG_VALUE
260    }
261}
262
263proc write_output {} {
264    puts $::channelName "param:achieved_sample_rate=$::achievedSampleRate"
265    puts $::channelName [format "param:achieved_sample_period=%.2f us" $::achievedScanPeriod_us]
266    puts $::channelName [format "param:adc_clock_divider=%.1f" $::adcClockDivider]
267
268    for {set chanNum 0} {$chanNum < $::MAX_NUM_CHANNELS} {incr chanNum} {
269        set ch_achieved_acq_time 0
270        set ch_achieved_sample_time 0
271        set ch_sample_time_sel ""
272        if {$chanNum < [llength $::channels]} {
273            set ch_achieved_acq_time [expr {int([lindex $::channels $chanNum $::CHAN_ACHIEVED_ACQ_TIME_NS])}]
274            set ch_achieved_sample_time [expr {int([lindex $::channels $chanNum $::CHAN_ACHIEVED_SAMPLE_TIME_NS])}]
275            set ch_sample_time_sel [format "CY_SAR_CHAN_SAMPLE_TIME_%d" [lindex $::channels $chanNum $::CHAN_TIMER]]
276        }
277        puts $::channelName [format "param:ch%d_achieved_acq_time=%d ns" $chanNum $ch_achieved_acq_time]
278        puts $::channelName [format "param:ch%d_achieved_sample_time=%d ns" $chanNum $ch_achieved_sample_time]
279        puts $::channelName [format "param:ch%d_sample_time_sel=%s" $chanNum $ch_sample_time_sel]
280    }
281
282    puts $::channelName [format "param:sample_time_min=%d" $::min_aperture]
283    set inj_sample_time_sel [format "CY_SAR_CHAN_SAMPLE_TIME_%d" $::min_sample_time_sel]
284    puts $::channelName [format "param:inj_sample_time_sel=%s" $inj_sample_time_sel]
285    puts $::channelName [format "param:inj_achieved_acq_time=%d ns" $::inj_achieved_acq_time]
286    puts $::channelName [format "param:inj_achieved_sample_time=%d ns" $::inj_achieved_sample_time]
287}
288
289proc schedule_adc_only_with_fixed_adc_clock {adcClockRate} {
290    # This method must be called to initialize channel sample and conversion ADC clock counts.
291    find_min_scan_adc_clocks $adcClockRate
292
293    # Set up data structures for optimization without padding.
294    set chanTimesNoPad [lsort -dictionary [get_channel_timing_info]]
295
296    set apertureClocksNoPad $::aperturesAdcClock
297
298    # Set up data structures for optimization with padding.
299    set chanTimesPad $chanTimesNoPad
300    set apertureClocksPad $apertureClocksNoPad
301    set padIndex [find_padding_channel $chanTimesPad]
302    if {$padIndex != 0} {
303        set padChan [lindex $::chanTimesPad $padIndex]
304        set ::chanTimesPad [lreplace $::chanTimesPad $padIndex $padIndex]
305        set ::chanTimesPad [linsert $::chanTimesPad 0 $padChan]
306    }
307
308    # Find solution with minimum ADC clocks without padding.
309    optimize_apertures "chanTimesNoPad" 0 [llength $chanTimesNoPad] "apertureClocksNoPad" 0 [llength $apertureClocksNoPad]
310
311    set ::aperturesAdcClock $apertureClocksNoPad
312    set_channel_timers 0 [llength $::aperturesAdcClock]
313    set mappedMinScanAdcClocksNoPad [find_mapped_scan_adc_clocks]
314
315    # Find solution with minimum ADC clocks, reserving one aperture timer for padding.
316    optimize_apertures "chanTimesPad" 1 [llength $chanTimesPad] "apertureClocksPad" 1 [llength $apertureClocksPad]
317
318    lset apertureClocksPad 0 [lindex $chanTimesPad 0 $::TIMING_IDEAL_SAMPLE_ADC_CLOCK]
319
320    set ::aperturesAdcClock $apertureClocksPad
321    set_channel_timers 1 [llength $::aperturesAdcClock]
322    lset ::channels [lindex $chanTimesPad 0 $::TIMING_CHANNEL_NUM] $::CHAN_TIMER 0
323    set mappedMinScanAdcClocksPad [find_mapped_scan_adc_clocks]
324
325    set targetScanAdcClocks [adc_clocks $::targetSampleRate $adcClockRate]
326
327    if {$mappedMinScanAdcClocksPad <= $targetScanAdcClocks} {
328        # What if more than one channel needs to be padded?
329        set padAmount [expr {$targetScanAdcClocks - $mappedMinScanAdcClocksPad}]
330        set samplesPerScan [lindex $::channels [lindex $chanTimesPad 0 $::TIMING_CHANNEL_NUM] $::CHAN_SAMPLES_PER_SCAN]
331        if {$samplesPerScan > 1} {
332            set padAmount [expr {($padAmount + ($samplesPerScan / 2)) / $samplesPerScan}]
333        }
334        lset ::aperturesAdcClock 0 [expr {min2([lindex $::aperturesAdcClock 0] + $padAmount, $::APERTURE_TIMER_MAX)}]
335    } elseif {abs($mappedMinScanAdcClocksNoPad - $targetScanAdcClocks) < abs($mappedMinScanAdcClocksPad - $targetScanAdcClocks)} {
336        # If unpadded timers overshoot less than padded timers, switch to unpadded timers.
337        set ::aperturesAdcClock $apertureClocksNoPad
338        set_channel_timers 0 [llength $::aperturesAdcClock]
339    }
340    # else, stick with padded timers without adding padding.
341}
342
343proc schedule_adc_one_shot {adcClockRate} {
344    # This method must be called to initialize channel sample and conversion ADC clock counts.
345    find_min_scan_adc_clocks $adcClockRate
346
347    # Set up data structures for optimization without padding.
348    set chanTimesNoPad [lsort -dictionary [get_channel_timing_info]]
349
350    # Find solution with minimum ADC clocks without padding.
351    optimize_apertures "chanTimesNoPad" 0 [llength $chanTimesNoPad] "::aperturesAdcClock" 0 [llength $::aperturesAdcClock]
352
353    set_channel_timers 0 [llength $::aperturesAdcClock]
354}
355
356# Find the minimum number of ADC clocks required to scan all channels.
357# Also stores the minimum number of ADC clocks in the channel table.
358# Assumes each channel has its own aperture timer.
359proc find_min_scan_adc_clocks {adcClockRate} {
360    set minScanAdcClocks 0
361    set newChannels {}
362
363    foreach chan $::channels {
364        lset chan $::CHAN_MIN_ACQ_ADC_CLOCKS_NEEDED [aperture_adc_clocks [lindex $chan $::CHAN_MIN_ACQ_TIME_NS] $adcClockRate]
365        if {[lindex $chan $::CHAN_IS_ENABLED]} {
366            lset chan $::CHAN_MIN_TOTAL_ADC_CLOCKS_NEEDED [expr {[lindex $chan $::CHAN_MIN_ACQ_ADC_CLOCKS_NEEDED] + [lindex $chan $::CHAN_MIN_CONV_ADC_CLOCKS_NEEDED] * [lindex $chan $::CHAN_SAMPLES_PER_SCAN]}]
367        } else {
368            lset chan $::CHAN_MIN_TOTAL_ADC_CLOCKS_NEEDED 0
369        }
370        incr minScanAdcClocks [lindex $chan $::CHAN_MIN_TOTAL_ADC_CLOCKS_NEEDED]
371        lappend newChannels $chan
372    }
373    set ::channels $newChannels
374    return $minScanAdcClocks
375}
376
377# Calculate number of ADC clocks required for specified sample time.
378# Number of ADC clocks required to cover sample time
379proc aperture_adc_clocks {apertureNs adcClockRate} {
380    # Convert to ns.
381    set adcClockNs [expr {1.0e9 / $adcClockRate}]
382
383    # Find number of clocks required to cover aperture period.
384    # Down 1/2 ns for rounding (the ceiling function below will adjust this)
385    set apertureClocks [expr {$apertureNs / $adcClockNs + $::ADC_CLOCKS_NOT_SAMPLING - (0.5 / $adcClockNs)}]
386
387    # Convert to integral number of clocks and enforce limits
388    set clocks [expr {int(ceil($apertureClocks))}]
389
390    # Enforce limits.
391    set clocks [expr {min2($clocks, $::APERTURE_TIMER_MAX)}]
392    set clocks [expr {max2($clocks, $::APERTURE_TIMER_MIN)}]
393
394    return $clocks
395}
396
397# Extract timing info for optimization from channel data.
398proc get_channel_timing_info {} {
399    set chanTimings {}
400
401    for {set chanNum 0} {$chanNum < [llength $::channels]} {incr chanNum} {
402        set chan [lindex $::channels $chanNum]
403        lappend chanTimings [construct_timing_info [lindex $chan $::CHAN_MIN_ACQ_ADC_CLOCKS_NEEDED] [lindex $chan $::CHAN_SAMPLES_PER_SCAN] [lindex $chan $::CHAN_IS_ENABLED] $chanNum]
404    }
405    return $chanTimings
406}
407
408# This function (in the original Java code) always returns 0.
409# TODO: Make it do something useful.
410proc compare_padding_merit {chanTimes $bestPadIndex $padIndex} {
411    return 0
412}
413
414# Find best channel in range to use for ADC clock padding.
415# Prefer channels with no averaging or minimum averaging and lowest ideal ADC clock count.
416# The chanTimes list must be sorted.
417proc find_padding_channel {chanTimes} {
418    set bestPadIndex 0
419    for {set padIndex 1} {$padIndex < [llength $chanTimes]} {incr padIndex} {
420        if {[compare_padding_merit $chanTimes $bestPadIndex $padIndex] > 0} {
421            set bestPadIndex $padIndex
422        }
423    }
424    return $bestPadIndex
425}
426
427# Recursive min cost optimizer.
428#
429# "Cost" is the difference between the number of ADC clocks used
430# by aperture timers to cover the given channels and the number of ADC
431# clocks that would be required if each channel had its own timer.
432#
433# The channel timing info list must be sorted.
434#
435# Each recursive call consumes one aperture timer and zero or more
436# channels. The recursion terminates when there are either no timers
437# or no channels left.
438#
439# Returns min cost timer settings in apertureClocks list.
440proc optimize_apertures {chanTimesName chanBase chanTop apertureClocksName apertureBase apertureTop} {
441    upvar $chanTimesName chanTimes            ;# "passed by reference", tcl style
442    upvar $apertureClocksName apertureClocks  ;# "passed by reference", tcl style
443    if {$chanTop == $chanBase} {
444        # All channels have been processed. Zero out remainder of timer list.
445        for {set index $apertureBase} {$index < $apertureTop} {incr index} {
446            lset apertureClocks $index 0
447        }
448        return 0
449    }
450    if {$apertureBase + 1 == $apertureTop} {
451        # There is only one timer left.  Use it to cover all the remaining channels.
452        set apertureTime [lindex $chanTimes [expr {$chanTop - 1}] $::TIMING_IDEAL_SAMPLE_ADC_CLOCK]
453        lset apertureClocks $apertureBase $apertureTime
454        return [calc_aperture_cost $chanTimes $chanBase $chanTop $apertureTime]
455    }
456
457    # Loop over possible timer values making recursive calls.  Save the best found.
458    set minCost 9876543210
459    set minClocks $apertureClocks
460
461    set chanNum $chanBase
462    while {$chanNum < $chanTop} {
463        set apertureTime [lindex $chanTimes $chanNum $::TIMING_IDEAL_SAMPLE_ADC_CLOCK]
464        set nextChanNum $chanNum
465        while {$nextChanNum < $chanTop && [lindex $chanTimes $nextChanNum $::TIMING_IDEAL_SAMPLE_ADC_CLOCK] == $apertureTime} {
466            incr nextChanNum
467        }
468
469        set branchClocks $apertureClocks
470        set branchCost [expr {[calc_aperture_cost $chanTimes $chanBase $nextChanNum $apertureTime]
471                              + [optimize_apertures "chanTimes" $nextChanNum $chanTop "branchClocks" [expr {$apertureBase + 1}] $apertureTop]}]
472        if {$minCost > $branchCost} {
473            set minCost $branchCost
474            set minClocks [lreplace $branchClocks $apertureBase $apertureBase $apertureTime]
475        }
476
477        set chanNum $nextChanNum
478    }
479
480    for {set index $apertureBase} {$index < $apertureTop} {incr index} {
481        lset apertureClocks $index [lindex $minClocks $index]
482    }
483    return $minCost
484}
485
486# Calculate cost of covering the specified channels with one timer setting.
487# Requires channel ideal sample times to have been calculated.
488proc calc_aperture_cost {chanTimes chanBase chanTop aperture} {
489    set cost 0
490
491    for {set chanNum $chanBase} {$chanNum < $chanTop} {incr chanNum} {
492        set chan [lindex $chanTimes $chanNum]
493        set chanExcess [expr {($aperture - [lindex $chan $::TIMING_IDEAL_SAMPLE_ADC_CLOCK]) * [lindex $chan $::TIMING_SAMPLE_COUNT]}]
494        incr cost $chanExcess
495    }
496    return $cost
497}
498
499# Assign best fit aperture timers to specified channels.
500proc set_channel_timers {timerBase timerTop} {
501    for {set chanNum 0} {$chanNum < [llength $::channels]} {incr chanNum} {
502        for {set timer $timerBase} {$timer < $timerTop} {incr timer} {
503            if {[lindex $::channels $chanNum $::CHAN_MIN_ACQ_ADC_CLOCKS_NEEDED] <= [lindex $::aperturesAdcClock $timer]} {
504                lset ::channels $chanNum $::CHAN_TIMER $timer
505                break
506            }
507        }
508    }
509}
510
511# Find number of ADC clocks required for scan with specified aperture timers.
512proc find_mapped_scan_adc_clocks {} {
513    set mappedScanAdcClocks 0
514
515    for {set chanNum 0} {$chanNum < [llength $::channels]} {incr chanNum} {
516        set mappedAcqAdcClocks [lindex $::aperturesAdcClock [lindex $::channels $chanNum $::CHAN_TIMER]]
517        lset ::channels $chanNum $::CHAN_MAPPED_ACQ_ADC_CLOCKS $mappedAcqAdcClocks
518        lset ::channels $chanNum $::CHAN_MAPPED_TOTAL_ADC_CLOCKS \
519            [expr {($mappedAcqAdcClocks + [lindex $::channels $chanNum $::CHAN_MIN_CONV_ADC_CLOCKS_NEEDED]) * [lindex $::channels $chanNum $::CHAN_SAMPLES_PER_SCAN]}]
520
521        incr mappedScanAdcClocks [lindex $::channels $chanNum $::CHAN_MAPPED_TOTAL_ADC_CLOCKS]
522    }
523    return $mappedScanAdcClocks
524}
525
526# Find number of ADC clocks required for target scan frequency.
527proc adc_clocks {targetFrequency adcClockRate} {
528    if {$targetFrequency == 0} {
529        return 2147483647   ;# This is the expected behavior of casting Infinity to an int, according to the Java version
530    }
531    return [expr {int(ceil($adcClockRate / $targetFrequency))}]
532}
533
534# Calculate ADC clock divider for internal ADC clocks.
535# Results are stored in the adcClockDivider property.
536proc select_adc_clock_divider {} {
537    if {$::isSingleShot} {
538        set ::adcClockDivider $::MIN_ADC_CLOCK_DIV
539        return
540    }
541    # Find minimum sample time and ADC conversion clock count for scan.
542    set minScan [find_adc_clock_parameters]
543    set minScanSample_ns [lindex $minScan 0]
544    set minScanConvAdcClock [lindex $minScan 1]
545
546    set minScanSampleTime_s [expr {$minScanSample_ns / 1.0e9}]
547
548    # Select a clock divider that will use about 512 padding clock periods to hit the target clock rate.
549    set targetScanPeriod_s [expr {1.0 / $::targetSampleRate}]
550    set allowedConversionTime_s [expr {$targetScanPeriod_s - $minScanSampleTime_s}]
551    set adcClockHz [expr {($minScanConvAdcClock + 512) / $allowedConversionTime_s}]
552    set ::adcClockDivider [expr {int(round($::hfClockRate / $adcClockHz))}]
553    set maxDivider [expr {int(floor($::hfClockRate / $::minClockRate))}]
554    set minDivider [expr {int(ceil($::hfClockRate / $::maxClockRate))}]
555    set ::adcClockDivider [expr {min2($::adcClockDivider, $maxDivider)}]
556    set ::adcClockDivider [expr {max2($::adcClockDivider, $minDivider)}]
557    set ::adcClockDivider [expr {max2($::adcClockDivider, $::MIN_ADC_CLOCK_DIV)}]
558}
559
560proc find_adc_clock_parameters {} {
561    set minScanSampleNs 0
562    set minScanConvAdcClocks 0
563
564    foreach chan $::channels {
565        set minSampleNs [lindex $chan $::CHAN_MIN_ACQ_TIME_NS]
566        set minConvAdcClocks [lindex $chan $::CHAN_MIN_CONV_ADC_CLOCKS_NEEDED]
567        if {[lindex $chan $::CHAN_SAMPLES_PER_SCAN] > 1} {
568            set minSampleNs [expr {$minSampleNs * [lindex $chan $::CHAN_SAMPLES_PER_SCAN]}]
569            set minConvAdcClocks [expr {$minConvAdcClocks * [lindex $chan $::CHAN_SAMPLES_PER_SCAN]}]
570            incr minScanSampleNs $minSampleNs
571            incr minScanConvAdcClocks $minConvAdcClocks
572        }
573    }
574    return [list $minScanSampleNs $minScanConvAdcClocks]
575}
576
577schedule_sar
578