\title{Bitwise Behavior in myHDL: Selecting, Shifting, Concatenation, Slicing} \author{Steven K Armour} \maketitle
<<
/>>
) behaviorconcat
behavior@misc{myhdl_2018, title={Hardware-oriented types MyHDL 0.10 documentation}, url={http://docs.myhdl.org/en/stable/manual/hwtypes.html}, journal={Docs.myhdl.org}, author={myHDL}, year={2018} },
@misc{vandenbout_2018, title={pygmyhdl 0.0.3 documentation}, url={https://xesscorp.github.io/pygmyhdl/docs/_build/singlehtml/index.html}, journal={Xesscorp.github.io}, author={Vandenbout, Dave}, year={2018} }
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 random
#https://github.com/jrjohansson/version_information
%load_ext version_information
%version_information myhdl, myhdlpeek, numpy, pandas, matplotlib, sympy, random
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
In [3]:
CountVal=17
BitSize=int(np.log2(CountVal))+1; BitSize
Out[3]:
In [4]:
TV=intbv(-93)[8:].signed()
print(f'Value:{int(TV)}, Binary {bin(TV)}')
In [5]:
for i in range(len(TV)):
print(f'Bit from LSB: {i}, Selected Bit: {int(TV[i])}')
which shows that when selecting a single bit from a BitVector that selection [0] is the Least Significant Bit (LSB) (inclusive behavior) while for the Most Significant Bit (MSB) will be the index of the BitVector length -1 (noninclusive behavior)
In [6]:
try:
TV[-1]
except ValueError:
print("ValueError: negative shift count")
This means that negative indexing using python's list selection wrap around is NOT implemented in a myHDL intbv
In [7]:
TV=modbv(-93)[8:].signed()
print(f'Value:{int(TV)}, Binary {bin(TV)}')
try:
TV[-1]
except ValueError:
print("ValueError: negative shift count")
nor is the negative wrapping supported by the use of the modbv
In [8]:
TV=intbv(93)[8:]
TV_S=intbv(-93)[8:].signed()
TV_M=modbv(-93)[8:].signed()
print(f'`intbv`:Value:{int(TV)}, Binary {bin(TV)}, [8]:{int(TV[8])}, [9]:{int(TV[9])}')
print(f'`intbv signed`:Value:{int(TV_S)}, Binary {bin(TV_S)}, [8]:{int(TV_S[8])}, [9]:{int(TV_S[9])}')
print(f'`modbv`:Value:{int(TV_M)}, Binary {bin(TV_M)}, [8]:{int(TV_M[8])}, [9]:{int(TV_M[9])}')
Thus selecting above the MSB will generate a 0
if the Bit Vector is not signed where as selecting above the MSB for a signed bit will produce a 1
.
In [9]:
TV=Signal(intbv(93)[8:])
In [10]:
TV[0], TV(0), TV[9], TV(9)
Out[10]:
The difference is that outside of a generator, bit selection of a signal
using []
only returns a value and not a signal that is only returned using ()
. This is important to know since only a Signal
can be converted to registers/wires in the conversion from myHDL to Verilog/VHDL
In [11]:
@block
def BitSelectDemo(Index, Res, SignRes):
"""
Bit Selection Demo
Input:
Index(4BitVec): value for selection from internal refrances
Output:
Res(8BitVec): BitVector with Bit Location set from `Index` from
refrance internal 8Bit `intbv` with value 93
SignRes(8BitVec Signed): signed BitVector with Bit Location set from `Index` from
refrance internal signed 8Bit `intbv` with value -93
"""
Ref=Signal(intbv(93)[8:])
RefS=Signal(intbv(-93)[8:].signed())
@always_comb
def logic():
Res.next[Index]=Ref[Index]
SignRes.next[Index]=RefS[Index]
return instances()
Note: that in the above the module also shows how to perform bit selection assignment. The output signal Res
or SignRes
is assigned a value from the References at position Index
but then the bit from the references is set to position Index
in the outputs. Notice that the syntax is
Variable.next[index]=
The same structure is also used in setting bit slices so that for a big slice assignment is
Variable.next[MSB:LSB]=
In [12]:
Peeker.clear()
Index=Signal(intbv(0)[4:]); Peeker(Index, 'Index')
Res=Signal(intbv(0)[8:]); Peeker(Res, 'Res')
SignRes=Signal(intbv(0)[8:].signed()); Peeker(SignRes, 'SignRes')
DUT=BitSelectDemo(Index, Res, SignRes)
def BitSelectDemo_TB():
"""
myHDL only Testbench
"""
@instance
def stimules():
for i in range(7):
Index.next=i
yield delay(1)
raise StopSimulation()
return instances()
sim=Simulation(DUT, BitSelectDemo_TB(), *Peeker.instances()).run()
Note that if the for
loop range was increased beyond 7 an error would be triggered.
In [13]:
Peeker.to_wavedrom('Index', 'Res', 'SignRes')
In [14]:
BitSelectDemoData=Peeker.to_dataframe()
BitSelectDemoData['Res Bin']=BitSelectDemoData['Res'].apply(lambda Row: bin(Row, 8), 1)
BitSelectDemoData['SignRes Bin']=BitSelectDemoData['SignRes'].apply(lambda Row: bin(Row, 8), 1)
BitSelectDemoData=BitSelectDemoData[['Index', 'Res', 'Res Bin', 'SignRes', 'SignRes Bin']]
BitSelectDemoData
Out[14]:
In [15]:
DUT.convert()
VerilogTextReader('BitSelectDemo');
In [16]:
DUT.convert('VHDL')
VHDLTextReader('BitSelectDemo');
In [17]:
@block
def BitSelectDemo_TB_V_VHDL():
"""
myHDL -> Verilog/VHDL Testbench for `BitSelectDemo`
"""
Index=Signal(intbv(0)[4:])
Res=Signal(intbv(0)[8:])
SignRes=Signal(intbv(0)[8:].signed())
@always_comb
def print_data():
print(Index, Res, SignRes)
DUT=BitSelectDemo(Index, Res, SignRes)
@instance
def stimules():
for i in range(7):
Index.next=i
yield delay(1)
raise StopSimulation()
return instances()
TB=BitSelectDemo_TB_V_VHDL()
In [18]:
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('BitSelectDemo_TB_V_VHDL');
In [19]:
TB.convert(hdl="VHDL", initial_values=True)
VHDLTextReader('BitSelectDemo_TB_V_VHDL');
In [20]:
#Left Shift test with intbv
#intialize
TV=intbv(52)[8:]
print(TV, bin(TV, 8))
#demenstrate left shifting with intbv
for i in range(8):
LSRes=TV<<i
print(f'Left Shift<<{i}; Binary: {bin(LSRes)}, BitLen: {len(bin(LSRes))}, Value:{LSRes}')
In [21]:
#Left Shift test with intbv signed
#intialize
TV=intbv(-52)[8:].signed()
print(TV, bin(TV, 8))
#demenstrate left shifting with intbv signed
for i in range(8):
LSRes=(TV<<i).signed()
print(f'Left Shift<<{i}; Binary: {bin(LSRes)}, BitLen: {len(bin(LSRes))}, Value:{LSRes}')
In [22]:
#Left Shift test with modbv
#intialize
TV=modbv(52)[8:]
print(TV, bin(TV, 8))
#demenstrate left shifting with modbv
for i in range(8):
LSRes=(TV<<i).signed()
print(f'Left Shift<<{i}; Binary: {bin(LSRes)}, BitLen: {len(bin(LSRes))}, Value:{LSRes}')
As can be seen, Left shifting tacks on a number of zeros equivalent to the shift increment to the end of the binary expression for the value. This then increases the size of the needed register that the resulting value needs to set into for each left shift that does not undergo right bit cutoff
In [23]:
#Right Shift test with intbv
#intialize
TV=intbv(52)[8:]
print(TV, bin(TV, 8))
#demenstrate left shifting with intbv
for i in range(8):
LSRes=TV>>i
print(f'Right Shift>>{i}; Binary: {bin(LSRes)}, BitLen: {len(bin(LSRes))}, Value:{LSRes}')
In [24]:
#Right Shift test with intbv signed
#intialize
TV=intbv(-52)[8:].signed()
print(TV, bin(TV, 8))
#demenstrate left shifting with intbv signed
for i in range(8):
LSRes=(TV>>i)
print(f'Right Shift>>{i}; Binary: {bin(LSRes)}, BitLen: {len(bin(LSRes))}, Value:{LSRes}')
In [25]:
#Right Shift test with modbv
#intialize
TV=modbv(52)[8:]
print(TV, bin(TV, 8))
#demenstrate left shifting with modbv
for i in range(8):
LSRes=(TV>>i)
print(f'Right Shift>>{i}; Binary: {bin(LSRes)}, BitLen: {len(bin(LSRes))}, Value:{LSRes}')
As can be seen, the right shift moves values (shifts) to the right by the shift increment while preserving the length of the register that is being shifted. While this means that overflow is not going to be in encountered. Right shifting trades that vulnerability for information loss as any information carried in the leftmost bits gets lost as it is shifted right beyond of the length of the register
In [26]:
@block
def ShiftingDemo(ShiftVal, RSRes, LSRes):
"""
Module to Demo Shifting Behavior in myHDL refrance value
-55 8Bit
Input:
ShiftVal(4BitVec): shift amount, for this demo to not
use values greater then 7
Output:
RSRes(8BitVec Signed): output of Right Shifting
LSRes (15BitVec Signed): output of Left Shifting
"""
RefVal=Signal(intbv(-55)[8:].signed())
@always_comb
def logic():
RSRes.next=RefVal>>ShiftVal
LSRes.next=RefVal<<ShiftVal
return instances()
In [27]:
Peeker.clear()
ShiftVal=Signal(intbv()[4:]); Peeker(ShiftVal, 'ShiftVal')
RSRes=Signal(intbv()[8:].signed()); Peeker(RSRes, 'RSRes')
LSRes=Signal(intbv()[15:].signed()); Peeker(LSRes, 'LSRes')
DUT=ShiftingDemo(ShiftVal, RSRes, LSRes)
def ShiftingDemo_TB():
"""
myHDL only Testbench for `ShiftingDemo`
"""
@instance
def stimules():
for i in range(8):
ShiftVal.next=i
yield delay(1)
raise StopSimulation()
return instances()
sim=Simulation(DUT, ShiftingDemo_TB(), *Peeker.instances()).run()
In [28]:
Peeker.to_wavedrom('ShiftVal', 'LSRes', 'RSRes');
In [29]:
Peeker.to_dataframe()[['ShiftVal', 'LSRes', 'RSRes']]
Out[29]:
Unfortunately this is an unsynthesizable module as is due
assign RefVal = 8'd-55;
needing to be changed to
assign RefVal = -8'd55;
after wich the module is synthesizable
In [30]:
DUT.convert()
VerilogTextReader('ShiftingDemo');
In [31]:
DUT.convert(hdl='VHDL')
VHDLTextReader('ShiftingDemo');
In [61]:
@block
def ShiftingDemo_TB_V_VHDL():
"""
myHDL -> verilog/VHDL testbench for `ShiftingDemo`
"""
ShiftVal=Signal(intbv()[4:])
RSRes=Signal(intbv()[8:].signed())
LSRes=Signal(intbv()[15:].signed())
@always_comb
def print_data():
print(ShiftVal, RSRes, LSRes)
DUT=ShiftingDemo(ShiftVal, RSRes, LSRes)
@instance
def stimules():
for i in range(8):
ShiftVal.next=i
yield delay(1)
raise StopSimulation()
return instances()
TB=ShiftingDemo_TB_V_VHDL()
In [33]:
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('ShiftingDemo_TB_V_VHDL');
In [34]:
TB.convert(hdl="VHDL", initial_values=True)
VHDLTextReader('ShiftingDemo_TB_V_VHDL');
In [35]:
RefVal=intbv(25)[6:]; RefVal, bin(RefVal, 6)
Out[35]:
In [36]:
Result=concat(True, RefVal); Result, bin(Result)
Out[36]:
In [37]:
ResultSigned=concat(True, RefVal).signed(); ResultSigned, bin(ResultSigned)
Out[37]:
In [38]:
@block
def ConcatDemo(Res, ResS):
"""
`concat` demo
Input:
None
Ouput:
Res(7BitVec): concat result
Res(7BitVec Signed): concat result that is signed
"""
RefVal=Signal(intbv(25)[6:])
@always_comb
def logic():
Res.next=concat(True, RefVal)
ResS.next=concat(True, RefVal).signed()
return instances()
In [39]:
Peeker.clear()
Res=Signal(intbv(0)[7:]); Peeker(Res, 'Res')
ResS=Signal(intbv(0)[7:].signed()); Peeker(ResS, ResS)
DUT=ConcatDemo(Res, ResS)
def ConcatDemo_TB():
"""
myHDL only Testbench for `ConcatDemo`
"""
@instance
def stimules():
for i in range(2):
yield delay(1)
raise StopSimulation()
return instances()
sim=Simulation(DUT, ConcatDemo_TB(), *Peeker.instances()).run()
In [40]:
Peeker.to_wavedrom()
In [41]:
DUT.convert()
VerilogTextReader('ConcatDemo');
In [42]:
DUT.convert('VHDL')
VHDLTextReader('ConcatDemo');
In [63]:
@block
def ConcatDemo_TB_V_VHDL():
"""
myHDL-> Verilog/VHDL Testbench
"""
Res=Signal(intbv(0)[7:])
ResS=Signal(intbv(0)[7:].signed())
@always_comb
def print_data():
print(Res, ResS)
DUT=ConcatDemo(Res, ResS)
@instance
def stimules():
for i in range(2):
yield delay(1)
raise StopSimulation()
return instances()
TB=ConcatDemo_TB_V_VHDL()
In [44]:
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('ConcatDemo_TB_V_VHDL');
In [45]:
TB.convert(hdl="VHDL", initial_values=True)
VHDLTextReader('ConcatDemo_TB_V_VHDL');
These example values come from the future work with floating point implemented in fixed point architecture which is incredibly important for Digital Signal Processing as will be shown. For now, just understand that the example are based on multiplying two Q4.4 (8bit fixed point) number resulting in Q8.8 (16bit fixed point) product
the following is an example of truncation from 16bit to 8bit rounding that shows how bit slicing works in myHDL. The truncation bit slicing keeps values from the far left (Most Significant Bit (MSB) ) to the rightmost specified bit (Least Significant Bit (LSB))
In [46]:
TV=intbv(1749)[16:]
print(f'int 1749 in bit is {bin(TV, len(TV))}')
for j in range(16):
try:
Trunc=TV[16:j]
print(f'Binary: {bin(Trunc, len(Trunc))}, Bits: {len(Trunc)}, rep: {int(Trunc)}, MSB:15, LSB: {j}')
except ValueError:
print ('MSB {15} is <= LSB {j}')
In [47]:
TV=intbv(1749)[16:]
print(f'int 1749 in bit is {bin(TV, len(TV))}')
for i in reversed(range(16+1)):
try:
Trunc=TV[i:0]
print(f'Binary: {bin(Trunc, len(Trunc))}, Bits: {len(Trunc)}, rep: {int(Trunc)}, MSB:{i}, LSB: {0}')
except ValueError:
print ('MSB is <= LSB index')
In [48]:
TV=intbv(-1749)[16:].signed()
print(f'int -1749 in bit is {bin(TV, len(TV))}')
for j in range(16):
try:
Trunc=TV[16:j].signed()
print(f'Binary: {bin(Trunc, len(Trunc))}, Bits: {len(Trunc)}, rep: {int(Trunc)}, MSB:15, LSB: {j}')
except ValueError:
print ('MSB {15} is <= LSB {j}')
In [49]:
TV=intbv(-1749)[16:].signed()
print(f'int -1749 in bit is {bin(TV, len(TV))}')
for i in reversed(range(16+1)):
try:
Trunc=TV[i:0].signed()
print(f'Binary: {bin(Trunc, len(Trunc))}, Bits: {len(Trunc)}, rep: {int(Trunc)}, MSB:{i}, LSB: {0}')
except ValueError:
print ('MSB is <= LSB index')
In [50]:
TV=modbv(1749)[16:]
print(f'int 1749 in bit is {bin(TV, len(TV))}')
for j in range(16):
try:
Trunc=TV[16:j]
print(f'Binary: {bin(Trunc, len(Trunc))}, Bits: {len(Trunc)}, rep: {int(Trunc)}, MSB:15, LSB: {j}')
except ValueError:
print ('MSB {15} is <= LSB {j}')
In [51]:
TV=modbv(1749)[16:]
print(f'int 1749 in bit is {bin(TV, len(TV))}')
for i in reversed(range(16+1)):
try:
Trunc=TV[i:0]
print(f'Binary: {bin(Trunc, len(Trunc))}, Bits: {len(Trunc)}, rep: {int(Trunc)}, MSB:{i}, LSB: {0}')
except ValueError:
print ('MSB is <= LSB index')
In [52]:
@block
def BitSlicingDemo(MSB, LSB, Res):
"""
Demenstration Module for Bit Slicing in myHDL
Inputs:
MSB (5BitVec): Most Signficant Bit Index Must be > LSB,
ex: if LSB==0 MSB must range between 1 and 15
LSB (5BitVec): Lest Signficant Bit Index Must be < MSB
ex: if MSB==15 LSB must range beteen 0 and 15
Outputs:
Res(16BitVec Signed): Result of the slicing operation from
Refrance Vales (hard coded in module) -1749 (16BitVec Signed)
"""
RefVal=Signal(intbv(-1749)[16:].signed())
@always_comb
def logic():
Res.next=RefVal[MSB:LSB].signed()
return instances()
In [53]:
Peeker.clear()
MSB=Signal(intbv(16)[5:]); Peeker(MSB, 'MSB')
LSB=Signal(intbv(0)[5:]); Peeker(LSB, 'LSB')
Res=Signal(intbv(0)[16:].signed()); Peeker(Res, 'Res')
DUT=BitSlicingDemo(MSB, LSB, Res)
def BitslicingDemo_TB():
"""
myHDL only Testbench for `BitSlicingDemo`
"""
@instance
def stimules():
for j in range(15):
MSB.next=16
LSB.next=j
yield delay(1)
for i in reversed(range(1, 16)):
MSB.next=i
LSB.next=0
yield delay(1)
raise StopSimulation()
return instances()
sim=Simulation(DUT, BitslicingDemo_TB(), *Peeker.instances()).run()
In [54]:
Peeker.to_wavedrom('MSB', 'LSB', 'Res')
In [55]:
Peeker.to_dataframe()[['MSB', 'LSB', 'Res']]
Out[55]:
The following is unsynthesizable since Verilog requires that the indexes in bit slicing (aka Part-selects) be constant values. Along with the error in assign RefVal = 16'd-1749;
However, the generated Verilog code from BitSlicingDemo
does hold merit in showing how the index values are mapped from myHDL to Verilog
In [56]:
DUT.convert()
VerilogTextReader('BitSlicingDemo');
In [57]:
DUT.convert(hdl='VHDL')
VHDLTextReader('BitSlicingDemo');
In [58]:
@block
def BitslicingDemo_TB_V_VHDL():
"""
myHDL -> Verilog/VHDL Testbench for `BitSlicingDemo`
"""
MSB=Signal(intbv(16)[5:])
LSB=Signal(intbv(0)[5:])
Res=Signal(intbv(0)[16:].signed())
@always_comb
def print_data():
print(MSB, LSB, Res)
DUT=BitSlicingDemo(MSB, LSB, Res)
@instance
def stimules():
for j in range(15):
MSB.next=16
LSB.next=j
yield delay(1)
#!!! reversed is not being converted
#for i in reversed(range(1, 16)):
# MSB.next=i
# LSB.next=0
# yield delay(1)
raise StopSimulation()
return instances()
TB=BitslicingDemo_TB_V_VHDL()
In [59]:
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('BitslicingDemo_TB_V_VHDL');
In [60]:
TB.convert(hdl="VHDL", initial_values=True)
VHDLTextReader('BitslicingDemo_TB_V_VHDL');
In [ ]: