@@ -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