* eval_sine_quality.py: Add Python program to evaluate sine waveform.
This commit is contained in:
parent
aa8f0217d3
commit
7ce5b82ff0
|
@ -0,0 +1,153 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
"""
|
||||||
|
Evaluate quality of generated sine/cosine waveform.
|
||||||
|
|
||||||
|
Reads output file from VHDL testbench and reports
|
||||||
|
key figures for the quality of the sine wave.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python eval_sine_quality.py datafile.dat
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
import sys
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
|
||||||
|
def read_data(fname):
|
||||||
|
|
||||||
|
n = 0
|
||||||
|
data = numpy.zeros((1024, 2), dtype=numpy.int64)
|
||||||
|
|
||||||
|
with open(fname, 'r') as f:
|
||||||
|
for s in f:
|
||||||
|
if n == data.shape[0]:
|
||||||
|
data = numpy.resize(data, (2*n, 2))
|
||||||
|
w = s.split()
|
||||||
|
assert len(w) == 2
|
||||||
|
if len(w) != 2:
|
||||||
|
raise ValueError("Expecting two columns in file")
|
||||||
|
data[n,0] = int(w[0])
|
||||||
|
data[n,1] = int(w[1])
|
||||||
|
n += 1
|
||||||
|
|
||||||
|
return data[:n,:]
|
||||||
|
|
||||||
|
|
||||||
|
def eval_sine_quality(data):
|
||||||
|
|
||||||
|
assert len(data.shape) == 2
|
||||||
|
assert data.shape[1] == 2
|
||||||
|
n = data.shape[0]
|
||||||
|
assert n >= 4 and n % 4 == 0
|
||||||
|
|
||||||
|
# Extract sine and cosine colums.
|
||||||
|
dsin = numpy.copy(data[:,0])
|
||||||
|
dcos = data[:,1]
|
||||||
|
del data
|
||||||
|
|
||||||
|
# Check that cosine is an exact phase-shifted version of sine.
|
||||||
|
dcos[0:3*n//4] -= dsin[n//4:]
|
||||||
|
dcos[3*n//4:] -= dsin[0:n//4]
|
||||||
|
tmin = numpy.amin(dcos)
|
||||||
|
tmax = numpy.amax(dcos)
|
||||||
|
del dcos
|
||||||
|
|
||||||
|
if tmin == 0 and tmax == 0:
|
||||||
|
print('cos(x) == sin(x+pi/2) exactly')
|
||||||
|
else:
|
||||||
|
print('cos(x) == sin(x+pi/2) + (%d .. %d)' % (tmin, tmax))
|
||||||
|
|
||||||
|
# Check that 180 degree phase shift is equivalent to sign negation.
|
||||||
|
t = dsin[0:n//2] + dsin[n//2:]
|
||||||
|
tmin = numpy.amin(t)
|
||||||
|
tmax = numpy.amax(t)
|
||||||
|
del t
|
||||||
|
|
||||||
|
if tmin == 0 and tmax == 0:
|
||||||
|
print('sin(x) == - sin(x+pi) exactly')
|
||||||
|
else:
|
||||||
|
print('sin(x) == - sin(x+pi) + (%d .. %d)' % (tmin, tmax))
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Determine offset (mean value of waveform).
|
||||||
|
offs = numpy.mean(dsin)
|
||||||
|
print('offset = %20.12f lsb' % offs)
|
||||||
|
|
||||||
|
# Determine amplitude and phase.
|
||||||
|
tref = numpy.sin(2 * numpy.pi / n * numpy.arange(n))
|
||||||
|
asin = numpy.sum(dsin * tref) * 2.0 / n
|
||||||
|
del tref
|
||||||
|
tref = numpy.cos(2 * numpy.pi / n * numpy.arange(n))
|
||||||
|
acos = numpy.sum(dsin * tref) * 2.0 / n
|
||||||
|
del tref
|
||||||
|
|
||||||
|
ampl = numpy.sqrt(asin**2 + acos**2)
|
||||||
|
phase = numpy.arctan2(acos, asin)
|
||||||
|
|
||||||
|
print('amplitude = %20.12f lsb' % ampl)
|
||||||
|
print('phase offset = %20.12f rad' % phase)
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Determine peak and rms deviation.
|
||||||
|
tref = ampl * numpy.sin(2 * numpy.pi / n * numpy.arange(n) + phase)
|
||||||
|
terr = dsin - tref
|
||||||
|
del tref
|
||||||
|
|
||||||
|
peakerr = numpy.amax(numpy.abs(terr))
|
||||||
|
rmserr = numpy.std(terr)
|
||||||
|
del terr
|
||||||
|
|
||||||
|
print('peak error = %20.12f lsb' % peakerr)
|
||||||
|
print('rms error = %20.12f lsb rms' % rmserr)
|
||||||
|
|
||||||
|
# Calculate SNR and effective number of bits.
|
||||||
|
sinad = 20 * numpy.log10(ampl * numpy.sqrt(0.5) / rmserr)
|
||||||
|
print('SINAD = %12.4f dB' % sinad)
|
||||||
|
print('ENOB = %12.4f bits' % ((sinad - 1.76) / 6.02))
|
||||||
|
|
||||||
|
# Determine spurious-free dynamic range.
|
||||||
|
q = numpy.fft.rfft(dsin)
|
||||||
|
tampl = numpy.abs(q[1])
|
||||||
|
tspur = numpy.amax(numpy.abs(q[2:]))
|
||||||
|
del q
|
||||||
|
sfdr = 20 * numpy.log10(tampl / tspur)
|
||||||
|
|
||||||
|
print('SFDR = %12.4f dB' % sfdr)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
if len(sys.argv) != 2:
|
||||||
|
print(__doc__, file=sys.stderr)
|
||||||
|
print("ERROR: Invalid/missing command line arguments", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
fname = sys.argv[1]
|
||||||
|
|
||||||
|
# Read data from file.
|
||||||
|
print("reading", fname, "...")
|
||||||
|
data = read_data(fname)
|
||||||
|
|
||||||
|
print("got array", data.shape)
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Check array shape.
|
||||||
|
if len(data.shape) != 2 or data.shape[1] != 2:
|
||||||
|
print("ERROR: Expected array of shape (N, 2)", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Check number of samples.
|
||||||
|
if data.shape[0] < 4 or (data.shape[0] & (data.shape[0] - 1)) != 0:
|
||||||
|
print("ERROR: Expected power-of-two record length", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
eval_sine_quality(data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
Loading…
Reference in New Issue