mirror of https://github.com/m-labs/artiq.git
gateware/dsp: add FIR and test
This commit is contained in:
parent
d34084be0f
commit
7e0f3edca5
|
@ -0,0 +1,68 @@
|
|||
from operator import add
|
||||
from functools import reduce
|
||||
import numpy as np
|
||||
from migen import *
|
||||
|
||||
|
||||
def halfgen4(up, n):
|
||||
"""
|
||||
http://recycle.lbl.gov/~ldoolitt/halfband
|
||||
|
||||
params:
|
||||
* `up` is the stopband width, as a fraction of input sampling rate
|
||||
* `n is the order of half-band filter to generate
|
||||
returns:
|
||||
* `a` is the full set of FIR coefficients, `4*n-1` long.
|
||||
implement wisely.
|
||||
"""
|
||||
|
||||
npt = n*40
|
||||
wmax = 2*np.pi*up
|
||||
wfit = (1 - np.linspace(0, 1, npt)[:, None]**2)*wmax
|
||||
|
||||
target = .5*np.ones_like(wfit)
|
||||
basis = np.cos(wfit*np.arange(1, 2*n, 2))
|
||||
l = np.linalg.pinv(basis)@target
|
||||
|
||||
weight = np.ones_like(wfit)
|
||||
for i in range(40):
|
||||
err = np.fabs(basis@l - .5)
|
||||
weight[err > .99*np.max(err)] *= 1 + 1.5/(i + 11)
|
||||
l = np.linalg.pinv(basis*weight)@(target*weight)
|
||||
a = np.c_[l, np.zeros_like(l)].ravel()[:-1]
|
||||
a = np.r_[a[::-1], 1, a]/2
|
||||
return a
|
||||
|
||||
|
||||
class FIR(Module):
|
||||
"""Full-rate finite impulse response filter.
|
||||
|
||||
:param coefficients: integer taps.
|
||||
:param width: bit width of input and output.
|
||||
:param shift: scale factor (as power of two).
|
||||
"""
|
||||
def __init__(self, coefficients, width=16, shift=None):
|
||||
self.width = width
|
||||
self.i = Signal((width, True))
|
||||
self.o = Signal((width, True))
|
||||
self.latency = (len(coefficients) + 1)//2 + 1
|
||||
|
||||
###
|
||||
|
||||
n = len(coefficients)
|
||||
x = [Signal((width, True)) for _ in range(n)]
|
||||
self.comb += x[0].eq(self.i)
|
||||
self.sync += [x[i + 1].eq(x[i]) for i in range(n - 1)]
|
||||
|
||||
o = []
|
||||
for i, c in enumerate(coefficients):
|
||||
# simplify for halfband and symmetric filters
|
||||
if c == 0 or c in coefficients[:i]:
|
||||
continue
|
||||
o.append(c*reduce(add, [
|
||||
xj for xj, cj in zip(x, coefficients) if cj == c
|
||||
]))
|
||||
|
||||
if shift is None:
|
||||
shift = width - 1
|
||||
self.sync += self.o.eq(reduce(add, o) >> shift)
|
|
@ -0,0 +1,78 @@
|
|||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from migen import *
|
||||
from migen.fhdl import verilog
|
||||
from artiq.gateware.dsp import fir
|
||||
|
||||
|
||||
class Transfer(Module):
|
||||
def __init__(self, dut):
|
||||
self.submodules.dut = dut
|
||||
|
||||
def drive(self, x):
|
||||
for xi in x:
|
||||
yield self.dut.i.eq(int(xi))
|
||||
yield
|
||||
|
||||
def record(self, y):
|
||||
for i in range(self.dut.latency):
|
||||
yield
|
||||
for i in range(len(y)):
|
||||
y[i] = (yield self.dut.o)
|
||||
yield
|
||||
|
||||
def run(self, samples, amplitude=1.):
|
||||
w = 2**(self.dut.width - 1) - 1
|
||||
x = np.round(np.random.uniform(
|
||||
-amplitude*w, amplitude*w, samples))
|
||||
y = np.empty_like(x)
|
||||
run_simulation(self, [self.drive(x), self.record(y)],
|
||||
vcd_name="fir.vcd")
|
||||
x /= w
|
||||
y /= w
|
||||
return x, y
|
||||
|
||||
def analyze(self, x, y):
|
||||
fig, ax = plt.subplots(3)
|
||||
ax[0].plot(x, "c-.", label="input")
|
||||
ax[0].plot(y, "r-", label="output")
|
||||
ax[0].legend(loc="right")
|
||||
ax[0].set_xlabel("time (1/fs)")
|
||||
ax[0].set_ylabel("signal")
|
||||
n = len(x)
|
||||
w = np.hanning(n)
|
||||
x = (x.reshape(-1, n)*w).sum(0)
|
||||
y = (y.reshape(-1, n)*w).sum(0)
|
||||
t = (np.fft.rfft(y)/np.fft.rfft(x))
|
||||
f = np.fft.rfftfreq(n)*2
|
||||
fmin = f[1]
|
||||
ax[1].plot(f, 20*np.log10(np.abs(t)), "r-")
|
||||
ax[1].set_ylim(-70, 3)
|
||||
ax[1].set_xlim(fmin, 1.)
|
||||
# ax[1].set_xscale("log")
|
||||
ax[1].set_xlabel("frequency (fs/2)")
|
||||
ax[1].set_ylabel("magnitude (dB)")
|
||||
ax[1].grid(True)
|
||||
ax[2].plot(f, np.rad2deg(np.angle(t)), "r-")
|
||||
ax[2].set_xlim(fmin, 1.)
|
||||
# ax[2].set_xscale("log")
|
||||
ax[2].set_xlabel("frequency (fs/2)")
|
||||
ax[2].set_ylabel("phase (deg)")
|
||||
ax[2].grid(True)
|
||||
return fig
|
||||
|
||||
|
||||
def _main():
|
||||
coeff = fir.halfgen4(.4/2, 8)
|
||||
coeff_int = [int(round(c * (1 << 16 - 1))) for c in coeff]
|
||||
dut = fir.FIR(coeff_int, width=16)
|
||||
# print(verilog.convert(dut, ios={dut.i, dut.o}))
|
||||
tb = Transfer(dut)
|
||||
x, y = tb.run(samples=1 << 10, amplitude=.8)
|
||||
tb.analyze(x, y)
|
||||
plt.show()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_main()
|
Loading…
Reference in New Issue