Skip to content

Commit c0c1d72

Browse files
committed
rewrite code for listing valid fs values to include DACs and ADCs, and more of the xilinx docs
1 parent bbcb8b9 commit c0c1d72

1 file changed

Lines changed: 75 additions & 33 deletions

File tree

qick_lib/qick/qick.py

Lines changed: 75 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ def _read_freqs(self):
163163
tile = tiles[iTile]
164164
pllcfg = tile.PLLConfig
165165
tilecfg['f_ref'] = pllcfg['RefClkFreq']
166+
tilecfg['ref_div'] = pllcfg['RefClkDivider']
166167
tilecfg['fs_mult'] = pllcfg['FeedbackDivider']
167168
tilecfg['fs_div'] = pllcfg['RefClkDivider']*pllcfg['OutputDivider']
168169
# we could use SampleRate here, but it's the same
@@ -187,6 +188,8 @@ def _read_freqs(self):
187188
iTile, iBlock = chcfg['index']
188189
# copy the tile info
189190
chcfg.update(self['tiles'][tiletype][iTile])
191+
# clean up parameters that are only used at the tile level
192+
del chcfg['ref_div']
190193

191194
block = tiles[iTile].blocks[iBlock]
192195

@@ -223,52 +226,91 @@ def clocks_locked(self):
223226
.PLLLockStatus == 2 for iTile in self['tiles']['ADC']]
224227
return dac_locked, adc_locked
225228

226-
def check_samp_freq(self, target_fs, fref, words_per_axi):
227-
"""
228-
Check if the requested sampling frequency is supported.
229-
If not, it will return the closest achievable frequency.
230-
"""
229+
def valid_samp_freqs(self, tiletype, tile):
230+
tilecfg = self['tiles'][tiletype][tile]
231+
# reference clock after the PLL reference divider
232+
# this divider can't be changed by software, and Xilinx recommends keeping it at 1 for best phase noise
233+
refclk = tilecfg['f_ref']/tilecfg['ref_div']
234+
words_per_axi = tilecfg['fabric_div']
235+
236+
# Allowed divider values, see PG269 "PLL Parameters"
237+
# https://docs.amd.com/r/en-US/pg269-rf-data-converter/PLL-Parameters
238+
Fb_div_vals = np.arange(13,161, dtype=int)
239+
if self['ip_type'] == self.XRFDC_GEN3 and tiletype=='DAC':
240+
M_vals = np.concatenate([[1,2,3], np.arange(4,66,2)])
241+
VCO_range = [7863, 13760]
242+
else:
243+
M_vals = np.concatenate([[2,3], np.arange(4,66,2)])
244+
VCO_range = [8500, 13200]
245+
246+
VCO_possible = refclk * Fb_div_vals
247+
Fb_div_possible = Fb_div_vals[(VCO_possible>=VCO_range[0]) & (VCO_possible<=VCO_range[1])]
248+
fs_possible = refclk*(Fb_div_possible.T/M_vals[:,np.newaxis]).ravel()
249+
231250
# See DS926 "RF-ADC/RF-DAC to PL Interface Performance"
232251
# https://docs.amd.com/r/en-US/ds926-zynq-ultrascale-plus-rfsoc/RF-ADC/RF-DAC-to-PL-Interface-Switching-Characteristics
233252
if self['ip_type'] == self.XRFDC_GEN3:
234253
max_axi_clk = 614 # MHz
235254
else:
236255
max_axi_clk = 520 # MHz
256+
fs_possible = fs_possible[fs_possible <= words_per_axi*max_axi_clk]
237257

238-
fs_max = words_per_axi * max_axi_clk # MHz
258+
# Allowed ranges of sampling freqs
259+
# https://docs.amd.com/r/en-US/ds926-zynq-ultrascale-plus-rfsoc/RF-DAC-Electrical-Characteristics
260+
# https://docs.amd.com/r/en-US/ds926-zynq-ultrascale-plus-rfsoc/RF-ADC-Electrical-Characteristics
261+
if tiletype=='ADC' and self['hs_adc']:
262+
fs_min = 1000
263+
else:
264+
fs_min = 500
265+
fs_possible = fs_possible[fs_possible >= fs_min]
266+
if tiletype=='DAC':
267+
if self['ip_type'] == self.XRFDC_GEN3:
268+
fs_max = 9850
269+
else:
270+
fs_max = 6554
271+
else:
272+
if self['ip_type'] < self.XRFDC_GEN3:
273+
fs_max = 4096 # ZCU111
274+
else:
275+
if self['hs_adc']:
276+
fs_max = 5000
277+
else:
278+
fs_max = 2500
279+
fs_possible = fs_possible[fs_possible <= fs_max]
239280

240-
# Allowed divider values, see PG269 "PLL Parameters"
241-
# https://docs.amd.com/r/en-US/pg269-rf-data-converter/PLL-Parameters
242-
Fb_div_vals = np.arange(13,161, dtype=int)
243-
M_vals = np.insert(np.arange(4,66,2,dtype=int), 0, np.array([2,3]))
244-
245-
# Calculate Feedback Divider and M value
246-
all_ratios = np.clip(fref*(Fb_div_vals[:,np.newaxis].T / M_vals[:,np.newaxis]), a_min=None, a_max=fs_max)
247-
differences = np.abs(all_ratios - target_fs)
248-
indicies = np.where(differences == differences.min())
249-
M = M_vals[indicies[0][0]]
250-
Fb_div_val = Fb_div_vals[indicies[1][0]]
251-
fs_acheived = fref*Fb_div_val/M
252-
f_err = fs_acheived - target_fs
253-
logger.info(f'Requested Fs = {target_fs} MHz, Acheived Fs = {fs_acheived:.3f} MHz, Frequency Error = {f_err:.3f} MHz.')
254-
logger.debug(f'Fb_div = {Fb_div_val}, M = {M}, fs_max = {fs_max:.3f} MHz')
255-
if f_err > 10:
256-
logger.warning(f'Frequency error is {f_err:.3} MHz. Please check the PLL settings.')
257-
return fs_acheived
281+
# forbidden "hole" for Gen3 RFSoC DAC PLL
282+
# https://docs.amd.com/r/en-US/ds926-zynq-ultrascale-plus-rfsoc/RF-Converters-Clocking-Characteristics
283+
if self['ip_type'] == self.XRFDC_GEN3 and tiletype=='DAC':
284+
fs_possible = fs_possible[(fs_possible<=6882) | (fs_possible>=7863)]
285+
286+
fs_possible.sort()
287+
return fs_possible
288+
289+
def check_samp_freq(self, tiletype, tile, fs_target):
290+
"""
291+
Check if the requested sampling frequency is supported.
292+
If not, it will return the closest achievable frequency.
293+
"""
294+
# TODO: warn or error if you're raising the frequency
295+
fs_possible = self.valid_samp_freqs(tiletype, tile)
296+
fs_best = fs_possible[np.argmin(np.abs(fs_possible - fs_target))]
297+
fs_err = fs_best - fs_target
298+
logger.info('fs requested = %f MHz, best possible = %.3f MHz, error = %.3f MHz.'%(fs_target, fs_best, fs_err))
299+
if abs(fs_err) > 1:
300+
logger.warning('%s tile %d: requested fs %f.3 MHz could not be achieved, will use %f.3 MHz.'%(tiletype, tile, fs_target, fs_best))
301+
return fs_best
258302

259303
def set_adc_sample_rate(self, tile, fs):
260304
"""
261305
Set the ADC sample rate of a tile.
262306
"""
263-
tilecfg = self['tiles']['ADC'][tile]
264-
ref_clk_freq = tilecfg['f_ref']
265-
words_per_axi = tilecfg['fabric_div']
266-
fs_safe = self.check_samp_freq(fs, ref_clk_freq, words_per_axi)
267-
if fs_safe:
268-
logging.info(f'Programming ADC Tile {tile} to {fs_safe:.3f} MHz')
269-
self.adc_tiles[tile].DynamicPLLConfig(source=1,ref_clk_freq=ref_clk_freq, samp_rate=fs_safe)
270-
else:
271-
raise RuntimeError(f"Requested sampling frequency {fs} MHz is not supported.")
307+
tiletype = 'ADC'
308+
fs_best = self.check_samp_freq(tiletype, tile, fs)
309+
if abs(fs_best-fs) > 10:
310+
raise RuntimeError("%s tile %d: requested sampling frequency %f MHz is not supported, closest is %.3f."%(tiletype, tile, fs, fs_best))
311+
logging.info('programming %s tile %d to %.3f MHz'%(tiletype, tile, fs))
312+
f_ref = self['tiles'][tiletype][tile]['f_ref']
313+
self.adc_tiles[tile].DynamicPLLConfig(source=xrfdc.CLK_SRC_PLL, ref_clk_freq=f_ref, samp_rate=fs_best)
272314

273315
def configure_adc_sample_rates(self, adc_sample_rates):
274316
"""

0 commit comments

Comments
 (0)