\title{Cascaded Integrator Comb (CIC) Filter in myHDL} \author{Steven K Armour} \maketitle

Python Libraries Utilized


In [1]:
import numpy as np
import scipy.signal as sig
import pandas as pd

from sympy import *
init_printing()
from IPython.display import display, Math, Latex
 
from myhdl import *
from myhdlpeek import Peeker

import matplotlib.pyplot as plt
%matplotlib inline

Acknowledgments

The orgianl CIC filter written in myHDL was done by Christopher Felton (myHDL.old version here)

Author of myHDL Jan Decaluwe and the author of the myHDL Peeker XESS Corp.

Refrances

CIC Filter Backround and Pro/Cons of use

CIC Comb (dervitive) Unit

1st Order Comb Unit

Sympy Anyslsis

One of the two fundumental stages of the CIC filter is the Derivative stage also called a Comb. The lowest order Comb filter is governed by the following equation


In [2]:
n, M=symbols('n, M')
y=Function('y')(n)
x=Function('x')

Comb1Eq=Eq(y, x(n)-x(n-1)); Comb1Eq


Out[2]:
$$y{\left (n \right )} = x{\left (n \right )} - x{\left (n - 1 \right )}$$

where by applying the "Z" Transform to 1st order Comb equation we get


In [3]:
z=symbols('z')
Y=Function('Y')(z); X=Function('X')(z)
Comb1ZEq=Eq(Y, (1-z**-1)*X); Comb1ZEq


Out[3]:
$$Y{\left (z \right )} = \left(1 - \frac{1}{z}\right) X{\left (z \right )}$$

Which results in the following Transfer Function and discrate inpulse responce


In [4]:
H=Function('H')(z)
Comb1TFEq=Eq(H, solve(Comb1ZEq, Y)[0]/X); Comb1TFEq


Out[4]:
$$H{\left (z \right )} = \frac{1}{z} \left(z - 1\right)$$

The Transfer function has the following zeros and poles


In [5]:
TFnum, TFden=fraction(Comb1TFEq.rhs); TFnum, TFden
TFZeros=solve(TFnum, z); TFPoles=solve(TFden)
TFPZStatment=f"""{H} has zeros from {TFnum} at {TFZeros}
and poles from {TFden} at {TFPoles}"""
print(TFPZStatment)


H(z) has zeros from z - 1 at [1]
and poles from z at [0]

scipy anyslsis

using the exspersion for the numurator and denomnator there coefecnts can be build off thusly and read into scipy's signal module to numerical inspect the resulting transfer function thusly where we will use a sample time


In [6]:
TFCoefN=lambda NumDen: [float(i) for i in Poly(NumDen, z).coeffs()]
TFnumC=TFCoefN(TFnum); TFdenC=TFCoefN(TFden)
TFnumC, TFdenC


Out[6]:
$$\left ( \left [ 1.0, \quad -1.0\right ], \quad \left [ 1.0\right ]\right )$$

In [7]:
Comb1Sci=sig.TransferFunction(TFnumC, [1, 0], dt=1/4.8e3)
Comb1Sci


Out[7]:
TransferFunctionDiscrete(
array([ 1., -1.]),
array([ 1.,  0.]),
dt: 0.00020833333333333335
)

In [8]:
w, mag, phase = sig.dbode(Comb1Sci )
fig, ax=plt.subplots(nrows=2, ncols=1, sharex=True)
plt.title('Bode Plot of 1st order Comb Section')
ax[0].semilogx(w, mag, label='Mag')
ax[0].set_ylabel('Mag [dB]')
ax[1].semilogx(w, phase, label='Phase')
ax[1].set_ylabel('Phase[deg]')
ax[1].set_xlabel(r'$\omega[rad/dt]$')
None


** RuntimeWarning: divide by zero encountered in log10

In [9]:
plt.title('Impulse Respince of 1st Order Comb Filter')
tout, yout=sig.dimpulse(Comb1Sci, n=100)
plt.plot(tout, yout[0])
plt.xlabel('time[sec]'); plt.ylabel('Amp')
plt.ticklabel_format(style='sci', axis='x', scilimits=(0,0))



In [10]:
plt.title('Step Respince of 1st Order Comb Filter')
tout, yout=sig.dstep(Comb1Sci, n=100)
plt.plot(tout, yout[0])
plt.xlabel('time[sec]'); plt.ylabel('Amp')
plt.ticklabel_format(style='sci', axis='x', scilimits=(0,0))


M-th Order Comb Unit


In [11]:
M=symbols('M')
CombMEq=Eq(y, x(n)-x(n-M)); CombMEq


Out[11]:
$$y{\left (n \right )} = x{\left (n \right )} - x{\left (- M + n \right )}$$

In [12]:
CombMZEq=Eq(Y, (1-z**-M)*X); CombMZEq


Out[12]:
$$Y{\left (z \right )} = \left(1 - z^{- M}\right) X{\left (z \right )}$$

In [13]:
H=Function('H')(z)
CombMTFEq=Eq(H, simplify(solve(CombMZEq, Y)[0]/X)); CombMTFEq
CombMTFEq=Eq(H, expand(CombMTFEq.rhs*z**M)/z**M); CombMTFEq

sups={M:3}
CombMTFEq=CombMTFEq.subs(sups); CombMTFEq


Out[13]:
$$H{\left (z \right )} = \frac{1}{z^{3}} \left(z^{3} - 1\right)$$

In [14]:
TFnum, TFden=fraction(CombMTFEq.rhs); TFnum, TFden
TFZeros=solve(TFnum, z); TFPoles=solve(TFden)
TFPZStatment=f"""{H} has zeros from {TFnum} at {TFZeros}
and poles from {TFden} at {TFPoles}"""
print(TFPZStatment)


H(z) has zeros from z**3 - 1 at [1, -1/2 - sqrt(3)*I/2, -1/2 + sqrt(3)*I/2]
and poles from z**3 at [0]

In [15]:
TFCoefN=lambda NumDen: [float(i) for i in Poly(NumDen, z).coeffs()]
TFnumC=TFCoefN(TFnum); TFdenC=TFCoefN(TFden)
for i in range(sups[M]-1):
    TFnumC.insert(1, 0)

for i in range(sups[M]):
    TFdenC.insert(1, 0)
TFnumC, TFdenC


Out[15]:
$$\left ( \left [ 1.0, \quad 0, \quad 0, \quad -1.0\right ], \quad \left [ 1.0, \quad 0, \quad 0, \quad 0\right ]\right )$$

In [16]:
CombMSci=sig.TransferFunction(TFnumC, TFdenC, dt=1/4.8e3)
CombMSci


Out[16]:
TransferFunctionDiscrete(
array([ 1.,  0.,  0., -1.]),
array([ 1.,  0.,  0.,  0.]),
dt: 0.00020833333333333335
)

In [17]:
w, mag, phase = sig.dbode(CombMSci )
fig, ax=plt.subplots(nrows=2, ncols=1, sharex=True)
plt.title(f'Bode Plot of {sups[M]} order Comb Section')
ax[0].semilogx(w, mag, label='Mag')
ax[0].set_ylabel('Mag [dB]')
ax[1].semilogx(w, phase, label='Phase')
ax[1].set_ylabel('Phase[deg]')
ax[1].set_xlabel(r'$\omega[rad/dt]$')
None


** RuntimeWarning: divide by zero encountered in log10

In [18]:
plt.title(f'Impulse Respince of {sups[M]} order Comb Section')
tout, yout=sig.dimpulse(CombMSci, n=100)
plt.plot(tout, yout[0])
plt.xlabel('time[sec]'); plt.ylabel('Amp')
plt.ticklabel_format(style='sci', axis='x', scilimits=(0,0))



In [19]:
plt.title(f'Step Respince of {sups[M]} order Comb Section')
tout, yout=sig.dstep(CombMSci, n=100)
plt.plot(tout, yout[0])
plt.xlabel('time[sec]'); plt.ylabel('Amp')
plt.ticklabel_format(style='sci', axis='x', scilimits=(0,0))


myHDL Implimintation of M-th Order Comb Section

$z^{-1}$ as a delay


In [20]:
TestValues=pd.read_pickle('SinGen16.pkl')
plt.stem(TestValues['Time[s]'], TestValues['GenValue'])
plt.plot(TestValues['Time[s]'], TestValues['GenValue'])
plt.ticklabel_format(style='sci', axis='x', scilimits=(0,0))



In [21]:
def CombDelay(x_in, y_out, DataValIn, clk ):
    """
    Modfied D flip-flop repersenting a delay of z^-1
    Ports:
    ------
    x_in:    2's compliment input
    y_out:    2's compliment output
    DataValIn:  data valid input, flow control signal for the different rates
    clk:  System clock, must be equal to or greater than the max
          sample rate after interpolation or before decimation
    """
    @always(clk.posedge)
    def logic():
        if DataValIn:
            y_out.next=x_in
    return logic

Using the output sin genratr values with 16 bit persisoin we optain the following test data


In [22]:
BitWidth=16
L=2**(BitWidth)
maxminV =2*L 
Peeker.clear()

x_in=Signal(intbv(0, min=-L, max=L)); Peeker(x_in, 'x_in')

y_out=Signal(intbv(0, min=-maxminV, max=maxminV)); Peeker(y_out, 'y_out')
CombDelayTracker=[]

DataValIn=Signal(bool(0)); Peeker(DataValIn, 'DataValIn')
clk=Signal(bool(0)); Peeker(clk, 'clk')

DUT=CombDelay(x_in=x_in, y_out=y_out, DataValIn=DataValIn, clk=clk)


def CombDelay_TB():
    TestValueGen=TestValues['GenValue'].iteritems()
    
    @always(delay(1))    
    def clkgen():
        clk.next = not clk
    
    @instance
    def stimules():
        #test the DataValIn responce
        for step, val in TestValueGen:
            x_in.next=int(val)
            if step<12:
                DataValIn.next=False
            else:
                DataValIn.next=1
            CombDelayTracker.append(int(y_out))
            
            yield clk.negedge
        
        raise StopSimulation
        
    return instances()

In [23]:
N=TestValues.shape[0]
sim = Simulation(DUT, CombDelay_TB(), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=40, stop_time=60, tock=True)



In [24]:
plt.stem(TestValues['Time[s]'], TestValues['GenValue']-np.array(CombDelayTracker))
plt.ticklabel_format(style='sci', axis='x', scilimits=(0,0))


Comb Section


In [25]:
def CombSection(x_in, y_out, DataValIn, DataValOut, clk, rst, MthOrderParm=1):
    """
     Comb (difference) section of CIC with parameterizable to Mth Order
    
    Parameters:
    -----------
    MthOrderParm: Delay Order of the Comb section
    
    Ports:
    ------
    x_in:    2's compliment input
    y_out:    2's compliment output
    DataValIn:  data valid input, flow control signal for the different rates
    clk:  System clock, must be equal to or greater than the max
          sample rate after interpolation or before decimation
    rst:  System reset
    """
    #quntiztion
    Q = len(x_in)-1
    #word lengths
    L = 2**Q
    
    #create the dealy structer
    #Comb Delay Instansis
    Delay_i = [None for i in range(MthOrderParm)]
    #Comb Delay Instansis Interconects
    Delay_iX = [Signal(intbv(0, min=-L, max=L)) for i in range(MthOrderParm)]
    
    #output of delay block = to x[n-M]
    subx=Delay_iX[MthOrderParm-1]
    
    for i in range(MthOrderParm):
        if i ==0:
            Delay_i[i]=CombDelay(x_in=x_in, y_out=Delay_iX[i], DataValIn=DataValIn, clk=clk)
        else:
            Delay_i[i]=CombDelay(x_in=Delay_iX[i-1], y_out=Delay_iX[i], DataValIn=DataValIn, clk=clk)

    @always(clk.posedge)
    def logic():
        if rst:
            y_out.next=0
        else:
            if DataValIn:
                y_out.next=x_in-subx
                DataValOut.next=True
            else:
                DataValOut.next=False
    return instances()

Comb Test


In [26]:
BitWidth=16
L=2**(BitWidth)
maxminV =2*L 
Peeker.clear()

x_in=Signal(intbv(0, min=-L, max=L)); Peeker(x_in, 'x_in')

y_out=Signal(intbv(0, min=-maxminV, max=maxminV)); Peeker(y_out, 'y_out')
CombTracker=[]

DataValIn=Signal(bool(0)); Peeker(DataValIn, 'DataValIn')
DataValOut=Signal(bool(0)); Peeker(DataValOut, 'DataValOut')

clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')


DUT=CombSection(x_in=x_in, y_out=y_out, 
                DataValIn=DataValIn, DataValOut=DataValOut, 
                clk=clk, rst=rst, 
                MthOrderParm=1)



def CombSection_TB():
    TestValueGen=TestValues['GenValue'].iteritems()
    
    @always(delay(1))    
    def clkgen():
        clk.next = not clk
    
    @instance
    def stimules():
        #test the DataValIn responce
        for step, val in TestValueGen:
            x_in.next=int(val)
            if step<12:
                DataValIn.next=False
            
            elif step==12:
                DataValIn.next=True
                
            elif step==100:
                rst.next=True
                DataValIn.next=False
            
            elif step==101:
                rst.next=False
                DataValIn.next=False
            
            elif step==112:
                DataValIn.next=False
                
            else:
                DataValIn.next=True
            CombTracker.append(int(y_out))
            
            yield clk.negedge
        
        raise StopSimulation
        
    return instances()

In [27]:
N=TestValues.shape[0]
sim = Simulation(DUT, CombSection_TB(), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=40, stop_time=60, tock=True)



In [28]:
plt.stem(TestValues['Time[s]'], TestValues['GenValue']-np.array(CombTracker))
plt.ticklabel_format(style='sci', axis='x', scilimits=(0,0))



In [29]:
BitWidth=16
L=2**(BitWidth)
maxminV =2*L 
Peeker.clear()

x_in=Signal(intbv(0, min=-L, max=L)); Peeker(x_in, 'x_in')

y_out=Signal(intbv(0, min=-maxminV, max=maxminV)); Peeker(y_out, 'y_out')
CombTracker=[]

DataValIn=Signal(bool(0)); Peeker(DataValIn, 'DataValIn')
DataValOut=Signal(bool(0)); Peeker(DataValOut, 'DataValOut')

clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')


DUT=CombSection(x_in=x_in, y_out=y_out, 
                DataValIn=DataValIn, DataValOut=DataValOut, 
                clk=clk, rst=rst, 
                MthOrderParm=3)

In [30]:
N=TestValues.shape[0]
sim = Simulation(DUT, CombSection_TB(), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=40, stop_time=60, tock=True)



In [31]:
plt.stem(TestValues['Time[s]'], TestValues['GenValue']-np.array(CombTracker))
plt.ticklabel_format(style='sci', axis='x', scilimits=(0,0))


CIC Accumultor (Integrator) Unit

Sympy anylsis


In [32]:
y=Function('y')
Accum1Eq=Eq(y(n), x(n)+y(n-1)); Accum1Eq


Out[32]:
$$y{\left (n \right )} = x{\left (n \right )} + y{\left (n - 1 \right )}$$

In [33]:
Accum1ZEq=Eq(Y, X+z**-1 *Y); Accum1ZEq


Out[33]:
$$Y{\left (z \right )} = X{\left (z \right )} + \frac{1}{z} Y{\left (z \right )}$$

In [34]:
Accum1TFEq=Eq(H, solve(Accum1ZEq, Y)[0]/X); Accum1TFEq


Out[34]:
$$H{\left (z \right )} = \frac{z}{z - 1}$$

In [35]:
TFnum, TFden=fraction(Accum1TFEq.rhs); TFnum, TFden
TFZeros=solve(TFnum, z); TFPoles=solve(TFden)
TFPZStatment=f"""{H} has zeros from {TFnum} at {TFZeros}
and poles from {TFden} at {TFPoles}"""
print(TFPZStatment)


H(z) has zeros from z at [0]
and poles from z - 1 at [1]

In [36]:
sups[M]=0
TFCoefN=lambda NumDen: [float(i) for i in Poly(NumDen, z).coeffs()]
TFnumC=TFCoefN(TFnum); TFdenC=TFCoefN(TFden)
for i in range(sups[M]-1):
    TFnumC.insert(1, 0)

for i in range(sups[M]):
    TFdenC.insert(1, 0)
TFnumC, TFdenC


Out[36]:
$$\left ( \left [ 1.0\right ], \quad \left [ 1.0, \quad -1.0\right ]\right )$$

In [37]:
AccumSci=sig.TransferFunction(TFnumC, TFdenC, dt=1/4.8e3)
AccumSci


Out[37]:
TransferFunctionDiscrete(
array([ 1.]),
array([ 1., -1.]),
dt: 0.00020833333333333335
)

In [38]:
w, mag, phase = sig.dbode(AccumSci )
fig, ax=plt.subplots(nrows=2, ncols=1, sharex=True)
plt.title(f'Bode Plot of Accum Section')
ax[0].semilogx(w, mag, label='Mag')
ax[0].set_ylabel('Mag [dB]')
ax[1].semilogx(w, phase, label='Phase')
ax[1].set_ylabel('Phase[deg]')
ax[1].set_xlabel(r'$\omega[rad/dt]$')


** RuntimeWarning: divide by zero encountered in true_divide
** RuntimeWarning: invalid value encountered in true_divide
** RuntimeWarning: invalid value encountered in remainder
** RuntimeWarning: invalid value encountered in greater
** RuntimeWarning: invalid value encountered in less
Out[38]:
<matplotlib.text.Text at 0x2a94bd084a8>

In [39]:
plt.title(f'Impulse Respince of Accum Section')
tout, yout=sig.dimpulse(AccumSci, n=100)
plt.plot(tout, yout[0])
plt.xlabel('time[sec]'); plt.ylabel('Amp')
plt.ticklabel_format(style='sci', axis='x', scilimits=(0,0))



In [40]:
plt.title(f'Step Respince of Accum Section')
tout, yout=sig.dstep(AccumSci, n=100)
plt.plot(tout, yout[0])
plt.xlabel('time[sec]'); plt.ylabel('Amp')
plt.ticklabel_format(style='sci', axis='x', scilimits=(0,0))



In [61]:
def AccumSection(x_in, y_out, DataValIn, DataValOut, clk, rst):
    """
    Simple Integrator    
    
    
    Ports:
    ------
    x_in:    2's compliment input
    y_out:    2's compliment output
    DataValIn:  data valid input, flow control signal for the different rates
    clk:  System clock, must be equal to or greater than the max
          sample rate after interpolation or before decimation
    rst:  System reset
    """
    
    @always(clk.posedge)
    def logic():
        if rst:
            y_out.next=0
        else:                
            test=0

            if DataValIn and (test<y_out.max and test>y_out.min):
                y_out.next=y_out+x_in
                DataValOut.next=True
            else:
                DataValOut.next=False
    
    return instances()

In [62]:
BitWidth=16
L=2**(BitWidth)
maxminV =4*L 
Peeker.clear()

x_in=Signal(intbv(0, min=-L, max=L)); Peeker(x_in, 'x_in')

y_out=Signal(intbv(0, min=-maxminV, max=maxminV)); Peeker(y_out, 'y_out')
AccumTracker=[]

DataValIn=Signal(bool(0)); Peeker(DataValIn, 'DataValIn')
DataValOut=Signal(bool(0)); Peeker(DataValOut, 'DataValOut')

clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')


DUT=AccumSection(x_in=x_in, y_out=y_out, 
                DataValIn=DataValIn, DataValOut=DataValOut, 
                clk=clk, rst=rst)



def AccumSection_TB():
    TestValueGen=TestValues['GenValue'].iteritems()
    
    @always(delay(1))    
    def clkgen():
        clk.next = not clk
    
    @instance
    def stimules():
        #test the DataValIn responce
        for step, val in TestValueGen:
            x_in.next=int(val)
            if step<12:
                DataValIn.next=False
            
            elif step==12:
                DataValIn.next=True
                
            elif step==100:
                rst.next=True
                DataValIn.next=False
            
            elif step==101:
                rst.next=False
                DataValIn.next=False
            
            elif step==112:
                DataValIn.next=False
                
            else:
                DataValIn.next=True
            AccumTracker.append(int(y_out))
            
        
            
            yield clk.negedge
        
        raise StopSimulation
        
    return instances()

In [63]:
N=TestValues.shape[0]
sim = Simulation(DUT, AccumSection_TB(), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=40, stop_time=60, tock=True)



In [65]:
plt.stem(TestValues['Time[s]'], TestValues['GenValue']-np.array(AccumTracker))
plt.ticklabel_format(style='sci', axis='x', scilimits=(0,0))


CIC Filter Additial Segments


In [67]:
def PassThroughSection(x_in, y_out, DataValIn, DataValOut):
    """
    Place holder insted of interpoplation or decimation 
    
    Ports:
    ------
    x_in:    2's compliment input
    y_out:    2's compliment output
    DataValIn:  data valid input, flow control signal for the different rates
    """
    
    @always_comb
    def logic():
        y_out.next=x_in
        DataValOut.next=DataValIn
    return logic

In [76]:
BitWidth=16
L=2**(BitWidth)
maxminV =4*L 
Peeker.clear()

x_in=Signal(intbv(0, min=-L, max=L)); Peeker(x_in, 'x_in')

y_out=Signal(intbv(0, min=-maxminV, max=maxminV)); Peeker(y_out, 'y_out')
PassThroughTracker=[]

DataValIn=Signal(bool(0)); Peeker(DataValIn, 'DataValIn')
DataValOut=Signal(bool(0)); Peeker(DataValOut, 'DataValOut')

clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')


DUT=PassThroughSection(x_in=x_in, y_out=y_out, 
                       DataValIn=DataValIn, DataValOut=DataValOut)




def PassThroughSection_TB():
    TestValueGen=TestValues['GenValue'].iteritems()
    
    @always(delay(1))    
    def clkgen():
        clk.next = not clk
    
    @instance
    def stimules():
        #test the DataValIn responce
        for step, val in TestValueGen:
            x_in.next=int(val)
            if step<12:
                DataValIn.next=False
            
            elif step==12:
                DataValIn.next=True
                
           
            PassThroughTracker.append(int(y_out))
        
            yield clk.negedge
        
        raise StopSimulation
        
    return instances()

In [77]:
N=TestValues.shape[0]
sim = Simulation(DUT, PassThroughSection_TB(), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=40, stop_time=60, tock=True)



In [78]:
plt.stem(TestValues['Time[s]'], TestValues['GenValue']-np.array(PassThroughTracker))
plt.ticklabel_format(style='sci', axis='x', scilimits=(0,0))



In [ ]: