\title{Counters in myHDL} \author{Steven K Armour} \maketitle
Counters play a vital role in Digital Hardware, ranging from Clock Dividers; (see below) to event triggers by recording the number of events that have occurred or will still need to occur (all the counters here in use a clock as the counting event but this is easily changed). Presented below are some basic HDL counters (Up, Down, Hybridized Up-Down) in myHDL.
@misc{loi le_2017, title={Verilog code for counter with testbench}, url={http://www.fpga4student.com/2017/03/verilog-code-for-counter-with-testbench.html}, journal={Fpga4student.com}, author={Loi Le, Van}, year={2017} }
@misc{digilent_2018, title={Learn.Digilentinc | Counter and Clock Divider}, url={https://learn.digilentinc.com/Documents/262}, journal={Learn.digilentinc.com}, author={Digilent}, year={2018} }
In [1]:
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]:
ModBV=modbv(0)[BitSize:]
IntBV=intbv(0)[BitSize:]
print(f"`ModBV` max is {ModBV.max}; min is {ModBV.min}")
print(f"`IntBV` max is {IntBV.max}; min is {IntBV.min}")
In [5]:
for _ in range(ModBV.max*2):
try:
ModBV+=1; IntBV+=1
print(f"`ModBV` value is {ModBV}; `IntBV` value is {IntBV}")
except ValueError:
ModBV+=1
print(f"`ModBV` value is {ModBV}; `IntBV` value is {IntBV} and INVALID")
In [6]:
ModBV=modbv(2**BitSize -1)[BitSize:]
IntBV=intbv(2**BitSize -1)[BitSize:]
print(f"`ModBV` max is {ModBV.max}; min is {ModBV.min}")
print(f"`IntBV` max is {IntBV.max}; min is {IntBV.min}")
In [7]:
for _ in range(ModBV.max*2):
try:
ModBV-=1; IntBV-=1
print(f"`ModBV` value is {ModBV}; `IntBV` value is {IntBV}")
except ValueError:
ModBV-=0
print(f"`ModBV` value is {ModBV}; `IntBV` value is {IntBV} and INVALID")
up counters are counters that count up to a target value from a lower starting value. The following counter is a simple one that uses the clock as incrementer (think one clock cycle as one swing of an old grandfather clock pendulum). But more complicated counters can use any signal as an incrementer. This Counter also has a signal the indicates that the counter has been triggered before the modulus values for the internal counter is reset. This is because this counter tries to reproduce the behavior of timers found on common apps that show how much time has elapsed since the counter has run up
\begin{figure} \centerline{\includegraphics[width=10cm]{Up_Counter.png}} \caption{\label{fig:RP} Up_Counter Functianl Digram } \end{figure}
In [8]:
@block
def Up_Counter(count, Trig, clk, rst, CountVal, BitSize):
"""
UpCounter
Input:
clk(bool): system clock feed
rst(bool): clock reset signal
Ouput:
count (bit vector): current count value; count
Trig(bool)
Parmeter(Python Only):
CountVal(int): value to count to
BitSize (int): Bitvalue size is log_2(CountVal)+1
"""
#internals
count_i=Signal(modbv(0)[BitSize:])
Trig_i=Signal(bool(0))
@always(clk.posedge, rst.negedge)
def logic():
if rst:
count_i.next=0
Trig_i.next=0
elif count_i%CountVal==0 and count_i!=0:
Trig_i.next=1
count_i.next=0
else:
count_i.next=count_i+1
@always_comb
def OuputBuffer():
count.next=count_i
Trig.next=Trig_i
return instances()
In [9]:
Peeker.clear()
clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')
Trig=Signal(bool(0)); Peeker(Trig, 'Trig')
count=Signal(modbv(0)[BitSize:]); Peeker(count, 'count')
DUT=Up_Counter(count, Trig, clk, rst, CountVal, BitSize)
def Up_CounterTB():
"""
myHDL only Testbench for `Up_Counter` module
"""
@always(delay(1))
def ClkGen():
clk.next=not clk
@instance
def stimules():
i=0
while True:
if i==int(CountVal*1.5):
rst.next=1
elif i==int(CountVal*1.5)+1:
rst.next=0
if i==int(CountVal*2.5):
raise StopSimulation()
i+=1
yield clk.posedge
return instances()
sim=Simulation(DUT, Up_CounterTB(), *Peeker.instances()).run()
In [10]:
Peeker.to_wavedrom()
In [11]:
Up_CounterData=Peeker.to_dataframe()
Up_CounterData=Up_CounterData[Up_CounterData['clk']==1]
Up_CounterData.drop('clk', axis=1, inplace=True)
Up_CounterData.reset_index(drop=True, inplace=True)
Up_CounterData
Out[11]:
In [12]:
DUT.convert()
VerilogTextReader('Up_Counter');
In [13]:
ResetAt=int(CountVal*1.5)+1
StopAt=int(CountVal*2.5)
@block
def Up_CounterTBV():
"""
myHDL -> Verilog Testbench for `Up_Counter` module
"""
clk=Signal(bool(0))
rst=Signal(bool(0))
Trig=Signal(bool(0))
count=Signal(modbv(0)[BitSize:])
@always_comb
def print_data():
print(clk, rst, Trig, count)
DUT=Up_Counter(count, Trig, clk, rst, CountVal, BitSize)
@instance
def clk_signal():
while True:
clk.next = not clk
yield delay(1)
@instance
def stimules():
i=0
while True:
if i==ResetAt:
rst.next=1
elif i==(ResetAt+1):
rst.next=0
else:
pass
if i==StopAt:
raise StopSimulation()
i+=1
yield clk.posedge
return instances()
TB=Up_CounterTBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('Up_CounterTBV');
Down Counters Count Down from a set upper value to a set target lower value. The following Down Counter is a simple revamp of the previous Up Counter. Thus it starts from the CountVal
and counts down to zero to trigger the trigger signal that it has completed one countdown cycle before the internal counter resets to restart the countdown.
In [14]:
@block
def Down_Counter(count, Trig, clk, rst, StartVal, BitSize):
"""
DownCounter
Input:
clk(bool): system clock feed
rst(bool): clock reset signal
Ouput:
count (bit vector): current count value; count
Trig(bool)
Parmeter(Python Only):
StartVal(int): value to count from
BitSize (int): Bitvalue size is log_2(CountVal)+1
CatButt
"""
#internal counter value
count_i=Signal(modbv(StartVal)[BitSize:])
@always(clk.posedge, rst.negedge)
def logic():
if rst:
count_i.next=StartVal
Trig.next=0
elif count_i==0:
Trig.next=1
count_i.next=StartVal
else:
count_i.next=count_i-1
@always_comb
def OuputBuffer():
count.next=count_i
return instances()
In [15]:
Peeker.clear()
clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')
Trig=Signal(bool(0)); Peeker(Trig, 'Trig')
count=Signal(modbv(0)[BitSize:]); Peeker(count, 'count')
DUT=Down_Counter(count, Trig, clk, rst, CountVal, BitSize)
def Down_CounterTB():
"""
myHDL only Testbench for `Down_Counter` module
"""
@always(delay(1))
def ClkGen():
clk.next=not clk
@instance
def stimules():
i=0
while True:
if i==int(CountVal*1.5):
rst.next=1
elif i==int(CountVal*1.5)+1:
rst.next=0
if i==int(CountVal*2.5):
raise StopSimulation()
i+=1
yield clk.posedge
return instances()
sim=Simulation(DUT, Down_CounterTB(), *Peeker.instances()).run()
In [16]:
Peeker.to_wavedrom()
In [17]:
Down_CounterData=Peeker.to_dataframe()
Down_CounterData=Down_CounterData[Down_CounterData['clk']==1]
Down_CounterData.drop('clk', axis=1, inplace=True)
Down_CounterData.reset_index(drop=True, inplace=True)
Down_CounterData
Out[17]:
In [18]:
DUT.convert()
VerilogTextReader('Down_Counter');
In [19]:
ResetAt=int(CountVal*1.5)
StopAt=int(CountVal*2.5)
@block
def Down_CounterTBV():
"""
myHDL -> Verilog Testbench for `Down_Counter` module
"""
clk=Signal(bool(0))
rst=Signal(bool(0))
Trig=Signal(bool(0))
count=Signal(modbv(0)[BitSize:])
@always_comb
def print_data():
print(clk, rst, Trig, count)
DUT=Down_Counter(count, Trig, clk, rst, CountVal, BitSize)
@instance
def clk_signal():
while True:
clk.next = not clk
yield delay(1)
@instance
def stimules():
i=0
while True:
if i==ResetAt:
rst.next=1
elif i==(ResetAt+1):
rst.next=0
else:
pass
if i==StopAt:
raise StopSimulation()
i+=1
yield clk.posedge
return instances()
TB=Down_CounterTBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('Down_CounterTBV');
In [20]:
#Create the Direction States for UpDown Counter
DirStates=enum('Up', 'Down')
print(f"`Up` state repersentation is {bin(DirStates.Up)}")
print(f"`Down` state repersentation is {bin(DirStates.Down)}")
In [21]:
@block
def UpDown_Counter(Dir, count, Trig, clk, rst,
CountVal, StartVal, BitSize):
"""
UpDownCounter, hybrid of a simple Up Counter and
a simple Down Counter using `Dir` to control Up/Down
count Direction
Input:
Dir():
clk(bool): system clock feed
rst(bool): clock reset signal
Ouput:
count (bit vector): current count value; count
Trig(bool)
Parmeter(Python Only):
CountVal(int): Highest Value for counter
StartVal(int): starting value for internal counter
BitSize (int): Bitvalue size is log_2(CountVal)+1
"""
#internal counter value
count_i=Signal(modbv(StartVal)[BitSize:])
@always(clk.posedge, rst.negedge)
def logic():
if rst:
count_i.next=StartVal
Trig.next=0
#counter contanment
elif count_i//CountVal==1 and rst==0:
count_i.next=StartVal
#up behavior
elif Dir==DirStates.Up:
count_i.next=count_i+1
#simple Triger at ends
if count_i%CountVal==0:
Trig.next=1
#down behavior
elif Dir==DirStates.Down:
count_i.next=count_i-1
#simple Triger at ends
if count_i%CountVal==0:
Trig.next=1
@always_comb
def OuputBuffer():
count.next=count_i
return instances()
In [22]:
Peeker.clear()
clk=Signal(bool(0)); Peeker(clk, 'clk')
rst=Signal(bool(0)); Peeker(rst, 'rst')
Trig=Signal(bool(0)); Peeker(Trig, 'Trig')
count=Signal(modbv(0)[BitSize:]); Peeker(count, 'count')
Dir=Signal(DirStates.Up); Peeker(Dir, 'Dir')
DUT=UpDown_Counter(Dir, count, Trig, clk, rst,
CountVal, StartVal=CountVal//2, BitSize=BitSize)
def UpDown_CounterTB():
"""
myHDL only Testbench for `UpDown_Counter` module
"""
@always(delay(1))
def ClkGen():
clk.next=not clk
@instance
def stimules():
i=0
while True:
if i==int(CountVal*1.5):
Dir.next=DirStates.Down
elif i==int(CountVal*2.5):
rst.next=1
elif i==int(CountVal*2.5)+1:
rst.next=0
if i==int(CountVal*3.5):
raise StopSimulation()
i+=1
yield clk.posedge
return instances()
sim=Simulation(DUT, UpDown_CounterTB(), *Peeker.instances()).run()
In [23]:
Peeker.to_wavedrom()
In [24]:
UpDown_CounterData=Peeker.to_dataframe()
UpDown_CounterData=UpDown_CounterData[UpDown_CounterData['clk']==1]
UpDown_CounterData.drop('clk', axis=1, inplace=True)
UpDown_CounterData.reset_index(drop=True, inplace=True)
UpDown_CounterData
Out[24]:
In [25]:
DUT.convert()
VerilogTextReader('UpDown_Counter');
In [26]:
StateChangeAt=int(CountVal*1.5)
ResetAt=int(CountVal*2.5)
StopAt=int(CountVal*3.5)
@block
def UpDown_CounterTBV():
"""
myHDL -> Verilog Testbench for `Down_Counter` module
"""
clk=Signal(bool(0))
rst=Signal(bool(0))
Trig=Signal(bool(0))
count=Signal(modbv(0)[BitSize:])
Dir=Signal(DirStates.Up)
DUT=UpDown_Counter(Dir, count, Trig, clk, rst,
CountVal, StartVal=CountVal//2, BitSize=BitSize)
@always_comb
def print_data():
print(clk, rst, Trig, count)
DUT=Down_Counter(count, Trig, clk, rst, CountVal, BitSize)
@instance
def clk_signal():
while True:
clk.next = not clk
yield delay(1)
@instance
def stimules():
i=0
while True:
if i==StateChangeAt:
Dir.next=DirStates.Down
elif i==ResetAt:
rst.next=1
elif i==ResetAt+1:
rst.next=0
else:
pass
if i==StopAt:
raise StopSimulation()
i+=1
yield clk.posedge
return instances()
TB=UpDown_CounterTBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('UpDown_CounterTBV');
On common application in HDL for counters in build clock dividers. And while there are more specialized and advanced means to perform up or down frequency generation from a reference clock (see for example digital Phase Lock Loops). A simple clock divider is very useful HDL code to drive other HDL IPs that should/need a slower event rate than the Megahertz+ speeds of today's FPGAs
\begin{figure} \centerline{\includegraphics[width=10cm]{}} \caption{\label{fig:RP} ClockDivider Functianl Digram (ToDo) } \end{figure}
In [27]:
@block
def ClockDivider(Divisor, clkOut, count, clk,rst):
"""
Simple Clock Divider based on the Digilint Clock Divider
https://learn.digilentinc.com/Documents/262
Input:
Divisor(32 bit): the clock frequncy divide by value
clk(bool): The input clock
rst(bool): clockDivider Reset
Ouput:
clkOut(bool): the divided clock ouput
count(32bit): the value of the internal counter
"""
count_i=Signal(modbv(0)[32:])
@always(clk.posedge, rst.posedge)
def counter():
if rst:
count_i.next=0
elif count_i==(Divisor-1):
count_i.next=0
else:
count_i.next=count_i+1
clkOut_i=Signal(bool(0))
@always(clk.posedge, rst.posedge)
def clockTick():
if rst:
clkOut_i.next=0
elif count_i==(Divisor-1):
clkOut_i.next=not clkOut_i
else:
clkOut_i.next=clkOut_i
@always_comb
def OuputBuffer():
count.next=count_i
clkOut.next=clkOut_i
return instances()
In [28]:
Peeker.clear()
clk=Signal(bool(0)); Peeker(clk, 'clk')
Divisor=Signal(intbv(0)[32:]); Peeker(Divisor, 'Divisor')
count=Signal(intbv(0)[32:]); Peeker(count, 'count')
clkOut=Signal(bool(0)); Peeker(clkOut, 'clkOut')
rst=Signal(bool(0)); Peeker(rst, 'rst')
DUT=ClockDivider(Divisor, clkOut, count, clk,rst)
def ClockDividerTB():
"""
myHDL only Testbench for `ClockDivider` module
"""
@always(delay(1))
def ClkGen():
clk.next=not clk
@instance
def stimules():
for i in range(2,6+1):
Divisor.next=i
rst.next=0
#run clock time
for _ in range(4*2**(i-1)):
yield clk.posedge
for j in range(1):
if j==0:
rst.next=1
yield clk.posedge
raise StopSimulation()
return instances()
sim=Simulation(DUT, ClockDividerTB(), *Peeker.instances()).run()
In [29]:
Peeker.to_wavedrom()
In [30]:
ClockDividerData=Peeker.to_dataframe()
ClockDividerData
Out[30]:
In [31]:
ClockDividerData_2=ClockDividerData[ClockDividerData['Divisor']==2]
ClockDividerData_2.reset_index(drop=True, inplace=True)
ClockDividerData_2.plot(y=['clk', 'clkOut']);
In [32]:
ClockDividerData_3=ClockDividerData[ClockDividerData['Divisor']==3]
ClockDividerData_3.reset_index(drop=True, inplace=True)
ClockDividerData_3.plot(y=['clk', 'clkOut']);
In [33]:
ClockDividerData_4=ClockDividerData[ClockDividerData['Divisor']==4]
ClockDividerData_4.reset_index(drop=True, inplace=True)
ClockDividerData_4.plot(y=['clk', 'clkOut']);
In [34]:
ClockDividerData_5=ClockDividerData[ClockDividerData['Divisor']==5]
ClockDividerData_5.reset_index(drop=True, inplace=True)
ClockDividerData_5.plot(y=['clk', 'clkOut']);
In [35]:
ClockDividerData_6=ClockDividerData[ClockDividerData['Divisor']==6]
ClockDividerData_6.reset_index(drop=True, inplace=True)
ClockDividerData_6.plot(y=['clk', 'clkOut']);
In [36]:
DUT.convert()
VerilogTextReader('ClockDivider');
In [37]:
@block
def ClockDividerTBV():
"""
myHDL -> Verilog Testbench for `ClockDivider` module
"""
clk=Signal(bool(0));
Divisor=Signal(intbv(0)[32:])
count=Signal(intbv(0)[32:])
clkOut=Signal(bool(0))
rst=Signal(bool(0))
@always_comb
def print_data():
print(clk, Divisor, count, clkOut, rst)
DUT=ClockDivider(Divisor, clkOut, count, clk,rst)
@instance
def clk_signal():
while True:
clk.next = not clk
yield delay(1)
@instance
def stimules():
for i in range(2,6+1):
Divisor.next=i
rst.next=0
#run clock time
for _ in range(4*2**(i-1)):
yield clk.posedge
for j in range(1):
if j==0:
rst.next=1
else:
pass
yield clk.posedge
raise StopSimulation()
return instances()
TB=ClockDividerTBV()
TB.convert(hdl="Verilog", initial_values=True)
VerilogTextReader('ClockDividerTBV');