User Tools

Site Tools


nmigen:nmigen_fir_example

FIR Example

A Finite Impulse Response Filter takes as input a set of coefficient and applies it to a signal as shown in the drawing.

FIR Filter implementation in 20 lines of code

from functools import reduce
from operator import add
from nmigen import *
 
class FIR(Elaboratable):
    def __init__(self, coef, wsize=16):
        self.coef = coef
        self.wsize = wsize
        self.i = Signal(signed(self.wsize))
        self.o = Signal(signed(self.wsize))
 
    def elaborate(self, platform):
        m = Module()
        muls = []
        src = self.i
        for c in self.coef:
            sreg = Signal(signed(self.wsize))
            m.d.sync += sreg.eq(src)
            src = sreg
            c_fp = int(c*2**(self.wsize - 1))
            muls.append(c_fp*sreg)
        sum_full = Signal(signed(2*self.wsize-1))
        m.d.sync += sum_full.eq(reduce(add, muls))
        m.d.comb += self.o.eq(sum_full >> self.wsize-1)
        return m

This implementation takes a set of coefficient, on each cycle, one sample is taken and output from the module. Using nmigen allows us to use the “reduce” and “add” from the “functools” and “operator” library.

FIR Testbench

Our testbench will use the “remez” implementation of python to generate a set of 30 taps: in this example, we have a 3 frequency range:

[0, 0.1] → gain 0 [0.2, 0.4]→ gain 1 [0.45, 0.5] → gain 0

>>> coef = signal.remez(30, [0, 0.1, 0.2, 0.4, 0.45, 0.5], [0, 1, 0])

which will generate 30 values:

>>> print(coef)
[ 0.00512237 -0.03018773 -0.00152377 -0.00641322  0.03160546  0.03296197
 -0.0239646  -0.01182186 -0.08703671  0.05289971  0.03961362  0.08002203
  0.01105187 -0.37396288  0.27016493  0.27016493 -0.37396288  0.01105187
  0.08002203  0.03961362  0.05289971 -0.08703671 -0.01182186 -0.0239646
  0.03296197  0.03160546 -0.00641322 -0.00152377 -0.03018773  0.00512237]

our goal is to write a testbench that would then generate 200 sample of a cosine in 3 frequency range: 0.05, 0,1, and 0,25

According to our tap calculation, we should expect to see only the 0.25 freq pass, and the 0.05 be discarded. Concerning the 0.1, it is quite close to the border of [0, 0.1] so it should be mostly discarded but still might have some residue.

We will write all the values of cosine we generate into a table called “in_signal” and get the output of our FIR into the “out_signal”.

We will use directly the “plot” function to plot them.

from math import cos, pi
from scipy import signal
import matplotlib.pyplot as plt
from nmigen import *
from nmigen.sim import *
from fir import FIR  
 
def process():
    in_signals = []
    out_signals = []
    for frequency in [0.05, 0.1, 0.25]:
        f = 2**(dut.wsize - 1)
        for cycle in range(200):
            v = 0.1*cos(2*pi*frequency*cycle)
            yield dut.i.eq(int(f*v))
            in_signals.append(v)
            out_signals.append((yield dut.o)/f)
            yield
    plt.plot(in_signals)
    plt.plot(out_signals)
    plt.show()
    return process
 
 
if __name__ == "__main__":
    coef = signal.remez(30, [0, 0.1, 0.2, 0.4, 0.45, 0.5], [0, 1, 0])
    dut = FIR(coef)
    sim = Simulator(dut)
    sim.add_clock(1e-6)
 
    sim.add_sync_process(process)
    with sim.write_vcd("fir.vcd", gtkw_file="fir.gtkw"):
        sim.run()

When executing our testbench, we see from 0 to 200 our first sinewave in blue, and the output of the FIR in red, which has been completely filtered (the small wave at the beginning is the time corresponds to the 30 first cycle so all taps have a value). We then see from 200 to 400 the 0.1 frequency that is barely there, and finally from the 400th sample we can see in red that the 0.25 freq goes through the filter without being filtered.

That does validate that our FIR is functional

nmigen/nmigen_fir_example.txt · Last modified: 2021/02/04 21:05 by ramtin

Page Tools