@@ -26,7 +26,8 @@ def enob(sndr, places=1):
2626def sndr_sfdr (spectrum , freq , fs , nfft , leak , full_scale = 0 ):
2727 """Get SNDR and SFDR."""
2828 # Zero the DC bin
29- spectrum [0 ] = 0
29+ for i in range (0 , leak + 1 ):
30+ spectrum [i ] = 0
3031 bin_sig = np .argmax (spectrum )
3132 psig = sum (spectrum [i ] for i in range (bin_sig - leak , bin_sig + leak + 1 ))
3233 spectrum_n = spectrum
@@ -79,7 +80,7 @@ def sndr_sfdr(spectrum, freq, fs, nfft, leak, full_scale=0):
7980 return stats
8081
8182
82- def find_harmonics (spectrum , freq , nfft , bin_sig , psig , harms = 5 , leak = 20 ):
83+ def find_harmonics (spectrum , freq , nfft , bin_sig , psig , harms = 5 , leak = 20 , fscale = 1e6 ):
8384 """Get the harmonic contents of the data."""
8485 harm_stats = {"harm" : {}}
8586 harm_index = 2
@@ -109,7 +110,7 @@ def find_harmonics(spectrum, freq, nfft, bin_sig, psig, harms=5, leak=20):
109110
110111 harm_stats ["harm" ][harm_index ]["bin" ] = bin_harm_max
111112 harm_stats ["harm" ][harm_index ]["power" ] = pwr_max
112- harm_stats ["harm" ][harm_index ]["freq" ] = round (freq [bin_harm ] / 1e6 , 1 )
113+ harm_stats ["harm" ][harm_index ]["freq" ] = round (freq [bin_harm ] / fscale , 1 )
113114 harm_stats ["harm" ][harm_index ]["dBc" ] = dBW (pwr_max / psig )
114115 harm_stats ["harm" ][harm_index ]["dB" ] = dBW (pwr_max )
115116
@@ -151,6 +152,7 @@ def plot_spectrum(
151152 window = "rectangular" ,
152153 no_plot = False ,
153154 yaxis = "power" ,
155+ fscale = "MHz" ,
154156):
155157 """Plot Power Spectrum for input signal."""
156158 wsize = data .size
@@ -159,10 +161,21 @@ def plot_spectrum(
159161 "hanning" : np .hanning (wsize ),
160162 }
161163
164+ fscalar = {
165+ "Hz" : 1 ,
166+ "kHz" : 1e3 ,
167+ "MHz" : 1e6 ,
168+ "GHz" : 1e9 ,
169+ }
170+
162171 if window not in windows :
163172 print (f"WARNING: { window } not implemented. Defaulting to 'rectangular'." )
164173 window = "rectangular"
165174
175+ if fscale not in fscalar :
176+ print (f"WARNING: { fscale } not implemented. Defaulting to 'MHz'." )
177+ fscale = "MHz"
178+
166179 wscale = {
167180 "rectangular" : 1.0 ,
168181 "hanning" : 1.633 ,
@@ -179,6 +192,8 @@ def plot_spectrum(
179192
180193 pwr_dB = 10 * np .log10 (pwr ) - scalar
181194
195+ xscale = fscalar [fscale ]
196+
182197 sndr_stats = sndr_sfdr (pwr , freq , fs , nfft , leak = leak , full_scale = full_scale )
183198 harm_stats = find_harmonics (
184199 pwr ,
@@ -188,19 +203,20 @@ def plot_spectrum(
188203 sndr_stats ["sig" ]["power" ],
189204 harms = harmonics ,
190205 leak = leak ,
206+ fscale = xscale ,
191207 )
192208
193209 stats = {** sndr_stats , ** harm_stats }
194210
195211 if not no_plot :
196- plt_str = get_plot_string (stats , full_scale , fs , nfft , window )
212+ plt_str = get_plot_string (stats , full_scale , fs , nfft , window , xscale , fscale )
197213
198214 fig , ax = plt .subplots (figsize = (15 , 8 ))
199- ax .plot (freq / 1e6 , pwr_dB )
215+ ax .plot (freq / xscale , pwr_dB )
200216 ax .set_ylabel (f"Power Spectrum ({ yunits } )" , fontsize = 18 )
201- ax .set_xlabel ("Frequency (MHz )" , fontsize = 16 )
217+ ax .set_xlabel (f "Frequency ({ fscale } )" , fontsize = 16 )
202218 ax .set_title ("Output Power Spectrum" , fontsize = 16 )
203- ax .set_xlim ([0 , fs / 2e6 ])
219+ ax .set_xlim ([0 , fs / ( 2 * xscale ) ])
204220 ax .set_ylim ([1.1 * min (pwr_dB ), 0 ])
205221 ax .annotate (
206222 plt_str ,
@@ -241,7 +257,7 @@ def plot_spectrum(
241257 return (pwr , stats )
242258
243259
244- def get_plot_string (stats , full_scale , fs , nfft , window ):
260+ def get_plot_string (stats , full_scale , fs , nfft , window , xscale = 1e6 , fscale = "MHz" ):
245261 """Generate plot string from stats dict."""
246262
247263 plt_str = "==== FFT ====\n "
@@ -252,22 +268,22 @@ def get_plot_string(stats, full_scale, fs, nfft, window):
252268 plt_str += "==== Signal ====\n "
253269 plt_str += f"FullScale = { full_scale } dB\n "
254270 plt_str += f"Psig = { stats ['sig' ]['dBFS' ]} dBFS ({ stats ['sig' ]['dB' ]} dB)\n "
255- plt_str += f"fsig = { round (stats ['sig' ]['freq' ]/ 1e6 , 2 )} MHz \n "
256- plt_str += f"fsamp = { round (fs / 1e6 , 2 )} MHz \n "
271+ plt_str += f"fsig = { round (stats ['sig' ]['freq' ]/ xscale , 2 )} { fscale } \n "
272+ plt_str += f"fsamp = { round (fs / xscale , 2 )} { fscale } \n "
257273 plt_str += "\n "
258274 plt_str += "==== SNDR/SFDR ====\n "
259275 plt_str += f"ENOB = { stats ['enob' ]['bits' ]} bits\n "
260276 plt_str += f"SNDR = { stats ['sndr' ]['dBFS' ]} dBFS ({ stats ['sndr' ]['dBc' ]} dBc)\n "
261277 plt_str += f"SFDR = { stats ['sfdr' ]['dBFS' ]} dBFS ({ stats ['sfdr' ]['dBc' ]} dBc)\n "
262278 plt_str += f"Pspur = { stats ['spur' ]['dBFS' ]} dBFS\n "
263- plt_str += f"fspur = { round (stats ['spur' ]['freq' ]/ 1e6 , 2 )} MHz \n "
279+ plt_str += f"fspur = { round (stats ['spur' ]['freq' ]/ xscale , 2 )} { fscale } \n "
264280 plt_str += f"Noise Floor = { stats ['noise' ]['dBHz' ]} dBFS\n "
265281 plt_str += f"NSD = { stats ['noise' ]['NSD' ]} dBFS\n "
266282 plt_str += "\n "
267283 plt_str += "==== Harmonics ====\n "
268284
269285 for hindex , hdata in stats ["harm" ].items ():
270- plt_str += f"HD{ hindex } = { round (hdata ['dB' ] - full_scale , 1 )} dBFS @ { hdata ['freq' ]} MHz \n "
286+ plt_str += f"HD{ hindex } = { round (hdata ['dB' ] - full_scale , 1 )} dBFS @ { hdata ['freq' ]} { fscale } \n "
271287
272288 plt_str += "\n "
273289
@@ -284,6 +300,7 @@ def analyze(
284300 window = "rectangular" ,
285301 no_plot = False ,
286302 yaxis = "fullscale" ,
303+ fscale = "MHz" ,
287304):
288305 """Perform spectral analysis on input waveform."""
289306 (spectrum , stats ) = plot_spectrum (
@@ -296,6 +313,7 @@ def analyze(
296313 window = window ,
297314 no_plot = no_plot ,
298315 yaxis = yaxis ,
316+ fscale = fscale ,
299317 )
300318
301319 return (spectrum , stats )
0 commit comments