\title{Digital Arithmetic Cells with myHDL} \author{Steven K Armour} \maketitle
@book{brown_vranesic_2014, place={New York, NY}, edition={3}, title={Fundamentals of digital logic with Verilog design}, publisher={McGraw-Hill}, author={Brown, Stephen and Vranesic, Zvonko G}, year={2014} },
@book{lameres_2017, title={Introduction to logic circuits & logic design with Verilog}, publisher={springer}, author={LaMeres, Brock J}, year={2017} }
@misc{four bit adder - rosetta code-myhdl_2017, url={https://rosettacode.org/wiki/Four_bit_adder#MyHDL}, journal={Rosettacode.org}, year={2017} },
@book{cavanagh_2010, place={Boca Raton, FL}, title={Computer arithmetic and Verilog HDL fundamentals}, publisher={CRC Press}, author={Cavanagh, Joseph}, year={2010} },
@misc{peeker_hier_add_2017, url={http://www.xess.com/static/media/pages/peeker_hier_add.html}, journal={Xess.com}, year={2017} }, Christopher Felton's CIC Exsample from myHDL old site (https://github.com/jandecaluwe/site-myhdl-retired/blob/master/_ori/pages/projects/gciccomplete.txt)
In [1]:
import numpy as np
import pandas as pd
from sympy import *
init_printing()
from myhdl import *
from myhdlpeek import *
import random
from sympy_myhdl_tools import *
import scipy.signal as sig
import matplotlib.pyplot as plt
%matplotlib inline
In [2]:
ConversionTable=pd.DataFrame(columns=['Decimal'])
In [3]:
ConversionTable['Decimal']=np.arange(0, 21)
ConversionTable['Binary']=[bin(i, 3) for i in np.arange(0, 21)]
ConversionTable['hex']=[hex(i) for i in np.arange(0, 21)]
ConversionTable['oct']=[oct(i) for i in np.arange(0, 21)]
ConversionTable
Out[3]:
In [4]:
def DecimialtoBaseN(dec, base):
quotant=[dec]; remander=[]
while True:
q, r=np.divmod(quotant[-1], base)
quotant.append(q); remander.append(r)
if q==0: break
TwosProd=[]
for i in range(len(remander)):
TwosProd.append(remander[i]*base**i)
return quotant, remander, TwosProd[::-1], np.sum(TwosProd)
In [5]:
binarySum=lambda a, b, bits=2: np.binary_repr(a+b, bits)
In [6]:
for i in [[0,0], [0,1], [1,0], [1,1]]:
print(f'{i[0]} + {i[1]} yield {binarySum(*i)}')
if we split the yileded output up we find that the right most place is the 'Sum' and the next place to the left if the 'carry'
therfore we can posttulate the following
In [7]:
S_out, C_out=symbols('S_out, C_out')
In [8]:
x_1in, x_2in, y_out=symbols('x_1in, x_2in, y_out')
SEq=Eq(S_out, x_1in^x_2in)
print(SEq)
SEq_TT=TruthTabelGenrator(SEq)
SEq_TT
Out[8]:
In [9]:
CEq=Eq(C_out, x_1in & x_2in)
CEq_TT=TruthTabelGenrator(CEq)
print(CEq)
CEq_TT
Out[9]:
In [10]:
pd.merge(SEq_TT, CEq_TT[CEq_TT.columns.difference(SEq_TT.columns)], left_index=True, right_index=True)
Out[10]:
Passing the carry
In [ ]:
In [11]:
binarySum=lambda a, b, bits=3: np.binary_repr(a+b, bits)
In [12]:
for i in [[0,0], [0,1], [1,0], [1,1], [0, 2], [1, 2], [2,2],
[0, 3], [1, 3], [2, 3], [3,3]]:
print(f'{i[0]} + {i[1]} yield {binarySum(*i)}')
We can see that once exceded the binary repersentation of 2 we have a carry to the next bit
In [13]:
C_in=symbols('C_in')
Sum3Eq=Eq(S_out, x_1in ^x_2in^C_in)
print(Sum3Eq)
SEq_TT=TruthTabelGenrator(Sum3Eq)
SEq_TT
Out[13]:
In [14]:
C3Eq=Eq(C_out, (x_1in & x_2in) | ((x_1in & x_2in)& C_in))
CEq_TT=TruthTabelGenrator(C3Eq)
print(CEq)
CEq_TT
Out[14]:
In [15]:
pd.merge(SEq_TT, CEq_TT[CEq_TT.columns.difference(SEq_TT.columns)], left_index=True, right_index=True)
Out[15]:
In [16]:
def HalfAdder(x_1in, x_2in, S_out, C_out):
@always_comb
def logic():
S_out.next=x_1in ^ x_2in
C_out.next=x_1in & x_2in
return logic
In [17]:
#create the test signals and intilize value to both be false(0)
x_1in, x_2in, S_out, C_out=[Signal(bool(0)) for _ in range(4)]
#Peeker is helper libary for viewing wavefrom outputs and run the sim inside
#jupyter notebooks
#clear any previeese data loaded into peeker
Peeker.clear()
#load and name the signals to watch into peeker
Peeker(x_1in, 'x_1in'); Peeker(x_2in, 'x_2in')
Peeker(S_out, 'y_out'); Peeker(C_out, 'C_out')
#make an instatince of the NotGate as the DUT
DUT=HalfAdder(x_1in=x_1in, x_2in=x_2in, S_out=S_out, C_out=C_out)
inputs=[x_1in, x_2in]
sim=Simulation(DUT, Combo_TB(inputs), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=0, stop_time=2*2**len(inputs), tock=True,
title='HalfAdder simulation',
caption=f'after clock cycle {2**len(inputs)-1} ->random input')
In [18]:
MakeDFfromPeeker(Peeker.to_wavejson(start_time=0, stop_time=2**len(inputs) -1))
Out[18]:
In [19]:
toVerilog(HalfAdder, x_1in, x_2in, S_out, C_out)
#toVHDL(And3Gate, x_1in, x_2in, x_3in y_out)
_=VerilogTextReader('HalfAdder')
In [20]:
def FullAdder21(x_1in, x_2in, C_in, S_out, C_out):
@always_comb
def logic():
S_out.next=x_1in ^ x_2in ^C_in
C_out.next=(x_1in and x_2in) or (x_1in and C_in) or (x_2in and C_in)
return logic
! need to change the order of the inputs in the simulator so that Cin is last
In [21]:
#create the test signals and intilize value to both be false(0)
x_1in, x_2in, C_in, S_out, C_out=[Signal(bool(0)) for _ in range(5)]
#Peeker is helper libary for viewing wavefrom outputs and run the sim inside
#jupyter notebooks
#clear any previeese data loaded into peeker
Peeker.clear()
#load and name the signals to watch into peeker
Peeker(x_1in, 'x_1in'); Peeker(x_2in, 'x_2in'); Peeker(C_in, 'C_in')
Peeker(S_out, 'y_out'); Peeker(C_out, 'C_out')
#make an instatince of the NotGate as the DUT
DUT=FullAdder21(x_1in=x_1in, x_2in=x_2in, C_in=C_in, S_out=S_out, C_out=C_out)
inputs=[x_1in, x_2in, C_in]
sim=Simulation(DUT, Combo_TB(inputs), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=0, stop_time=2*2**len(inputs), tock=True,
title='FullAdder21 simulation',
caption=f'after clock cycle {2**len(inputs)-1} ->random input')
In [22]:
FA21DF=MakeDFfromPeeker(Peeker.to_wavejson(start_time=0, stop_time=2**len(inputs) -1))
FA21DF[['x_1in', 'x_2in','C_in', 'y_out','C_out' ]]
Out[22]:
Ripple adders are a exsample of composistion built by using previoues instaninces
In [23]:
def FullAdder(x_1in, x_2in, C_in, S_out, C_out):
S_1out=Signal(bool()); C_1out=Signal(bool())
HalfAdder1=HalfAdder(x_1in=x_1in, x_2in=C_in,
S_out=S_1out, C_out=C_1out)
C_2out=Signal(bool())
HalfAdder2=HalfAdder(x_1in=S_1out, x_2in=x_2in,
S_out=S_out, C_out=C_2out)
@always_comb
def C_outLogic():
C_out.next= C_1out or C_2out
return HalfAdder1, HalfAdder2, C_outLogic
In [24]:
#create the test signals and intilize value to both be false(0)
x_1in, x_2in, C_in, S_out, C_out=[Signal(bool(0)) for _ in range(5)]
#Peeker is helper libary for viewing wavefrom outputs and run the sim inside
#jupyter notebooks
#clear any previeese data loaded into peeker
Peeker.clear()
#load and name the signals to watch into peeker
Peeker(x_1in, 'x_1in'); Peeker(x_2in, 'x_2in'); Peeker(C_in, 'C_in')
Peeker(S_out, 'y_out'); Peeker(C_out, 'C_out')
#make an instatince of the NotGate as the DUT
DUT=FullAdder(x_1in=x_1in, x_2in=x_2in, C_in=C_in, S_out=S_out, C_out=C_out)
inputs=[x_1in, x_2in, C_in]
sim=Simulation(DUT, Combo_TB(inputs), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=0, stop_time=2*2**len(inputs), tock=True,
title='FullAdder simulation',
caption=f'after clock cycle {2**len(inputs)-1} ->random input')
In [25]:
FARDF=MakeDFfromPeeker(Peeker.to_wavejson(start_time=0, stop_time=2**len(inputs) -1))
FARDF[['x_1in', 'x_2in','C_in', 'y_out','C_out' ]]
Out[25]:
In [26]:
def AdderBehavioral(x_1in, x_2in, S_out):
@always_comb
def logic():
S_out.next=x_1in+x_2in
return logic
In [27]:
Peeker.clear()
BITWidth=3
x_1in=Signal(intbv(0)[BITWidth:]); Peeker(x_1in, 'x_1in')
x_2in=Signal(intbv(0)[BITWidth:]); Peeker(x_2in, 'x_2in')
S_out=Signal(intbv(0)[BITWidth+1:]); Peeker(S_out, 'S_out')
DUT=AdderBehavioral(x_1in, x_2in, S_out)
def AdderBehavioral_TB():
for i in ((x,y) for x in range(2**BITWidth) for y in range(2**BITWidth)):
x_1in.next, x_2in.next=i
yield delay(1)
raise StopSimulation
sim=Simulation(DUT, AdderBehavioral_TB(), *Peeker.instances()).run()
Peeker.to_wavedrom(tock=True,start_time=0, stop_time=3*2**BITWidth -1,
title='AdderBehavioral simulation',
caption=f'bitwidth inputs:{BITWidth}, outputs:{BITWidth+1}')
In [28]:
#got to improve MakeDFfromPeeker to deal with =
MakeDFfromPeeker(Peeker.to_wavejson())
Out[28]:
In [29]:
x_1in, x_2in=[Signal(intbv(0)[BITWidth:]) for _ in range(2)]
S_out=Signal(intbv(0)[BITWidth+1:])
toVerilog(AdderBehavioral, x_1in, x_2in, S_out)
#toVHDL(AdderBehavioral, x_1in, x_2in, x_3in y_out)
_=VerilogTextReader('AdderBehavioral')
In [30]:
def FullSup21(x_1in, x_2in, B_in, S_out, B_out):
@always_comb
def logic():
S_out.next=x_1in ^ (not x_2in) ^B_in
B_out.next=(x_1in and (not x_2in)) or (x_1in and B_in) or ((not x_2in) and B_in)
return logic
In [31]:
#create the test signals and intilize value to both be false(0)
x_1in, x_2in, S_out, B_out=[Signal(bool(0)) for _ in range(4)]
B_in=Signal(bool(1))
#Peeker is helper libary for viewing wavefrom outputs and run the sim inside
#jupyter notebooks
#clear any previeese data loaded into peeker
Peeker.clear()
#load and name the signals to watch into peeker
Peeker(x_1in, 'x_1in'); Peeker(x_2in, 'x_2in'); Peeker(B_in, 'B_in')
Peeker(S_out, 'y_out'); Peeker(B_out, 'B_out')
#make an instatince of the NotGate as the DUT
DUT=FullSup21(x_1in=x_1in, x_2in=x_2in, B_in=B_in, S_out=S_out, B_out=B_out)
inputs=[x_1in, x_2in]
sim=Simulation(DUT, Combo_TB(inputs), *Peeker.instances()).run()
Peeker.to_wavedrom(start_time=0, stop_time=2*2**len(inputs), tock=True,
title='FullSup21 simulation',
caption=f'after clock cycle {2**len(inputs)-1} ->random input')
In [32]:
def SuptractionBehavioral(x_1in, x_2in, S_out):
@always_comb
def logic():
S_out.next=x_1in-x_2in
return logic
In [33]:
Peeker.clear()
BITWidth=3
x_1in=Signal(intbv(0)[BITWidth:]); Peeker(x_1in, 'x_1in')
x_2in=Signal(intbv(0)[BITWidth:]); Peeker(x_2in, 'x_2in')
S_out=Signal(intbv(0, min=-2**BITWidth, max=2**BITWidth)); Peeker(S_out, 'S_out')
DUT=SuptractionBehavioral(x_1in, x_2in, S_out)
def SuptractionBehavioral_TB():
for i in ((x,y) for x in range(2**BITWidth) for y in range(2**BITWidth)):
x_1in.next, x_2in.next=i
yield delay(1)
raise StopSimulation
sim=Simulation(DUT, AdderBehavioral_TB(), *Peeker.instances()).run()
Peeker.to_wavedrom(tock=True,start_time=0, stop_time=3*2**BITWidth -1,
title='SuptractionBehavioral simulation',
caption=f'bitwidth inputs:{BITWidth}, outputs:{BITWidth+1}')
In [34]:
#got to improve MakeDFfromPeeker to deal with =
MakeDFfromPeeker(Peeker.to_wavejson())
Out[34]:
In [35]:
x_1in, x_2in=[Signal(intbv(0)[BITWidth:]) for _ in range(2)]
S_out=S_out=Signal(intbv(0, min=-2**BITWidth, max=2**BITWidth))
toVerilog(SuptractionBehavioral, x_1in, x_2in, S_out)
#toVHDL(SuptractionBehavioral, x_1in, x_2in, x_3in y_out)
_=VerilogTextReader('SuptractionBehavioral')
In [36]:
def MultiplyBehavioral(x_1in, x_2in, P_out):
@always_comb
def logic():
P_out.next=x_1in*x_2in
return logic
In [37]:
Peeker.clear()
BITWidth=3
x_1in=Signal(intbv(0)[BITWidth:]); Peeker(x_1in, 'x_1in')
x_2in=Signal(intbv(0)[BITWidth:]); Peeker(x_2in, 'x_2in')
P_out=Signal(intbv(0)[BITWidth**2:]); Peeker(P_out, 'P_out')
DUT=MultiplyBehavioral(x_1in, x_2in, P_out)
def MultiplyBehavioral_TB():
for i in ((x,y) for x in range(2**BITWidth) for y in range(2**BITWidth)):
x_1in.next, x_2in.next=i
yield delay(1)
raise StopSimulation
sim=Simulation(DUT, MultiplyBehavioral_TB(), *Peeker.instances()).run()
Peeker.to_wavedrom(tock=True,start_time=0, stop_time=3*2**BITWidth -1,
title='MultiplyBehavioral simulation',
caption=f'bitwidth inputs:{BITWidth}, outputs:{BITWidth+1}')
In [38]:
#got to improve MakeDFfromPeeker to deal with =
MakeDFfromPeeker(Peeker.to_wavejson())
Out[38]:
In [39]:
x_1in, x_2in=[Signal(intbv(0)[BITWidth:]) for _ in range(2)]
P_out=Signal(intbv(0)[BITWidth**2:])
toVerilog(MultiplyBehavioral, x_1in, x_2in, P_out)
#toVHDL(MultiplyBehavioral, x_1in, x_2in, x_3in y_out)
_=VerilogTextReader('MultiplyBehavioral')
In [ ]:
ignoring the residual $r_i$ and setting the weight $w_i=1$ for all $i$ we can make a very very crude approximation of the integral to be $$y=\int f(x)dx \approx f(x_i)+f(x_{i-1})$$
wich is better written as
In [40]:
z, n=symbols('z, n')
yn=Function('y')(n)
yn_1=Function('y')(n-1)
xn=Function('x')(n)
IntEq=Eq(yn, yn_1+xn); IntEq
Out[40]:
Perform a primitive Z transform on the above equation (note at this time sympy has no Z transform, so I am hacking one here) we get
In [41]:
Y=Function('Y')(z); X=Function('X')(z)
Equat=IntEq
for j in [yn, yn_1]:
zsups=expand(z**-(list(j.args))[0])
zsups
Equat=Equat.subs(j, z**(list(j.args))[0])
Equat=expand(Equat).subs(z**n, Y)
for j in [xn]:
zsups=expand(z**-(list(j.args))[0])
zsups
Equat=Equat.subs(j, z**(list(j.args))[0])
Equat=expand(Equat).subs(z**n, X)
Equat
Out[41]:
that then yields the Transfer function $H(z)=\dfrac{Y(z)}{X(z)}$
In [42]:
TF=solve(Equat, Y)[0]/X; TF
Out[42]:
the resulting Transfer Function is analyzed by splitting apart the numurator and denomantior to find zeros and poles respectively as well as the coefficient
In [43]:
TFnum, TFden=fraction(TF); TFnum, TFden
Out[43]:
In [44]:
TFCoefN=lambda NumDen: [float(i) for i in Poly(NumDen, z).coeffs()]
TFnumC=TFCoefN(TFnum); TFdenC=TFCoefN(TFden)
TFnumC, TFdenC
Out[44]:
these coefinents for the poles and zeros can be pulled into scipy's signals modul for further anylsis via
In [45]:
TFN=sig.TransferFunction(TFnumC, TFdenC, dt=1)
w, mag, phase = sig.dbode(TFN)
In [46]:
fig, [ax1, ax2]=plt.subplots(nrows=2, ncols=1)
ax1.semilogx(w, mag, label='Mag')
ax1.set_ylabel('mag')
ax2.semilogx(w, phase)
ax2.set_ylabel('phase')
ax2.set_xlabel('ang freq')
Out[46]:
In [47]:
#The following will not synth but will run in peeker ?
ysim, xsim=symbols('y,x')
sups={xn:xsim, yn_1:ysim}
IntEq.rhs.subs(sups)
toMyHDL=lambdify((xsim, ysim), IntEq.rhs.subs(sups), dummify=False)
In [48]:
def IntCurde(x_in, y_out, clk, rst):
@always(clk.posedge)
def logic():
if rst:
y_out.next=0
else:
y_out.next=x_in+y_out
#!!! Houston we have a problom here
#this will not synthesis to HDL but will run in Peeker
#y_out.next=toMyHDL(x=x_in, y=y_out)
return logic
In [49]:
BITWidth=3
Peeker.clear()
x_in=Signal(intbv(int(BITWidth*random.uniform(-1, 1)), min=-10, max=10)); Peeker(x_in, 'x_in')
y_out=Signal(intbv(0, min=-40, max=40)); Peeker(y_out, 'y_out')
clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')
DUT=IntCurde(x_in, y_out, clk, rst)
def Int_TB():
@always(delay(1))
def clkgen():
clk.next = not clk
@always(clk.posedge)
def ith():
x_in.next=int(BITWidth*random.uniform(0, 2))
return ith, clkgen
In [50]:
sim=Simulation(DUT, Int_TB(), *Peeker.instances()).run(15)
Peeker.to_wavedrom(tock=True)
In [51]:
x_in=Signal(intbv(0, min=-10, max=10))
y_out=Signal(intbv(0, min=-40, max=40))
clk=Signal(bool(0)); rst=Signal(bool(0))
toVerilog(IntCurde, x_in, y_out, clk, rst)
_=VerilogTextReader('IntCurde')