\title{myHDL Combinational Logic Elements: Multiplexers (MUXs))} \author{Steven K Armour} \maketitle
@misc{xu_2018, title={Introduction to Digital Systems Supplementary Reading Shannon's Expansion Formulas and Compressed Truth Table}, author={Xu, Xuping}, year={Fall 2017} site=http://ecse.bd.psu.edu/cse271/comprttb.pdf }
In [1]:
#This notebook also uses the `(some) LaTeX environments for Jupyter`
#https://github.com/ProfFan/latex_envs wich is part of the
#jupyter_contrib_nbextensions package
from myhdl import *
from myhdlpeek import Peeker
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
from sympy import *
init_printing()
import itertools
#https://github.com/jrjohansson/version_information
%load_ext version_information
%version_information myhdl, myhdlpeek, numpy, pandas, matplotlib, sympy, itertools, SchemDraw
Out[1]:
In [2]:
#helper functions to read in the .v and .vhd generated files into python
def VerilogTextReader(loc, printresult=True):
with open(f'{loc}.v', 'r') as vText:
VerilogText=vText.read()
if printresult:
print(f'***Verilog modual from {loc}.v***\n\n', VerilogText)
return VerilogText
def VHDLTextReader(loc, printresult=True):
with open(f'{loc}.vhd', 'r') as vText:
VerilogText=vText.read()
if printresult:
print(f'***VHDL modual from {loc}.vhd***\n\n', VerilogText)
return VerilogText
def ConstraintXDCTextReader(loc, printresult=True):
with open(f'{loc}.xdc', 'r') as xdcText:
ConstraintText=xdcText.read()
if printresult:
print(f'***Constraint file from {loc}.xdc***\n\n', ConstraintText)
return ConstraintText
In [3]:
def TruthTabelGenrator(BoolSymFunc):
"""
Function to generate a truth table from a sympy boolian expression
BoolSymFunc: sympy boolian expression
return TT: a Truth table stored in a pandas dataframe
"""
colsL=sorted([i for i in list(BoolSymFunc.rhs.atoms())], key=lambda x:x.sort_key())
colsR=sorted([i for i in list(BoolSymFunc.lhs.atoms())], key=lambda x:x.sort_key())
bitwidth=len(colsL)
cols=colsL+colsR; cols
TT=pd.DataFrame(columns=cols, index=range(2**bitwidth))
for i in range(2**bitwidth):
inputs=[int(j) for j in list(np.binary_repr(i, bitwidth))]
outputs=BoolSymFunc.rhs.subs({j:v for j, v in zip(colsL, inputs)})
inputs.append(int(bool(outputs)))
TT.iloc[i]=inputs
return TT
In [4]:
x0, x1, s, y=symbols('x0, x1, s, y')
y21Eq=Eq(y, (~s&x0) |(s&x1) ); y21Eq
Out[4]:
In [5]:
TruthTabelGenrator(y21Eq)[[x1, x0, s, y]]
Out[5]:
In [6]:
y21EqN=lambdify([x0, x1, s], y21Eq.rhs, dummify=False)
SystmaticVals=np.array(list(itertools.product([0,1], repeat=3)))
print(SystmaticVals)
y21EqN(SystmaticVals[:, 1], SystmaticVals[:, 2], SystmaticVals[:, 0]).astype(int)
Out[6]:
In [7]:
@block
def MUX2_1_Combo(x0, x1, s, y):
"""
2:1 Multiplexer written in full combo
Input:
x0(bool): input channel 0
x1(bool): input channel 1
s(bool): channel selection input
Output:
y(bool): ouput
"""
@always_comb
def logic():
y.next= (not s and x0) |(s and x1)
return instances()
In [8]:
#generate systmatic and random test values
#stimules inputs X1 and X2
TestLen=10
SystmaticVals=list(itertools.product([0,1], repeat=3))
x0TVs=np.array([i[1] for i in SystmaticVals]).astype(int)
np.random.seed(15)
x0TVs=np.append(x0TVs, np.random.randint(0,2, TestLen)).astype(int)
x1TVs=np.array([i[2] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(16)
x1TVs=np.append(x1TVs, np.random.randint(0,2, TestLen)).astype(int)
sTVs=np.array([i[0] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(17)
sTVs=np.append(sTVs, np.random.randint(0,2, TestLen)).astype(int)
TestLen=len(x0TVs)
x0TVs, x1TVs, sTVs, TestLen
Out[8]:
In [9]:
Peeker.clear()
x0=Signal(bool(0)); Peeker(x0, 'x0')
x1=Signal(bool(0)); Peeker(x1, 'x1')
s=Signal(bool(0)); Peeker(s, 's')
y=Signal(bool(0)); Peeker(y, 'y')
DUT=MUX2_1_Combo(x0, x1, s, y)
def MUX2_1_Combo_TB():
"""
myHDL only testbench for module `MUX2_1_Combo`
"""
@instance
def stimules():
for i in range(TestLen):
x0.next=int(x0TVs[i])
x1.next=int(x1TVs[i])
s.next=int(sTVs[i])
yield delay(1)
raise StopSimulation()
return instances()
sim=Simulation(DUT, MUX2_1_Combo_TB(), *Peeker.instances()).run()
In [10]:
Peeker.to_wavedrom('x1', 'x0', 's', 'y')
In [11]:
MUX2_1_ComboData=Peeker.to_dataframe()
MUX2_1_ComboData=MUX2_1_ComboData[['x1', 'x0', 's', 'y']]
MUX2_1_ComboData
Out[11]:
In [12]:
MUX2_1_ComboData['yRef']=MUX2_1_ComboData.apply(lambda row:y21EqN(row['x0'], row['x1'], row['s']), axis=1).astype(int)
MUX2_1_ComboData
Out[12]:
In [13]:
Test=(MUX2_1_ComboData['y']==MUX2_1_ComboData['yRef']).all()
print(f'Module `MUX2_1_Combo` works as exspected: {Test}')
In [14]:
DUT.convert()
VerilogTextReader('MUX2_1_Combo');
In [15]:
#create BitVectors
x0TVs=intbv(int(''.join(x0TVs.astype(str)), 2))[TestLen:]
x1TVs=intbv(int(''.join(x1TVs.astype(str)), 2))[TestLen:]
sTVs=intbv(int(''.join(sTVs.astype(str)), 2))[TestLen:]
x0TVs, bin(x0TVs), x1TVs, bin(x1TVs), sTVs, bin(sTVs)
Out[15]:
In [16]:
@block
def MUX2_1_Combo_TBV():
"""
myHDL -> Verilog testbench for module `MUX2_1_Combo`
"""
x0=Signal(bool(0))
x1=Signal(bool(0))
s=Signal(bool(0))
y=Signal(bool(0))
@always_comb
def print_data():
print(x0, x1, s, y)
#Test Signal Bit Vectors
x0TV=Signal(x0TVs)
x1TV=Signal(x1TVs)
sTV=Signal(sTVs)
DUT=MUX2_1_Combo(x0, x1, s, y)
@instance
def stimules():
for i in range(TestLen):
x0.next=int(x0TV[i])
x1.next=int(x1TV[i])
s.next=int(sTV[i])
yield delay(1)
raise StopSimulation()
return instances()
TB=MUX2_1_Combo_TBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('MUX2_1_Combo_TBV');
In [17]:
ConstraintXDCTextReader('MUX2_1');
MUX2_1_Combo myHDL PYNQ-Z1 (YouTube)
In [18]:
x0, x1, x2, x3, s0, s1, y=symbols('x0, x1, x2, x3, s0, s1, y')
y41Eq=Eq(y, (~s0&~s1&x0) | (s0&~s1&x1)| (~s0&s1&x2)|(s0&s1&x3))
y41Eq
Out[18]:
In [19]:
TruthTabelGenrator(y41Eq)[[x3, x2, x1, x0, s1, s0, y]]
Out[19]:
In [20]:
y41EqN=lambdify([x0, x1, x2, x3, s0, s1], y41Eq.rhs, dummify=False)
SystmaticVals=np.array(list(itertools.product([0,1], repeat=6)))
SystmaticVals
y41EqN(*[SystmaticVals[:, i] for i in range(6)] ).astype(int)
Out[20]:
In [21]:
@block
def MUX4_1_Combo(x0, x1, x2, x3, s0, s1, y):
"""
4:1 Multiplexer written in full combo
Input:
x0(bool): input channel 0
x1(bool): input channel 1
x2(bool): input channel 2
x3(bool): input channel 3
s1(bool): channel selection input bit 1
s0(bool): channel selection input bit 0
Output:
y(bool): ouput
"""
@always_comb
def logic():
y.next= (not s0 and not s1 and x0) or (s0 and not s1 and x1) or (not s0 and s1 and x2) or (s0 and s1 and x3)
return instances()
In [22]:
#generate systmatic and random test values
TestLen=5
SystmaticVals=list(itertools.product([0,1], repeat=6))
s0TVs=np.array([i[0] for i in SystmaticVals]).astype(int)
np.random.seed(15)
s0TVs=np.append(s0TVs, np.random.randint(0,2, TestLen)).astype(int)
s1TVs=np.array([i[1] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(16)
s1TVs=np.append(s1TVs, np.random.randint(0,2, TestLen)).astype(int)
x0TVs=np.array([i[2] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(17)
x0TVs=np.append(x0TVs, np.random.randint(0,2, TestLen)).astype(int)
x1TVs=np.array([i[3] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(18)
x1TVs=np.append(x1TVs, np.random.randint(0,2, TestLen)).astype(int)
x2TVs=np.array([i[4] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(19)
x2TVs=np.append(x2TVs, np.random.randint(0,2, TestLen)).astype(int)
x3TVs=np.array([i[5] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(20)
x3TVs=np.append(x3TVs, np.random.randint(0,2, TestLen)).astype(int)
TestLen=len(x0TVs)
SystmaticVals, s0TVs, s1TVs, x3TVs, x2TVs, x1TVs, x0TVs, TestLen
Out[22]:
In [23]:
Peeker.clear()
x0=Signal(bool(0)); Peeker(x0, 'x0')
x1=Signal(bool(0)); Peeker(x1, 'x1')
x2=Signal(bool(0)); Peeker(x2, 'x2')
x3=Signal(bool(0)); Peeker(x3, 'x3')
s0=Signal(bool(0)); Peeker(s0, 's0')
s1=Signal(bool(0)); Peeker(s1, 's1')
y=Signal(bool(0)); Peeker(y, 'y')
DUT=MUX4_1_Combo(x0, x1, x2, x3, s0, s1, y)
def MUX4_1_Combo_TB():
"""
myHDL only testbench for module `MUX4_1_Combo`
"""
@instance
def stimules():
for i in range(TestLen):
x0.next=int(x0TVs[i])
x1.next=int(x1TVs[i])
x2.next=int(x2TVs[i])
x3.next=int(x3TVs[i])
s0.next=int(s0TVs[i])
s1.next=int(s1TVs[i])
yield delay(1)
raise StopSimulation()
return instances()
sim=Simulation(DUT, MUX4_1_Combo_TB(), *Peeker.instances()).run()
In [24]:
Peeker.to_wavedrom()
In [25]:
MUX4_1_ComboData=Peeker.to_dataframe()
MUX4_1_ComboData=MUX4_1_ComboData[['x3', 'x2', 'x1', 'x0', 's1', 's0', 'y']]
MUX4_1_ComboData
Out[25]:
In [26]:
MUX4_1_ComboData['yRef']=MUX4_1_ComboData.apply(lambda row:y41EqN(row['x0'], row['x1'], row['x2'], row['x3'], row['s0'], row['s1']), axis=1).astype(int)
MUX4_1_ComboData
Out[26]:
In [27]:
Test=(MUX4_1_ComboData['y']==MUX4_1_ComboData['yRef']).all()
print(f'Module `MUX4_1_Combo` works as exspected: {Test}')
In [28]:
DUT.convert()
VerilogTextReader('MUX4_1_Combo');
In [29]:
#create BitVectors for MUX4_1_Combo_TBV
x0TVs=intbv(int(''.join(x0TVs.astype(str)), 2))[TestLen:]
x1TVs=intbv(int(''.join(x1TVs.astype(str)), 2))[TestLen:]
x2TVs=intbv(int(''.join(x2TVs.astype(str)), 2))[TestLen:]
x3TVs=intbv(int(''.join(x3TVs.astype(str)), 2))[TestLen:]
s0TVs=intbv(int(''.join(s0TVs.astype(str)), 2))[TestLen:]
s1TVs=intbv(int(''.join(s1TVs.astype(str)), 2))[TestLen:]
x0TVs, bin(x0TVs), x1TVs, bin(x1TVs), x2TVs, bin(x2TVs), x3TVs, bin(x3TVs), s0TVs, bin(s0TVs), s1TVs, bin(s1TVs)
Out[29]:
In [30]:
@block
def MUX4_1_Combo_TBV():
"""
myHDL -> Verilog testbench for module `MUX4_1_Combo`
"""
x0=Signal(bool(0))
x1=Signal(bool(0))
x2=Signal(bool(0))
x3=Signal(bool(0))
y=Signal(bool(0))
s0=Signal(bool(0))
s1=Signal(bool(0))
@always_comb
def print_data():
print(x0, x1, x2, x3, s0, s1, y)
#Test Signal Bit Vectors
x0TV=Signal(x0TVs)
x1TV=Signal(x1TVs)
x2TV=Signal(x2TVs)
x3TV=Signal(x3TVs)
s0TV=Signal(s0TVs)
s1TV=Signal(s1TVs)
DUT=MUX4_1_Combo(x0, x1, x2, x3, s0, s1, y)
@instance
def stimules():
for i in range(TestLen):
x0.next=int(x0TV[i])
x1.next=int(x1TV[i])
x2.next=int(x2TV[i])
x3.next=int(x3TV[i])
s0.next=int(s0TV[i])
s1.next=int(s1TV[i])
yield delay(1)
raise StopSimulation()
return instances()
TB=MUX4_1_Combo_TBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('MUX4_1_Combo_TBV');
In [31]:
ConstraintXDCTextReader('MUX4_1');
MUX4_1_MS myHDL PYNQ-Z1 (YouTube)
Claude Shannon, of the famed Shannon-Nyquist theorem, discovered that any boolean expression $F(x_0, x_1, \ldots, x_n)$ can be decomposed in a manner akin to polynomials of perfect squares via
$$ F(x_0, x_1, \ldots, x_n)=x_0 \cdot F(x_0=1, x_1, \ldots, x_n) +\overline{x_0} \cdot F(x_0=0, x_1, \ldots, x_n) $$known as the Sum of Products (SOP) form since when the expansion is completed for all $x_n$ the result is that $$ F(x_0, x_1, \ldots, x_n)=\sum^{2^n-1}_{i=0} (m_i \cdot F(m_i)) $$ aka the Sum of all Minterms ($m_i$) belonging to the original boolean expression $F$ factored down to the $i$th of $n$ variables belonging to $F$ and product (&) of $F$ evaluated with the respective minterm as the argument
The Dual to the SOP form of Shannon's expansion formula is the Product of Sum (POS) form $$ F(x_0, x_1, \ldots, x_n)=(x_0+ F(x_0=1, x_1, \ldots, x_n)) \cdot (\overline{x_0} + F(x_0=0, x_1, \ldots, x_n)) $$ thus
$$F(x_0, x_1, \ldots, x_n)=\prod^{2^n-1}_{i=0} (M_i + F(M_i)) $$with $M_i$ being the $i$th Maxterm
it is for this reason that Shannon's Expansion Formula is known is further liked to the fundamental theorem of algebra that it is called the "fundamental theorem of Boolean algebra"
So why then is Shannon's decomposition formula discussed in terms of Multiplexers. Because the general expression for a $2^n:1$ multiplexer is $$y_{\text{MUX}}=\sum^{2^n-1}_{i=0}m_i\cdot x_n$$ where then $n$ is the required number of control inputs (referred to in this tutorial as $s_i$). Which is the same as the SOP form of Shannon's Formula for a boolean expression that has been fully decomposed (Factored). And further, if the boolean expression has not been fully factored we can replace $n-1$ parts of the partially factored expression with multiplexers. This then gives way to what is called "Multiplexer Stacking" in order to implement large boolean expressions and or large multiplexers
In [32]:
@block
def MUX4_1_MS(x0, x1, x2, x3, s0, s1, y):
"""
4:1 Multiplexer via 2:1 MUX stacking
Input:
x0(bool): input channel 0
x1(bool): input channel 1
x2(bool): input channel 2
x3(bool): input channel 3
s1(bool): channel selection input bit 1
s0(bool): channel selection input bit 0
Output:
y(bool): ouput
"""
#create ouput from x0x1 input MUX to y ouput MUX
x0x1_yWire=Signal(bool(0))
#create instance of 2:1 mux and wire in inputs
#a, b, s0 and wire to ouput mux
x0x1MUX=MUX2_1_Combo(x0, x1, s0, x0x1_yWire)
#create ouput from x2x3 input MUX to y ouput MUX
x2x3_yWire=Signal(bool(0))
#create instance of 2:1 mux and wire in inputs
#c, d, s0 and wire to ouput mux
x2x3MUX=MUX2_1_Combo(x2, x3, s0, x2x3_yWire)
#create ouput MUX and wire to internal wires,
#s1 and ouput y
yMUX=MUX2_1_Combo(x0x1_yWire, x2x3_yWire, s1, y)
return instances()
In [33]:
#generate systmatic and random test values
TestLen=5
SystmaticVals=list(itertools.product([0,1], repeat=6))
s0TVs=np.array([i[0] for i in SystmaticVals]).astype(int)
np.random.seed(15)
s0TVs=np.append(s0TVs, np.random.randint(0,2, TestLen)).astype(int)
s1TVs=np.array([i[1] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(16)
s1TVs=np.append(s1TVs, np.random.randint(0,2, TestLen)).astype(int)
x0TVs=np.array([i[2] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(17)
x0TVs=np.append(x0TVs, np.random.randint(0,2, TestLen)).astype(int)
x1TVs=np.array([i[3] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(18)
x1TVs=np.append(x1TVs, np.random.randint(0,2, TestLen)).astype(int)
x2TVs=np.array([i[4] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(19)
x2TVs=np.append(x2TVs, np.random.randint(0,2, TestLen)).astype(int)
x3TVs=np.array([i[5] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(20)
x3TVs=np.append(x3TVs, np.random.randint(0,2, TestLen)).astype(int)
TestLen=len(x0TVs)
SystmaticVals, s0TVs, s1TVs, x3TVs, x2TVs, x1TVs, x0TVs, TestLen
Out[33]:
In [34]:
Peeker.clear()
x0=Signal(bool(0)); Peeker(x0, 'x0')
x1=Signal(bool(0)); Peeker(x1, 'x1')
x2=Signal(bool(0)); Peeker(x2, 'x2')
x3=Signal(bool(0)); Peeker(x3, 'x3')
s0=Signal(bool(0)); Peeker(s0, 's0')
s1=Signal(bool(0)); Peeker(s1, 's1')
y=Signal(bool(0)); Peeker(y, 'y')
DUT=MUX4_1_MS(x0, x1, x2, x3, s0, s1, y)
def MUX4_1_MS_TB():
"""
myHDL only testbench for module `MUX4_1_MS`
"""
@instance
def stimules():
for i in range(TestLen):
x0.next=int(x0TVs[i])
x1.next=int(x1TVs[i])
x2.next=int(x2TVs[i])
x3.next=int(x3TVs[i])
s0.next=int(s0TVs[i])
s1.next=int(s1TVs[i])
yield delay(1)
raise StopSimulation()
return instances()
sim=Simulation(DUT, MUX4_1_MS_TB(), *Peeker.instances()).run()
In [35]:
Peeker.to_wavedrom()
In [36]:
MUX4_1_MSData=Peeker.to_dataframe()
MUX4_1_MSData=MUX4_1_MSData[['x3', 'x2', 'x1', 'x0', 's1', 's0', 'y']]
MUX4_1_MSData
Out[36]:
In [37]:
Test=MUX4_1_ComboData[['x3', 'x2', 'x1', 'x0', 's1', 's0', 'y']]==MUX4_1_MSData
Test=Test.all().all()
print(f'Module `MUX4_1_MS` works as exspected: {Test}')
In [38]:
DUT.convert()
VerilogTextReader('MUX4_1_MS');
In [39]:
#create BitVectors
x0TVs=intbv(int(''.join(x0TVs.astype(str)), 2))[TestLen:]
x1TVs=intbv(int(''.join(x1TVs.astype(str)), 2))[TestLen:]
x2TVs=intbv(int(''.join(x2TVs.astype(str)), 2))[TestLen:]
x3TVs=intbv(int(''.join(x3TVs.astype(str)), 2))[TestLen:]
s0TVs=intbv(int(''.join(s0TVs.astype(str)), 2))[TestLen:]
s1TVs=intbv(int(''.join(s1TVs.astype(str)), 2))[TestLen:]
x0TVs, bin(x0TVs), x1TVs, bin(x1TVs), x2TVs, bin(x2TVs), x3TVs, bin(x3TVs), s0TVs, bin(s0TVs), s1TVs, bin(s1TVs)
Out[39]:
In [40]:
@block
def MUX4_1_MS_TBV():
"""
myHDL -> Verilog testbench for module `MUX4_1_MS`
"""
x0=Signal(bool(0))
x1=Signal(bool(0))
x2=Signal(bool(0))
x3=Signal(bool(0))
y=Signal(bool(0))
s0=Signal(bool(0))
s1=Signal(bool(0))
@always_comb
def print_data():
print(x0, x1, x2, x3, s0, s1, y)
#Test Signal Bit Vectors
x0TV=Signal(x0TVs)
x1TV=Signal(x1TVs)
x2TV=Signal(x2TVs)
x3TV=Signal(x3TVs)
s0TV=Signal(s0TVs)
s1TV=Signal(s1TVs)
DUT=MUX4_1_MS(x0, x1, x2, x3, s0, s1, y)
@instance
def stimules():
for i in range(TestLen):
x0.next=int(x0TV[i])
x1.next=int(x1TV[i])
x2.next=int(x2TV[i])
x3.next=int(x3TV[i])
s0.next=int(s0TV[i])
s1.next=int(s1TV[i])
yield delay(1)
raise StopSimulation()
return instances()
TB=MUX4_1_MS_TBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('MUX4_1_MS_TBV');
MUX4_1_MS myHDL PYNQ-Z1 (YouTube)
HDL behavioral modeling is a "High" level, though not at the HLS level, HDL syntax where the intended hardware element is modeled via its intended abstract algorithm behavior. Thus the common computer science (and mathematician)tool of abstraction is borrowed and incorporated into the HDL syntax. The abstraction that follows has, like all things, its pros and cons.
As a pro, this means that the Hard Ware Designer is no longer consumed by the manuchia of implementing boolean algebra for every device and can instead focus on implementing the intended algorithm in hardware. And it is thanks to this blending of Software and Hardware that the design of digital devices has grown as prolific as it has. However, there is quite a cache for using behavioral modeling. First off HDL now absolutely requires synthesis tools that can map the behavioral statements to hardware. And even when the behavioral logic is mapped at least to the RTL level there is no escaping two points. 1. At the end of the day, the RTL will be implemented via Gate level devices in some form or another. 2. the way the synthesis tool has mapped the abstract behavioral to RTL may not be physical implementable especially in ASIC implementations.
For these reasons it as Hardware Developers using Behavioral HDL we have to be able to still be able to implement the smallest indivisible units of our HDL at the gate level. Must know what physical limits our target architecture (FPGA, ASIC, etc) has and keep within these limits when writing our HDL code. And lastly, we can not grow lazy in writing behavioral HDL, but must always see at least down to the major RTL elements that our behavioral statements are embodying.
In [41]:
@block
def MUX2_1_B(x0, x1, s, y):
"""
2:1 Multiplexer written via behavioral if
Input:
x0(bool): input channel 0
x1(bool): input channel 1
s(bool): channel selection input
Output:
y(bool): ouput
"""
@always_comb
def logic():
if s:
y.next=x1
else:
y.next=x0
return instances()
In [42]:
#generate systmatic and random test values
TestLen=10
SystmaticVals=list(itertools.product([0,1], repeat=3))
x0TVs=np.array([i[1] for i in SystmaticVals]).astype(int)
np.random.seed(15)
x0TVs=np.append(x0TVs, np.random.randint(0,2, TestLen)).astype(int)
x1TVs=np.array([i[2] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(16)
x1TVs=np.append(x1TVs, np.random.randint(0,2, TestLen)).astype(int)
sTVs=np.array([i[0] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(17)
sTVs=np.append(sTVs, np.random.randint(0,2, TestLen)).astype(int)
TestLen=len(x0TVs)
x0TVs, x1TVs, sTVs, TestLen
Out[42]:
In [43]:
Peeker.clear()
x0=Signal(bool(0)); Peeker(x0, 'x0')
x1=Signal(bool(0)); Peeker(x1, 'x1')
s=Signal(bool(0)); Peeker(s, 's')
y=Signal(bool(0)); Peeker(y, 'y')
DUT=MUX2_1_B(x0, x1, s, y)
def MUX2_1_B_TB():
"""
myHDL only testbench for module `MUX2_1_B`
"""
@instance
def stimules():
for i in range(TestLen):
x0.next=int(x0TVs[i])
x1.next=int(x1TVs[i])
s.next=int(sTVs[i])
yield delay(1)
raise StopSimulation()
return instances()
sim=Simulation(DUT, MUX2_1_B_TB(), *Peeker.instances()).run()
In [44]:
Peeker.to_wavedrom('x1', 'x0', 's', 'y')
In [45]:
MUX2_1_BData=Peeker.to_dataframe()
MUX2_1_BData=MUX2_1_BData[['x1', 'x0', 's', 'y']]
MUX2_1_BData
Out[45]:
In [46]:
Test=MUX2_1_ComboData[['x1', 'x0', 's', 'y']]==MUX2_1_BData
Test=Test.all().all()
print(f'`MUX2_1_B` Behavioral is Eqivlint to `MUX2_1_Combo`: {Test}')
In [47]:
DUT.convert()
VerilogTextReader('MUX2_1_B');
In [48]:
#create BitVectors
x0TVs=intbv(int(''.join(x0TVs.astype(str)), 2))[TestLen:]
x1TVs=intbv(int(''.join(x1TVs.astype(str)), 2))[TestLen:]
sTVs=intbv(int(''.join(sTVs.astype(str)), 2))[TestLen:]
x0TVs, bin(x0TVs), x1TVs, bin(x1TVs), sTVs, bin(sTVs)
Out[48]:
In [49]:
@block
def MUX2_1_B_TBV():
"""
myHDL -> Verilog testbench for module `MUX2_1_B`
"""
x0=Signal(bool(0))
x1=Signal(bool(0))
s=Signal(bool(0))
y=Signal(bool(0))
@always_comb
def print_data():
print(x0, x1, s, y)
#Test Signal Bit Vectors
x0TV=Signal(x0TVs)
x1TV=Signal(x1TVs)
sTV=Signal(sTVs)
DUT=MUX2_1_B(x0, x1, s, y)
@instance
def stimules():
for i in range(TestLen):
x0.next=int(x0TV[i])
x1.next=int(x1TV[i])
s.next=int(sTV[i])
yield delay(1)
raise StopSimulation()
return instances()
TB=MUX2_1_B_TBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('MUX2_1_B_TBV');
MUX2_1_B myHDL PYNQ-Z1 (YouTube)
In [50]:
@block
def MUX4_1_B(x0, x1, x2, x3, s0, s1, y):
"""
4:1 Multiblexer written in if-elif-else Behavioral
Input:
x0(bool): input channel 0
x1(bool): input channel 1
x2(bool): input channel 2
x3(bool): input channel 3
s1(bool): channel selection input bit 1
s0(bool): channel selection input bit 0
Output:
y(bool): ouput
"""
@always_comb
def logic():
if s0==0 and s1==0:
y.next=x0
elif s0==1 and s1==0:
y.next=x1
elif s0==0 and s1==1:
y.next=x2
else:
y.next=x3
return instances()
In [51]:
#generate systmatic and random test values
TestLen=5
SystmaticVals=list(itertools.product([0,1], repeat=6))
s0TVs=np.array([i[0] for i in SystmaticVals]).astype(int)
np.random.seed(15)
s0TVs=np.append(s0TVs, np.random.randint(0,2, TestLen)).astype(int)
s1TVs=np.array([i[1] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(16)
s1TVs=np.append(s1TVs, np.random.randint(0,2, TestLen)).astype(int)
x0TVs=np.array([i[2] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(17)
x0TVs=np.append(x0TVs, np.random.randint(0,2, TestLen)).astype(int)
x1TVs=np.array([i[3] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(18)
x1TVs=np.append(x1TVs, np.random.randint(0,2, TestLen)).astype(int)
x2TVs=np.array([i[4] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(19)
x2TVs=np.append(x2TVs, np.random.randint(0,2, TestLen)).astype(int)
x3TVs=np.array([i[5] for i in SystmaticVals]).astype(int)
#the random genrator must have a differint seed beween each generation
#call in order to produce differint values for each call
np.random.seed(20)
x3TVs=np.append(x3TVs, np.random.randint(0,2, TestLen)).astype(int)
TestLen=len(x0TVs)
SystmaticVals, s0TVs, s1TVs, x3TVs, x2TVs, x1TVs, x0TVs, TestLen
Out[51]:
In [52]:
Peeker.clear()
x0=Signal(bool(0)); Peeker(x0, 'x0')
x1=Signal(bool(0)); Peeker(x1, 'x1')
x2=Signal(bool(0)); Peeker(x2, 'x2')
x3=Signal(bool(0)); Peeker(x3, 'x3')
s0=Signal(bool(0)); Peeker(s0, 's0')
s1=Signal(bool(0)); Peeker(s1, 's1')
y=Signal(bool(0)); Peeker(y, 'y')
DUT=MUX4_1_B(x0, x1, x2, x3, s0, s1, y)
def MUX4_1_B_TB():
"""
myHDL only testbench for module `MUX4_1_B`
"""
@instance
def stimules():
for i in range(TestLen):
x0.next=int(x0TVs[i])
x1.next=int(x1TVs[i])
x2.next=int(x2TVs[i])
x3.next=int(x3TVs[i])
s0.next=int(s0TVs[i])
s1.next=int(s1TVs[i])
yield delay(1)
raise StopSimulation()
return instances()
sim=Simulation(DUT, MUX4_1_B_TB(), *Peeker.instances()).run()
In [53]:
Peeker.to_wavedrom()
In [54]:
MUX4_1_BData=Peeker.to_dataframe()
MUX4_1_BData=MUX4_1_BData[['x3', 'x2', 'x1', 'x0', 's1', 's0', 'y']]
MUX4_1_BData
Out[54]:
In [55]:
Test=MUX4_1_ComboData[['x3', 'x2', 'x1', 'x0', 's1', 's0', 'y']]==MUX4_1_BData
Test=Test.all().all()
print(f'Module `MUX4_1_B` works as exspected: {Test}')
In [56]:
DUT.convert()
VerilogTextReader('MUX4_1_B');
In [57]:
#create BitVectors
x0TVs=intbv(int(''.join(x0TVs.astype(str)), 2))[TestLen:]
x1TVs=intbv(int(''.join(x1TVs.astype(str)), 2))[TestLen:]
x2TVs=intbv(int(''.join(x2TVs.astype(str)), 2))[TestLen:]
x3TVs=intbv(int(''.join(x3TVs.astype(str)), 2))[TestLen:]
s0TVs=intbv(int(''.join(s0TVs.astype(str)), 2))[TestLen:]
s1TVs=intbv(int(''.join(s1TVs.astype(str)), 2))[TestLen:]
x0TVs, bin(x0TVs), x1TVs, bin(x1TVs), x2TVs, bin(x2TVs), x3TVs, bin(x3TVs), s0TVs, bin(s0TVs), s1TVs, bin(s1TVs)
Out[57]:
In [58]:
@block
def MUX4_1_B_TBV():
"""
myHDL -> Verilog testbench for module `MUX4_1_B`
"""
x0=Signal(bool(0))
x1=Signal(bool(0))
x2=Signal(bool(0))
x3=Signal(bool(0))
y=Signal(bool(0))
s0=Signal(bool(0))
s1=Signal(bool(0))
@always_comb
def print_data():
print(x0, x1, x2, x3, s0, s1, y)
#Test Signal Bit Vectors
x0TV=Signal(x0TVs)
x1TV=Signal(x1TVs)
x2TV=Signal(x2TVs)
x3TV=Signal(x3TVs)
s0TV=Signal(s0TVs)
s1TV=Signal(s1TVs)
DUT=MUX4_1_B(x0, x1, x2, x3, s0, s1, y)
@instance
def stimules():
for i in range(TestLen):
x0.next=int(x0TV[i])
x1.next=int(x1TV[i])
x2.next=int(x2TV[i])
x3.next=int(x3TV[i])
s0.next=int(s0TV[i])
s1.next=int(s1TV[i])
yield delay(1)
raise StopSimulation()
return instances()
TB=MUX4_1_B_TBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('MUX4_1_B_TBV');
MUX4_1_B myHDL PYNQ-Z1 (YouTube)
In [59]:
@block
def MUX4_1_BV(X, S, y):
"""
4:1 Multiblexerwritten in behvioral "if-elif-else"(case)
with BitVector inputs
Input:
X(4bitBV):input bit vector; min=0, max=15
S(2bitBV):selection bit vector; min=0, max=3
Output:
y(bool): ouput
"""
@always_comb
def logic():
if S==0:
y.next=X[0]
elif S==1:
y.next=X[1]
elif S==2:
y.next=X[2]
else:
y.next=X[3]
return instances()
In [60]:
XTVs=np.array([1,2,4,8])
XTVs=np.append(XTVs, np.random.choice([1,2,4,8], 6)).astype(int)
TestLen=len(XTVs)
np.random.seed(12)
STVs=np.arange(0,4)
STVs=np.append(STVs, np.random.randint(0,4, 5))
TestLen, XTVs, STVs
Out[60]:
In [61]:
Peeker.clear()
X=Signal(intbv(0)[4:]); Peeker(X, 'X')
S=Signal(intbv(0)[2:]); Peeker(S, 'S')
y=Signal(bool(0)); Peeker(y, 'y')
DUT=MUX4_1_BV(X, S, y)
def MUX4_1_BV_TB():
@instance
def stimules():
for i in STVs:
for j in XTVs:
S.next=int(i)
X.next=int(j)
yield delay(1)
raise StopSimulation()
return instances()
sim=Simulation(DUT, MUX4_1_BV_TB(), *Peeker.instances()).run()
In [62]:
Peeker.to_wavedrom('X', 'S', 'y', start_time=0, stop_time=2*TestLen+2)
In [63]:
MUX4_1_BVData=Peeker.to_dataframe()
MUX4_1_BVData=MUX4_1_BVData[['X', 'S', 'y']]
MUX4_1_BVData
Out[63]:
In [64]:
MUX4_1_BVData['x0']=None; MUX4_1_BVData['x1']=None; MUX4_1_BVData['x2']=None; MUX4_1_BVData['x3']=None
MUX4_1_BVData[['x3', 'x2', 'x1', 'x0']]=MUX4_1_BVData[['X']].apply(lambda bv: [int(i) for i in bin(bv, 4)], axis=1, result_type='expand')
MUX4_1_BVData['s0']=None; MUX4_1_BVData['s1']=None
MUX4_1_BVData[['s1', 's0']]=MUX4_1_BVData[['S']].apply(lambda bv: [int(i) for i in bin(bv, 2)], axis=1, result_type='expand')
MUX4_1_BVData=MUX4_1_BVData[['X', 'x0', 'x1', 'x2', 'x3', 'S', 's0', 's1', 'y']]
MUX4_1_BVData
Out[64]:
In [65]:
MUX4_1_BVData['yRef']=MUX4_1_BVData.apply(lambda row:y41EqN(row['x0'], row['x1'], row['x2'], row['x3'], row['s0'], row['s1']), axis=1).astype(int)
MUX4_1_BVData
Out[65]:
In [66]:
Test=(MUX4_1_BVData['y']==MUX4_1_BVData['yRef']).all()
print(f'Module `MUX4_1_BVData` works as exspected: {Test}')
In [67]:
DUT.convert()
VerilogTextReader('MUX4_1_BV');
In [68]:
ConstraintXDCTextReader('MUX4_1_BV');
MUX4_1_BV myHDL PYNQ-Z1 (YouTube)