\title{Shift Registers in myHDL} \author{Steven K Armour} \maketitle
Shift registers are common structures that move around data in primitive memory by rearranging the bit order. While shift registers are built as needed they typically fall into five categories. The four conversion types: PIPO, PISO, SISO, SIPO; and cyclic shift registers such as the ring and Johnson counters. Wich can be used as counters but are in reality shift registers since they reorder the bits in the modules internal memory
@misc{myhdl_2017, title={Johnson Counter}, url={http://www.myhdl.org/docs/examples/jc2.html}, journal={Myhdl.org}, author={myHDL}, year={2017} }
@misc{the shift register, url={https://www.electronics-tutorials.ws/sequential/seq_5.html}, journal={Electronics Tutorials} },
@misc{petrescu, title={Shift Registers}, url={http://www.csit-sun.pub.ro/courses/Masterat/Xilinx%20Synthesis%20Technology/toolbox.xilinx.com/docsan/xilinx4/data/docs/xst/hdlcode8.html}, journal={Csit-sun.pub.ro}, author={Petrescu, Adrian} },
@misc{reddy_2014, title={verilog code for ALU,SISO,PIPO,SIPO,PISO}, url={http://thrinadhreddy.blogspot.com/2014/01/verilog-code-for-alusisopiposipopiso.html}, journal={Thrinadhreddy.blogspot.com}, author={Reddy, Trinadh}, year={2014} }
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
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]:
CountVal=17
BitSize=int(np.log2(CountVal))+1; BitSize
Out[3]:
A PIPO shift Register is one of the most redundant of the four classic shift registers when used as a One Bus In, One Bus Out, thus the PIPO here is implemented as a One Bus In, Two Bus Out. Further, this opportunity is taken here to talk about HDL algorithms vs HDL Implementation by presenting the same One In, Two Out PIPO but implemented as an asynchronous case and two synchronous cases. While there are obvious differences algorithmically due to the asynchronous vs synchronous. The hardware implementation is even more strikingly different and serves as case in point that HDL programming is neither hardware or software but an intermedte between the worlds. But with grave consequences when translated into hardware that the HDL must keep in perspective when writing Hardware Descriptive Language code.
In [4]:
TestData=np.random.randint(0, 2**4, 15); TestData
Out[4]:
In [5]:
@block
def PIPO_AS1(DataIn, DataOut1, DataOut2):
"""
1:2 PIPO shift regestor with no clock (Asynchronous)
Input:
DataIn(bitvec)
Output:
DataOut1(bitVec): ouput one bitvec len should be same as
`DataIn`
DataOut2(bitVec):ouput two bitvec len should be same as
`DataIn`
"""
@always_comb
def logic():
DataOut1.next=DataIn
DataOut2.next=DataIn
return instances()
In [6]:
Peeker.clear()
clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')
DataIn=Signal(intbv(0)[4:]); Peeker(DataIn, 'DataIn')
DataOut1=Signal(intbv(0)[4:]); Peeker(DataOut1, 'DataOut1')
DataOut2=Signal(intbv(0)[4:]); Peeker(DataOut2, 'DataOut2')
DUT=PIPO_AS1(DataIn, DataOut1, DataOut2)
def PIPO_TB():
"""
myHDL only Testbench for `PIPO_*` module
"""
@always(delay(1))
def ClkGen():
clk.next=not clk
@instance
def stimules():
i=0
while True:
DataIn.next=int(TestData[i])
if i==14:
raise StopSimulation()
i+=1
yield clk.posedge
return instances()
sim=Simulation(DUT, PIPO_TB(), *Peeker.instances()).run()
In [7]:
Peeker.to_wavedrom()
In [8]:
PIPO_AS1Data=Peeker.to_dataframe();
PIPO_AS1Data=PIPO_AS1Data[PIPO_AS1Data['clk']==1]
PIPO_AS1Data.drop(['clk', 'rst'], axis=1, inplace=True)
PIPO_AS1Data.reset_index(drop=True, inplace=True)
PIPO_AS1Data
Out[8]:
In [9]:
DUT.convert()
VerilogTextReader('PIPO_AS1');
In [10]:
@block
def PIPO_S1(DataIn, DataOut1, DataOut2, clk, rst):
"""
one-in two-out PIPO typicaly found in the lititure
lacking buffering
Inputs:
DataIn(bitVec): one-in Parallel data int
clk(bool): clock
rst(bool): reset
Ouputs:
DataOut1(bitVec): Parallel out 1
DataOut2(bitVec): Parallel out 1
"""
@always(clk.posedge, rst.negedge)
def logic():
if rst:
DataOut1.next=0
DataOut2.next=0
else:
DataOut1.next=DataIn
DataOut2.next=DataIn
return instances()
In [11]:
Peeker.clear()
clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')
DataIn=Signal(intbv(0)[4:]); Peeker(DataIn, 'DataIn')
DataOut1=Signal(intbv(0)[4:]); Peeker(DataOut1, 'DataOut1')
DataOut2=Signal(intbv(0)[4:]); Peeker(DataOut2, 'DataOut2')
DUT=PIPO_S1(DataIn, DataOut1, DataOut2, clk, rst)
def PIPO_TB():
"""
myHDL only Testbench for `RingCounter` module
"""
@always(delay(1))
def ClkGen():
clk.next=not clk
@instance
def stimules():
i=0
while True:
DataIn.next=int(TestData[i])
if i==14:
raise StopSimulation()
i+=1
yield clk.posedge
return instances()
sim=Simulation(DUT, PIPO_TB(), *Peeker.instances()).run()
In [12]:
Peeker.to_wavedrom()
In [13]:
PIPO_S1Data=Peeker.to_dataframe();
PIPO_S1Data=PIPO_S1Data[PIPO_S1Data['clk']==1]
PIPO_S1Data.drop(['clk', 'rst'], axis=1, inplace=True)
PIPO_S1Data.reset_index(drop=True, inplace=True)
PIPO_S1Data
Out[13]:
In [14]:
DUT.convert()
VerilogTextReader('PIPO_S1');
In [15]:
@block
def PIPO_S2(DataIn, DataOut1, DataOut2, clk, rst):
"""
one-in two-out PIPO with buffering
Inputs:
DataIn(bitVec): one-in Parallel data int
clk(bool): clock
rst(bool): reset
Ouputs:
DataOut1(bitVec): Parallel out 1
DataOut2(bitVec): Parallel out 1
"""
Buffer=Signal(modbv(0)[len(DataIn):])
@always(clk.posedge, rst.negedge)
def logic():
if rst:
Buffer.next=0
else:
Buffer.next=DataIn
#not normaly found in PIPO; but is better practice since buffers help
#with isolation for ASIC desighn
@always_comb
def OuputBuffer():
DataOut1.next=Buffer
DataOut2.next=Buffer
return instances()
In [16]:
Peeker.clear()
clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')
DataIn=Signal(intbv(0)[4:]); Peeker(DataIn, 'DataIn')
DataOut1=Signal(intbv(0)[4:]); Peeker(DataOut1, 'DataOut1')
DataOut2=Signal(intbv(0)[4:]); Peeker(DataOut2, 'DataOut2')
DUT=PIPO_S2(DataIn, DataOut1, DataOut2, clk, rst)
def PIPO_TB():
"""
myHDL only Testbench for `PIPO_*` module
"""
@always(delay(1))
def ClkGen():
clk.next=not clk
@instance
def stimules():
i=0
while True:
DataIn.next=int(TestData[i])
if i==14:
raise StopSimulation()
i+=1
yield clk.posedge
return instances()
sim=Simulation(DUT, PIPO_TB(), *Peeker.instances()).run()
In [17]:
Peeker.to_wavedrom()
In [18]:
PIPO_S2Data=Peeker.to_dataframe();
PIPO_S2Data=PIPO_S2Data[PIPO_S2Data['clk']==1]
PIPO_S2Data.drop(['clk', 'rst'], axis=1, inplace=True)
PIPO_S2Data.reset_index(drop=True, inplace=True)
PIPO_S2Data
Out[18]:
In [19]:
DUT.convert()
VerilogTextReader('PIPO_S2');
In the asynchronous case, it can be seen in the RTL that incoming Bus is passed through a buffer and then the Bus is then junctioned to two outputs. This , therefore, does not have any synchronicity to a clock and is the HDL equivalent of taping the wires of the incoming bus to create a copy of the signal. What this design lacks in clock support it gains in resource saving and instantaneous interchange of the input signal to output signals
For the two synchronous cases the incoming signal is buffered by a register set, therefore any signal on the bus will not be present on the output buses for at least one clock cycle. Wich for clocked designs is a good thing but for signals that needs an instantaneous transmission, this is a failing in tradeoff. And while Vivado (and most other FPGA synthesis tools) recognized that the two designs are the same we should not take this for granted. Since other tools may not. Since at the RTL level the implementations are clearly different.
In the first case, the incoming signals is recorded by two registers that then feeds each of the respective output busses. While this means that we gain redundancy in parallel registers. The parallel memories could also become out of sync for any number of reasons. Further, this is a huge amount of resource if it were to be synthesized this way. In comparison, the second design uses a single register that then feeds each of the outputs where this design has better resource allocation and would not suffer from any asynchronicity between parallel registers. It now suffers in lacking redundancy since any issues in the one register effect both outputs.
The lesson here is that HDL should never be thought of as of programming. Instead, it is a sophisticated abstraction description of Hardware! And therefore HDL should always be written to satisfy the hardware constraints.
In [20]:
@block
def PISO(ReadBus, BusIn, SerialOut, clk, rst):
"""
Parallel In Serial Out right shift regestor
Input:
ReadBus(bool): read bus flag
BusIn(bool): Serial wire input
clk(bool): clock
rst(bool): reset
Output:
SerialOut(bool): Serial(wire) output data from `BusIn`
Note:
Does not have a finsh serial write indicator
"""
Buffer=Signal(intbv(0)[len(BusIn):])
@always(clk.posedge, rst.negedge)
def logic():
if rst:
Buffer.next=0
elif ReadBus:
Buffer.next=BusIn
else:
Buffer.next=Buffer>>1
#A more robust PISO would have a counter to trigger
#a finish serial write flag here
@always_comb
def SerialWriteOut():
SerialOut.next=Buffer[0]
return instances()
In [21]:
TestData=np.random.randint(0, 2**4, 3)
print(TestData)
#reverse bit order since right shift
TestDataBin="".join([bin(i, 4)[::-1] for i in TestData])
TestDataBin=[int(i) for i in TestDataBin]
TestDataBin
Out[21]:
In [22]:
Peeker.clear()
ReadBus=Signal(bool(0)); Peeker(ReadBus, 'ReadBus')
BusIn=Signal(intbv(0)[4:]); Peeker(BusIn, 'BusIn')
SerialOut=Signal(bool(0)); Peeker(SerialOut, 'SerialOut')
clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')
DUT=PISO(ReadBus, BusIn, SerialOut, clk, rst)
def PISO_TB():
"""
myHDL only Testbench for `SIPO` module
"""
@always(delay(1))
def ClkGen():
clk.next=not clk
@instance
def stimules():
yield clk.posedge
for i in TestData:
BusIn.next=int(i)
ReadBus.next=True
yield clk.posedge
ReadBus.next=False
for j in range(len(bin(i, 4))):
yield clk.posedge
raise StopSimulation()
return instances()
sim=Simulation(DUT, PISO_TB(), *Peeker.instances()).run()
print(TestData)
print(TestDataBin)
In [23]:
Peeker.to_wavedrom()
In [24]:
PISOData=Peeker.to_dataframe();
PISOData=PISOData[PISOData['clk']==1]
PISOData.drop(['clk', 'rst'], axis=1, inplace=True)
PISOData['BusBits']=PISOData['BusIn'].apply(lambda x:bin(x, 4))
PISOData.reset_index(drop=True, inplace=True)
PISOData
Out[24]:
In [25]:
DUT.convert()
VerilogTextReader('PISO');
In [26]:
@block
def SISO(SerialIn, SerialOut, clk, rst, BufferSize):
"""
SISO Left Shift registor
Input:
SerialIn(bool): serial input feed
clk(bool): clock signal
rst(bool):reset signal
Output:
SerialOut(bool): serial out delayed by BufferSize
Paramter:
BufferSize(int): size of SISO buffer, aka delay amount
"""
Buffer=Signal(modbv(0)[BufferSize:])
@always(clk.posedge, rst.negedge)
def logic():
if rst:
Buffer.next=0
else:
Buffer.next=concat(Buffer[BufferSize-1:0], SerialIn)
@always_comb
def ReadLeftMostToSer():
SerialOut.next=Buffer[BufferSize-1]
return instances()
In [27]:
SerialInTVLen=20
np.random.seed(71)
SerialInTV=np.random.randint(0,2,20).astype(int)
SerialInTV
Out[27]:
In [28]:
Peeker.clear()
SerialIn=Signal(bool(0)); Peeker(SerialIn, 'SerialIn')
SerialOut=Signal(bool(0)); Peeker(SerialOut, 'SerialOut')
clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')
BufferSize=4
DUT=SISO(SerialIn, SerialOut, clk, rst, BufferSize)
def SISO_TB():
"""
myHDL only Testbench for `SISO` module
"""
@always(delay(1))
def ClkGen():
clk.next=not clk
@instance
def stimules():
for i in range(SerialInTVLen):
SerialIn.next=int(SerialInTV[i])
yield clk.posedge
for i in range(2):
if i==0:
SerialIn.next=0
rst.next=1
else:
rst.next=0
yield clk.posedge
for i in range(BufferSize+1):
SerialIn.next=1
yield clk.posedge
raise StopSimulation()
return instances()
sim=Simulation(DUT, SISO_TB(), *Peeker.instances()).run()
In [29]:
Peeker.to_wavedrom()
In [30]:
SISOData=Peeker.to_dataframe()
SISOData
Out[30]:
In [31]:
SISOData=SISOData[SISOData['clk']==1]
SISOData.drop('clk', inplace=True, axis=1)
SISOData.reset_index(drop=True, inplace=True)
SISOData
Out[31]:
In [32]:
SISOData['SerialOutS4']=SISOData['SerialOut'].shift(-4)
SISOData
Out[32]:
In [33]:
SISOCheck=(SISOData[:16]['SerialIn'] == SISOData[:16]['SerialOutS4'].astype(int)).all()
print(f'SISO Check:{SISOCheck}')
In [34]:
DUT.convert()
VerilogTextReader('SISO');
In [35]:
#create BitVector for BufferGate_TBV
SerialInTVs=intbv(int(''.join(SerialInTV.astype(str)), 2))[SerialInTVLen:]
SerialInTVs, bin(SerialInTVs)
Out[35]:
In [36]:
BufferSize=4
@block
def SISO_TBV():
"""
myHDL -> Verilog Testbench for `SISO` module
"""
SerialIn=Signal(bool(0))
SerialOut=Signal(bool(0))
clk=Signal(bool(0))
rst=Signal(bool(0))
@always_comb
def print_data():
print(SerialIn, SerialOut, clk, rst)
SerialInTV=Signal(SerialInTVs)
DUT=SISO(SerialIn, SerialOut, clk, rst, BufferSize)
@instance
def clk_signal():
while True:
clk.next = not clk
yield delay(1)
@instance
def stimules():
for i in range(SerialInTVLen):
SerialIn.next=int(SerialInTV[i])
yield clk.posedge
for i in range(2):
if i==0:
SerialIn.next=0
rst.next=1
else:
rst.next=0
yield clk.posedge
for i in range(BufferSize+1):
SerialIn.next=1
yield clk.posedge
raise StopSimulation()
return instances()
TB=SISO_TBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('SISO_TBV');
In [37]:
@block
def SIPO(SerialIn, BusOut, clk, rst):
"""
Serial In Parallel Out right shift regestor
Input:
SerialIn(bool): Serial wire input
clk(bool): clock
rst(bool): reset
Output:
BusOut(bitVec): Parallel(Bus) output data from `SerialWire`
"""
Buffer=Signal(modbv(0)[len(BusOut):])
@always(clk.posedge, rst.negedge)
def logic():
if rst:
Buffer.next=0
else:
Buffer.next=concat(Buffer[len(Buffer):0],SerialIn)
@always_comb
def OuputBuffer():
BusOut.next=Buffer
return instances()
In [38]:
TestData=np.random.randint(0, 2**4, 3)
print(TestData)
TestDataBin="".join([bin(i, 4) for i in TestData])
TestDataBin=[int(i) for i in TestDataBin]
TestDataBin
Out[38]:
In [39]:
Peeker.clear()
SerialIn=Signal(bool(0)); Peeker(SerialIn, 'SerialIn')
BusOut=Signal(intbv(0)[4:]); Peeker(BusOut, 'BusOut')
clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')
DUT=SIPO(SerialIn, BusOut, clk, rst)
def SIPO_TB():
"""
myHDL only Testbench for `SIPO` module
"""
@always(delay(1))
def ClkGen():
clk.next=not clk
@instance
def stimules():
i=0
while True:
if i<len(TestDataBin):
SerialIn.next=TestDataBin[i]
elif i==len(TestDataBin):
pass
elif i>len(TestDataBin):
raise StopSimulation()
i+=1
yield clk.posedge
raise StopSimulation()
return instances()
sim=Simulation(DUT, SIPO_TB(), *Peeker.instances()).run()
In [40]:
Peeker.to_wavedrom()
In [41]:
SIPOData=Peeker.to_dataframe();
SIPOData=SIPOData[SIPOData['clk']==1]
SIPOData.drop(['clk', 'rst'], axis=1, inplace=True)
SIPOData['BusBits']=SIPOData['BusOut'].apply(lambda x:bin(x, 4))
SIPOData.reset_index(drop=True, inplace=True)
SIPOData
Out[41]:
In [42]:
TestData, TestDataBin
Out[42]:
In [43]:
DUT.convert()
VerilogTextReader('SIPO');
In [44]:
V=int(''.join([str(i) for i in TestDataBin]), 2)
SerialVal=intbv(V)[len(TestDataBin):]
SerialVal
Out[44]:
In [45]:
@block
def SIPO_TBV():
"""
myHDL -> Verilog Testbench for `SIPO` module
"""
SerialVals=Signal(SerialVal)
SerialIn=Signal(bool(0))
BusOut=Signal(intbv(0)[4:])
clk=Signal(bool(0))
rst=Signal(bool(0))
@always_comb
def print_data():
print(SerialIn, BusOut, clk, rst)
DUT=SIPO(SerialIn, BusOut, clk, rst)
@instance
def clk_signal():
while True:
clk.next = not clk
yield delay(1)
@instance
def stimules():
i=0
while True:
if i<len(TestDataBin):
SerialIn.next=SerialVals[i]
elif i==len(TestDataBin):
pass
elif i>len(TestDataBin):
raise StopSimulation()
i+=1
yield clk.posedge
raise StopSimulation()
return instances()
TB=SIPO_TBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('SIPO_TBV');
The Johnson Counter is implemented in this notebook about shift registers since in reality, a Johnson Counter is a cyclic shift register with single bit inversion. Hence the Johnson Counter, which goes by the other name of a Mobius Ring Counter. The following are aspects of Johnson Counter by Sougata Bhattacharjee [https://www.quora.com/What-is-the-difference-between-a-Johnson-counter-and-a-ring-counter]
\begin{itemize} \item In a Johnson Counter the output bar or Q(bar) of the last flip-flop is connected to the input of the first flip-flop \item If $n$ is the number of the flip-flop used, then the total number of states used is $2n$. \item The Johnson Counter is also known as walking counter, switching tail counter and is mostly used in phase shift or function generator. \item The decoding circuit is complex as compared to a ring counter. \item If input frequency is $f$ in Johnson Counter,then the output is $\dfrac{f}{2n}$. \item The total number of unused states in Johnson Counter is $(2^n - 2n)$ \item The main problem with Johnson counter is that once it enters into an unused state it is in a lockout state \end{itemize}For a four-bit Johnson Counter, the next state diagram is given by the following table from wikipedga
\begin{figure} \centerline{\includegraphics[width=1cm]{JohnsonCounterTable.png}} \caption{\label{fig:JohnsonTable} 4-bit Ring Counter next state table from [wikipedia](https://en.wikipedia.org/wiki/Ring_counter#Four-bit_ring-counter_sequences)} \end{figure}whereupon examining the next state table for the Johnson counter we can see why a Johnson counter is also called a Mobius counter
In [46]:
#Create the Direction States for Johnson Counter
DirStates=enum('Left','Halt','Right')
print(f"`Left` state repersentation is {bin(DirStates.Left)}")
print(f"`Halt` state repersentation is {bin(DirStates.Halt)}")
print(f"`Right` state repersentation is {bin(DirStates.Right)}")
In [47]:
@block
def JohnsonCount3(Dir, q, clk, rst):
"""
Based of the `jc2` exsample from the myHDL website
http://www.myhdl.org/docs/examples/jc2.html
Input:
Dir(state): Left,Right, Halt Direction States
clk(bool): input clock
rst(bool): reset signal
Ouput:
q(bitVec): the values in the D flip flops(aka counter)
"""
q_i=Signal(intbv(0)[len(q):])
@always(clk.posedge, rst.negedge)
def JCStateMachine():
#moore state machine
if rst:
q_i.next=0
elif Dir==DirStates.Left:
#set bit slice from left most to one from the right
#from bit slice from one to the left to the right most
q_i.next[len(q_i):1]=q_i[len(q_i)-1:]
#set next right most bit to negated one to the left bit
q_i.next[0]=not q_i[len(q_i)-1]
elif Dir==DirStates.Halt:
#create circular stop
q_i.next=q_i
elif Dir==DirStates.Right:
#set bit slice from one from the left to right most
#from bit slice left most bit to one from the right
q_i.next[len(q_i)-1:]=q_i[len(q_i):1]
#set next one bit from the right to be negated left most bit
q_i.next[len(q_i)-1]=not q_i[0]
@always_comb
def OuputBuffer():
q.next=q_i
return instances()
In [48]:
BitSize=4
Peeker.clear()
clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')
q=Signal(intbv(0)[BitSize:]); Peeker(q, 'q')
Dir=Signal(DirStates.Right); Peeker(Dir, 'Dir')
DUT=JohnsonCount3(Dir, q, clk, rst)
def JohnsonCount3_TB():
"""
myHDL only Testbench for `RingCounter` module
"""
@always(delay(1))
def ClkGen():
clk.next=not clk
@instance
def stimules():
i=0
while True:
if i==2*2*BitSize:
Dir.next=DirStates.Left
elif i==4*2*BitSize:
rst.next=1
elif i==4*2*BitSize+1:
rst.next=0
elif i==4*2*BitSize+2:
Dir.next=DirStates.Halt
if i==5*2*BitSize:
raise StopSimulation()
i+=1
yield clk.posedge
return instances()
sim=Simulation(DUT, JohnsonCount3_TB(), *Peeker.instances()).run()
In [49]:
Peeker.to_wavedrom()
In [50]:
JohnsonCount3Data=Peeker.to_dataframe()
JohnsonCount3Data=JohnsonCount3Data[JohnsonCount3Data['clk']==1]
JohnsonCount3Data.drop('clk', axis=1, inplace=True)
JohnsonCount3Data.reset_index(drop=True, inplace=True)
JohnsonCount3Data
Out[50]:
In [51]:
JohnsonCount3Data['q']=JohnsonCount3Data['q'].apply(lambda x:bin(x, BitSize))
JohnsonCount3Data
Out[51]:
In [52]:
JohnsonCount3Data[JohnsonCount3Data['Dir']==DirStates.Right]
Out[52]:
In [53]:
JohnsonCount3Data[JohnsonCount3Data['Dir']==DirStates.Left]
Out[53]:
In [54]:
JohnsonCount3Data[JohnsonCount3Data['Dir']==DirStates.Halt]
Out[54]:
In [55]:
DUT.convert()
VerilogTextReader('JohnsonCount3');
In [56]:
@block
def JohnsonCount3_TBV():
"""
myHDL -> Verilog Testbench for `UpDown_Counter` module
"""
clk=Signal(bool(0))
rst=Signal(bool(0))
q=Signal(intbv(0)[BitSize:])
Dir=Signal(DirStates.Right)
@always_comb
def print_data():
print(clk, rst, q, Dir)
DUT=JohnsonCount3(Dir, q, clk, rst)
@instance
def clk_signal():
while True:
clk.next = not clk
yield delay(1)
@instance
def stimules():
i=0
while True:
if i==2*2*BitSize:
Dir.next=DirStates.Left
elif i==4*2*BitSize:
rst.next=1
elif i==4*2*BitSize+1:
rst.next=0
elif i==4*2*BitSize+2:
Dir.next=DirStates.Halt
else:
pass
if i==5*2*BitSize:
raise StopSimulation()
i+=1
yield clk.posedge
return instances()
TB=JohnsonCount3_TBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('JohnsonCount3_TBV');
In [57]:
ConstraintXDCTextReader('PYNQ_Z1Constraints_JohnsonCount3');
Pay attention to line 22 that follows of setting the clk
input signal to Button 3
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets {clk}];
This is needed in the constraint file in order for the Implementation and Bitstream to work. What this line says to Vivado is "I know that a clock signal is hooked up to a nonstandard clock source, go ahead and make the connection so". This is because the internal rule checking in vivado will raise errors if one attempts to connect a nonclock source to what it perceives (in this case correctly) should be a clock input. Normally this should not be done this way. But because this is for teaching and the JohnsonCount3
was to be implemented as a stand-alone module without a clock divider at a clock speed of Hertz (FPGAs typically have Megahertz built-in clocks) this had to be done.
YouTube:Bi-Directional Johnson Counter from myHDL on the PYNQ-Z1
Like the Johnson Counter, which is the Mobius ring version of the Ring Counter, a Ring Counter is, in reality, a cyclic shift register since it simply shift the bits in its memory left or right. The following are aspects of Ring Counter by Sougata Bhattacharjee [https://www.quora.com/What-is-the-difference-between-a-Johnson-counter-and-a-ring-counter]
\begin{itemize} \item In a ring counter, the output of the last flip-flop is connected to the input of the first flip-flop. \item If $n$ is the number of flip-flops that are used in ring counter, the number of possible states are also $n$.That means the number of states is equal to the number of flip-flops used. \item A Ring counter is mostly used in Successive approximation type ADC and stepper motor control. \item Decoding is easy in a ring counter as the number of states is equal to the number of flip-flops. \item If the input frequency to a ring counter is $f$ then the output frequency $\dfrac{f}{n}$. \item The total number of unused states in the ring counter is $(2^n - n)$. \end{itemize}For a four-bit ring counter, the next state diagram is given by the following table from wikipedga
\begin{figure} \centerline{\includegraphics[width=1cm]{RingCounterTable.png}} \caption{\label{fig:RingTable} 4-bit Ring Counter next state table from [wikipedia](https://en.wikipedia.org/wiki/Ring_counter#Four-bit_ring-counter_sequences)} \end{figure}
In [58]:
#Create the Direction States for Ring Counter
DirStates=enum('Left','Halt','Right')
print(f"`Left` state repersentation is {bin(DirStates.Left)}")
print(f"`Halt` state repersentation is {bin(DirStates.Halt)}")
print(f"`Right` state repersentation is {bin(DirStates.Right)}")
In [59]:
@block
def RingCounter(seed, Dir, q, clk, rst):
"""
Seedable and direction controlable ring counter in myHDL
Input:
seed(bitvec): intial value for ring counter
Dir(enum): Direction contorl signal
clk(bool): clock
rst(bool): reset
Output
"""
q_i=Signal(intbv(int(seed))[len(q):])
@always(clk.posedge, rst.negedge)
def RCStateMachine():
#moore state machine
if rst:
q_i.next=seed
elif Dir==DirStates.Left:
q_i.next=concat(q_i[len(q_i)-1:0],q_i[len(q_i)-1])
elif Dir==DirStates.Halt:
#create circular stop
q_i.next=q_i
elif Dir==DirStates.Right:
q_i.next=concat(q_i[0], q_i[len(q_i):1])
@always_comb
def OuputBuffer():
q.next=q_i
return instances()
In [60]:
BitSize=4; seedval=3
Peeker.clear()
seed=Signal(intbv(seedval)[BitSize:]); Peeker(seed, 'seed')
clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')
q=Signal(intbv(0)[BitSize:]); Peeker(q, 'q')
Dir=Signal(DirStates.Right); Peeker(Dir, 'Dir')
DUT=RingCounter(seed, Dir, q, clk, rst)
def RingCounter_TB():
"""
myHDL only Testbench for `RingCounter` module
"""
@always(delay(1))
def ClkGen():
clk.next=not clk
@instance
def stimules():
i=0
while True:
if i==2*BitSize:
Dir.next=DirStates.Left
elif i==3*BitSize:
rst.next=1
elif i==3*BitSize+1:
rst.next=0
elif i==3*BitSize+2:
Dir.next=DirStates.Halt
if i==5*BitSize:
raise StopSimulation()
i+=1
yield clk.posedge
return instances()
sim=Simulation(DUT, RingCounter_TB(), *Peeker.instances()).run()
In [61]:
Peeker.to_wavedrom()
In [62]:
RingCountData=Peeker.to_dataframe()
RingCountData=RingCountData[RingCountData['clk']==1]
RingCountData.drop('clk', axis=1, inplace=True)
RingCountData.reset_index(drop=True, inplace=True)
RingCountData
Out[62]:
In [63]:
RingCountData['q']=RingCountData['q'].apply(lambda x:bin(x, BitSize))
RingCountData
Out[63]:
In [64]:
RingCountData[RingCountData['Dir']==DirStates.Right]
Out[64]:
In [65]:
RingCountData[RingCountData['Dir']==DirStates.Left]
Out[65]:
In [66]:
RingCountData[RingCountData['Dir']==DirStates.Halt]
Out[66]:
In [67]:
DUT.convert()
VerilogTextReader('RingCounter');
In [68]:
BitSize=4; seedval=3
@block
def RingCounter_TBV():
"""
myHDL -> verilog Testbench for `RingCounter` module
"""
seed=Signal(intbv(seedval)[BitSize:])
clk=Signal(bool(0))
rst=Signal(bool(0))
q=Signal(intbv(0)[BitSize:])
Dir=Signal(DirStates.Right)
@always_comb
def print_data():
print(seed, Dir, q, clk, rst)
DUT=RingCounter(seed, Dir, q, clk, rst)
@instance
def clk_signal():
while True:
clk.next = not clk
yield delay(1)
@instance
def stimules():
i=0
while True:
if i==2*BitSize:
Dir.next=DirStates.Left
elif i==3*BitSize:
rst.next=1
elif i==3*BitSize+1:
rst.next=0
elif i==3*BitSize+2:
Dir.next=DirStates.Halt
if i==5*BitSize:
raise StopSimulation()
i+=1
yield clk.posedge
return instances()
TB=RingCounter_TBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('RingCounter_TBV');
Video on how Block Design was made found here: YouTube:Seeded Ring Counter RTL IP Hookup in Vivado from myHDL to PYNQ-Z1
The Constant IP in the top left corner of the Block Design has the following internal parameterizations, accessed by right clicking on the IP and selecting "Customize Block" \begin{figure} \centerline{\includegraphics[width=10cm]{ConstIPSettings.png}} \caption{\label{fig:ConstSetting} Xilinx IP Constant 1.1 Internal Settings for Ring Counter Block design ; Xilinx Vivado 2017.4} \end{figure}
In [69]:
ConstraintXDCTextReader('PYNQ_Z1Constraints_RingCounterBlock');