2
2
3
3
import numpy as np
4
4
import matplotlib .pyplot as plt
5
-
6
-
7
- def db_to_pow (value , places = 3 ):
8
- """Convert dBW to W."""
9
- if isinstance (value , np .ndarray ):
10
- return round (10 ** (0.1 * value ), places )
11
- return round (10 ** (0.1 * value ), places )
12
-
13
-
14
- def dBW (value , places = 1 ):
15
- """Convert to dBW."""
16
- if isinstance (value , np .ndarray ):
17
- return round (10 * np .log10 (value ), places )
18
- return round (10 * np .log10 (value ), places )
19
-
20
-
21
- def enob (sndr , places = 1 ):
22
- """Return ENOB for given SNDR."""
23
- return round ((sndr - 1.76 ) / 6.02 , places )
24
-
25
-
26
- def sndr_sfdr (spectrum , freq , fs , nfft , leak , full_scale = 0 ):
27
- """Get SNDR and SFDR."""
28
-
29
- # Zero the DC bin
30
- for i in range (0 , leak + 1 ):
31
- spectrum [i ] = 0
32
- bin_sig = np .argmax (spectrum )
33
- psig = sum (spectrum [i ] for i in range (bin_sig - leak , bin_sig + leak + 1 ))
34
- spectrum_n = spectrum .copy ()
35
- spectrum_n [bin_sig ] = 0
36
- fbin = fs / nfft
37
-
38
- for i in range (bin_sig - leak , bin_sig + leak + 1 ):
39
- spectrum_n [i ] = 0
40
-
41
- bin_spur = np .argmax (spectrum_n )
42
- pspur = spectrum [bin_spur ]
43
-
44
- noise_power = sum (spectrum_n )
45
- noise_floor = 2 * noise_power / nfft
46
-
47
- stats = {}
48
-
49
- stats ["sig" ] = {
50
- "freq" : freq [bin_sig ],
51
- "bin" : bin_sig ,
52
- "power" : psig ,
53
- "dB" : dBW (psig ),
54
- "dBFS" : round (dBW (psig ) - full_scale , 1 ),
55
- }
56
-
57
- stats ["spur" ] = {
58
- "freq" : freq [bin_spur ],
59
- "bin" : bin_spur ,
60
- "power" : pspur ,
61
- "dB" : dBW (pspur ),
62
- "dBFS" : round (dBW (pspur ) - full_scale , 1 ),
63
- }
64
- stats ["noise" ] = {
65
- "floor" : noise_floor ,
66
- "power" : noise_power ,
67
- "rms" : np .sqrt (noise_power ),
68
- "dBHz" : round (dBW (noise_floor , 3 ) - full_scale , 1 ),
69
- "NSD" : round (dBW (noise_floor , 3 ) - full_scale - 2 * dBW (fbin , 3 ), 1 ),
70
- }
71
- stats ["sndr" ] = {
72
- "dBc" : dBW (psig / noise_power ),
73
- "dBFS" : round (full_scale - dBW (noise_power ), 1 ),
74
- }
75
- stats ["sfdr" ] = {
76
- "dBc" : dBW (psig / pspur ),
77
- "dBFS" : round (full_scale - dBW (pspur ), 1 ),
78
- }
79
- stats ["enob" ] = {"bits" : enob (stats ["sndr" ]["dBFS" ])}
80
-
81
- return stats
82
-
83
-
84
- def find_harmonics (spectrum , freq , nfft , bin_sig , psig , harms = 5 , leak = 20 , fscale = 1e6 ):
85
- """Get the harmonic contents of the data."""
86
- harm_stats = {"harm" : {}}
87
- harm_index = 2
88
- for harm in bin_sig * np .arange (2 , harms + 1 ):
89
- harm_stats ["harm" ][harm_index ] = {}
90
- zone = np .floor (harm / (nfft / 2 )) + 1
91
- if zone % 2 == 0 :
92
- bin_harm = int (nfft / 2 - (harm - (zone - 1 ) * nfft / 2 ))
93
- else :
94
- bin_harm = int (harm - (zone - 1 ) * nfft / 2 )
95
-
96
- # Make sure we pick the max bin where power is maximized; due to spectral leakage
97
- # if bin_harm == nfft/2, set to bin of 0
98
- if bin_harm == nfft / 2 :
99
- bin_harm = 0
100
- pwr_max = spectrum [bin_harm ]
101
- bin_harm_max = bin_harm
102
- for i in range (bin_harm - leak , bin_harm + leak + 1 ):
103
- try :
104
- pwr = spectrum [i ]
105
- if pwr > pwr_max :
106
- bin_harm_max = i
107
- pwr_max = pwr
108
- except IndexError :
109
- # bin + leakage out of bounds, so stop looking
110
- break
111
-
112
- harm_stats ["harm" ][harm_index ]["bin" ] = bin_harm_max
113
- harm_stats ["harm" ][harm_index ]["power" ] = pwr_max
114
- harm_stats ["harm" ][harm_index ]["freq" ] = round (freq [bin_harm ] / fscale , 1 )
115
- harm_stats ["harm" ][harm_index ]["dBc" ] = dBW (pwr_max / psig )
116
- harm_stats ["harm" ][harm_index ]["dB" ] = dBW (pwr_max )
117
-
118
- harm_index = harm_index + 1
119
-
120
- return harm_stats
5
+ from adc_eval .eval import calc
121
6
122
7
123
8
def calc_psd (data , fs , nfft = 2 ** 12 ):
@@ -197,12 +82,12 @@ def plot_spectrum(
197
82
(freq , pwr ) = get_spectrum (
198
83
data * windows [window ] * wscale , fs = fs , nfft = nfft , single_sided = single_sided
199
84
)
200
- full_scale = dBW (dr ** 2 / 8 )
85
+ full_scale = calc . dBW (dr ** 2 / 8 )
201
86
202
87
yaxis_lut = {
203
88
"power" : [0 , "dB" ],
204
- "fullscale" : [dBW ( dr ** 2 / 8 ) , "dBFS" ],
205
- "normalize" : [max (dBW (pwr )), "dB Normalized" ],
89
+ "fullscale" : [full_scale , "dBFS" ],
90
+ "normalize" : [max (calc . dBW (pwr )), "dB Normalized" ],
206
91
"magnitude" : [0 , "W" ],
207
92
}
208
93
@@ -217,7 +102,7 @@ def plot_spectrum(
217
102
)
218
103
print (" Defaulting to Hz." )
219
104
220
- psd_out = 10 * np . log10 (pwr ) - scalar
105
+ psd_out = calc . dBW (pwr , places = 3 ) - scalar
221
106
if lut_key in ["magnitude" ]:
222
107
psd_out = pwr
223
108
@@ -229,9 +114,9 @@ def plot_spectrum(
229
114
data * windows [window ] * wscale , fs = fs , nfft = nfft , single_sided = True
230
115
)
231
116
232
- sndr_stats = sndr_sfdr (psd_ss , f_ss , fs , nfft , leak = leak , full_scale = full_scale )
117
+ sndr_stats = calc . sndr_sfdr (psd_ss , f_ss , fs , nfft , leak = leak , full_scale = full_scale )
233
118
234
- harm_stats = find_harmonics (
119
+ harm_stats = calc . find_harmonics (
235
120
psd_ss ,
236
121
f_ss ,
237
122
nfft ,
@@ -247,7 +132,7 @@ def plot_spectrum(
247
132
xmin = 0 if single_sided else - fs / 2e6
248
133
249
134
if not no_plot :
250
- plt_str = get_plot_string (stats , full_scale , fs , nfft , window , xscale , fscale )
135
+ plt_str = calc . get_plot_string (stats , full_scale , fs , nfft , window , xscale , fscale )
251
136
fig , ax = plt .subplots (figsize = (15 , 8 ))
252
137
ax .plot (freq / xscale , psd_out )
253
138
ax .set_ylabel (f"Power Spectrum ({ yunits } )" , fontsize = 18 )
@@ -316,39 +201,6 @@ def plot_spectrum(
316
201
return (freq , psd_out , stats )
317
202
318
203
319
- def get_plot_string (stats , full_scale , fs , nfft , window , xscale = 1e6 , fscale = "MHz" ):
320
- """Generate plot string from stats dict."""
321
-
322
- plt_str = "==== FFT ====\n "
323
- plt_str += f"NFFT = { nfft } \n "
324
- plt_str += f"fbin = { round (fs / nfft / 1e3 , 2 )} kHz\n "
325
- plt_str += f"window = { window } \n "
326
- plt_str += "\n "
327
- plt_str += "==== Signal ====\n "
328
- plt_str += f"FullScale = { full_scale } dB\n "
329
- plt_str += f"Psig = { stats ['sig' ]['dBFS' ]} dBFS ({ stats ['sig' ]['dB' ]} dB)\n "
330
- plt_str += f"fsig = { round (stats ['sig' ]['freq' ]/ xscale , 2 )} { fscale } \n "
331
- plt_str += f"fsamp = { round (fs / xscale , 2 )} { fscale } \n "
332
- plt_str += "\n "
333
- plt_str += "==== SNDR/SFDR ====\n "
334
- plt_str += f"ENOB = { stats ['enob' ]['bits' ]} bits\n "
335
- plt_str += f"SNDR = { stats ['sndr' ]['dBFS' ]} dBFS ({ stats ['sndr' ]['dBc' ]} dBc)\n "
336
- plt_str += f"SFDR = { stats ['sfdr' ]['dBFS' ]} dBFS ({ stats ['sfdr' ]['dBc' ]} dBc)\n "
337
- plt_str += f"Pspur = { stats ['spur' ]['dBFS' ]} dBFS\n "
338
- plt_str += f"fspur = { round (stats ['spur' ]['freq' ]/ xscale , 2 )} { fscale } \n "
339
- plt_str += f"Noise Floor = { stats ['noise' ]['dBHz' ]} dBFS\n "
340
- plt_str += f"NSD = { stats ['noise' ]['NSD' ]} dBFS\n "
341
- plt_str += "\n "
342
- plt_str += "==== Harmonics ====\n "
343
-
344
- for hindex , hdata in stats ["harm" ].items ():
345
- plt_str += f"HD{ hindex } = { round (hdata ['dB' ] - full_scale , 1 )} dBFS @ { hdata ['freq' ]} { fscale } \n "
346
-
347
- plt_str += "\n "
348
-
349
- return plt_str
350
-
351
-
352
204
def analyze (
353
205
data ,
354
206
nfft ,
@@ -377,4 +229,4 @@ def analyze(
377
229
fscale = fscale ,
378
230
)
379
231
380
- return (freq , spectrum , stats )
232
+ return (freq , spectrum , stats )
0 commit comments