After my conclusion ‘best practice’ i just came upon another behavior which differs between myhdl and cosim when producing synthesizable code:
The initialization of variables
(moreover ‘adding to a variable doesn’t work’ in cosim when not initialized)
I describe this here to underline the necessity to simulate both in myhdl and cosim to understand what’s happening when your output in hardware is pure zeroes…unexpectedly.
My conclusion is: Don’t rely on the intbv initial value to init your variable.
Do initialize in the simulation with a ResetSignal. And use this signal in hw then.
But try the code by running tb_unittest.py. Start with line *1) set so that things won’t work. Follow the lines marked *1) to *5) to reproduce an watch the results until things work. report.py also plots the results. More elaboration can be done with the .vcd output.
I hope this helps newbie developers who don’t get their hw to work the same as the sim. replace unit_test.py with your own unit and adapt …
Hints:
-For cosimulation you will need a myhdl.vpi file which depends on your system. I found it easy to compile from c source for ubuntu. But i did not succeed in windows… but following the instructions in the myhdl handbook this should be feasible.
- For the report you will need the matplotlib library installed in python3
If you dislike comment out report in tb_unittest.py (You will get results anyway)
otherwise use pip install matplotlib in a command window.
Here is my showcase consisting on three files
tb_unittest.py:
'''
sk 16.11.2022
tb_unittest: cosimulation myhdl and verilog
instantiates a unit and a cosimulation of the unit and simulates this toplevel testbench
Also needed: myhdl.vpi defining communication between cosim objects, together with tb_xxx.v
define result file (insert the dump command in tb_xxx.v will write a dump_result.vcd)
finally does some python plotting of the results in out1.txt, out2.txt with the report_xx.py
'''
import os
from myhdl import Cosimulation, Simulation, block, Signal, delay, always, always_comb, always_seq, instance, instances, intbv,modbv, ResetSignal, now
from unit_test import unit_test
from report import report
from math import pi
n=12
nspi=16
tend=12e-4
dt=128e-6
Wn=2*pi*200
f1=open('out1.txt',"w")
f2=open('out2.txt',"w")
#Amplitude and freq when using internal sin/cos generator for standalone use
M=2.0/3.0 #amplitude of modulation: 2/3 is natural full sine, Max numerical is 1.0
CLKIN = Signal(bool(0))
RESET_N = ResetSignal(0,0,True)
FAULT_N = Signal(bool(0)) # FAULT_N wired or interrupt input
CLK8M = Signal(bool(0)) # clock output to DSP
RELAY_trig = Signal(intbv(0)[1:])
fastcounter = Signal(intbv(0, min=0, max=2**25)) #this counter runs at CLKIN, tap 12=ca.8kHz,
pwmcounter = Signal(modbv(0, min=0,max=2**11)) #this counter is fix 11 bits for 16MHz and 128us loop
strobe_counter_2M = Signal(intbv(0, min=0, max=2**3))
strobe_counter_16k= Signal(intbv(0, min=0, max=2**10))
strobe_counter_8k = Signal(intbv(0, min=0, max=2**11))
strobe2M = Signal(bool(0))
strobe16k = Signal(bool(0))
strobe8k = Signal(bool(0))
clk8k = Signal(bool(0))
# needed for debug
y10=Signal(intbv(0,min=-2**(n),max=2**(n))) # (probes)
y20=Signal(intbv(0,min=-2**(n),max=2**(n)))
y30=Signal(intbv(0,min=-2**(n),max=2**(n)))
def init():
@instance
def initialize():
RESET_N.next = 0
FAULT_N.next = 1
yield delay(1000)
RESET_N.next = 1 # *2) RESET_N.posedge necessary to make cosim work
yield delay(100)
return initialize
def osc():
@always(delay(31))
def driver():
CLKIN.next = not CLKIN
return driver
def clocks():
@always_comb #CLKIN is 16MHz
def clockwork():
CLK8M.next=fastcounter[0]
clk8k.next=fastcounter[10]
RELAY_trig.next =fastcounter[24]
return clockwork
def counters():
@always_seq(CLKIN.posedge, reset=RESET_N)
def mycounters():
pwmcounter.next=pwmcounter+1
fastcounter.next=fastcounter+1
return mycounters
def fastreport():
@always(CLKIN.negedge)
def out1():
#print('ns', now(),' ', clk8k,moda0,modb0,'gate:',gate)
f1.write(str(now())+' '+str(RESET_N)+'\n')
return out1
def slowreport():
@always(clk8k.negedge) #new values calculated at posedge
def out2():
print('ns', now(),' ', y10, y20, y30)
f2.write(str(y10)+' '+str(y20)+' '+str(y30)+'\n')
return out2
def unit_testX(CLKIN,clk8k,RESET_N,y10,y20,y30,M,Wn,dt,n):
# A Cosimulation object, used to simulate Verilog modules
os.system("iverilog -o unit_test.o unit_test.v tb_unit_test.v")
return Cosimulation("vvp -m ./myhdl.vpi unit_test.o", CLKIN=CLKIN, clk8k=clk8k, RESET_N=RESET_N, y10=y10, y20=y20, y30=y30)
init1=init()
osc1=osc()
clocks1=clocks()
counters1=counters()
fastreport1=fastreport()
slowreport1=slowreport()
dummy = unit_test(CLKIN,clk8k,RESET_N,y10,y20,y30,M,Wn,dt,n) #debug
dummy.convert(hdl='Verilog') #convert outputs unit_test.v and tb_unit_test.v
vcd=False
if vcd:
ftb=open('tb_unit_test.v','r+')
fd=open('dump.v','r')
contentStr=ftb.read()
additionalStr=fd.read()
newStr=contentStr.replace('endmodule',additionalStr)
ftb=open('tb_unit_test.v','w')
ftb.write(newStr+'endmodule')
ftb.close()
fd.close()
#choose to simulate pure python or cosim *4) Init problem occurs only in cosimulation mode
# *3) and *5) overflow indication shows up only in pure python
#test1 = unit_test(CLKIN,clk8k,RESET_N,y10,y20,y30,M,Wn,dt,n) #debug pure python
test1 = unit_testX(CLKIN,clk8k,RESET_N,y10,y20,y30,M,Wn,dt,n) #debug cosimulation style
print("Simulating",test1)
sim = Simulation(init1, osc1, clocks1, counters1, test1, fastreport1, slowreport1)
sim.run(tend*1e9)
print("report written to out1.txt and out2.txt")
f1.close()
f2.close()
report(n)
unittest.py:
#unit_test.py 16.10.2022 sk testing multiply and add
from myhdl import *
from math import pi,sqrt
#from unit_cos import unit_cos
#from unit_pwm import unit_pwm
@block
def unit_test(CLKIN,clk8k,RESET_N,y10,y20,y30,M,Wn,dt,n):
M0 = Signal(intbv(int(round(M*2**n-1)),min=0,max=2**n)) #modulation signal amplitude
Wndt0 = Signal(intbv(int(round(Wn*dt*2**n/(pi/2))),min=0,max=2**n)) #rad/sec*time ->unit angle, standalone use
Wt0 = Signal(intbv(0,min=0,max=2**(n-1))) #angle (integrated angular speed)
ma0 = Signal(intbv(2**(n-1)-1,min=-2**(n-1),max=2**(n-1))) #modulation signals individally initialized
mb0 = Signal(intbv(0,min=-2**(n-1),max=2**(n-1)))
moda0 = Signal(intbv(0,min=-2**(n-1),max=2**(n-1)))
modb0 = Signal(intbv(0,min=-2**(n-1),max=2**(n-1)))
#helper
m=2*n
XH0 = Signal(intbv(0,min=-2**m,max=2**m))
@always(RESET_N.posedge)
def initialize():
#Wt0.next=100 # 0) No Way. Signal would have multiple drivers
pass
#probes to be sent to testbench
@always(CLKIN.posedge)
def probe():
y10.next=moda0
y20.next=modb0
y30.next=Wt0
@always(clk8k.posedge) # *1) Wt0 fails to increment (always 0)
#@always_seq(clk8k.posedge, RESET_N) # *2) this will work and increment Wt0 - but RESET_N must be driven in testbench
def loop():
moda0.next=M0*ma0 # *3) this works even with missing reset *1) but only in cosim. myhdl detects overflow
XH0.next=M0*ma0 # *4) this works both debug modes. But the initial values differ when compared with 3)
modb0.next=XH0[:n]
if Wt0>2**(n-1): # *5a) let an overflow happen and try both sim modes
#if Wt0>(2**(n-1)-Wt0): # *5b) no overflow -> same results both sim modes
Wt0.next=0
else:
Wt0.next=Wt0+Wndt0
return instances()
report.py:
'''
sk 09.11.2022
do the report plotting in this separate file'''
import os
from numpy import pi,exp,sqrt,array,real,imag
from matplotlib.pyplot import *
def report(n):
print('plotreport')
t0=0.0 #normal time vector (control system dt)
t10=0.0 #fast time vector (clock system clkdt)
dt=128e-6
clkdt=1/16e6 #clock sampling time
a=exp(1j*(2*pi/3))
f1=open('out1.txt',"r")
f2=open('out2.txt',"r")
# Inits ***************************
t1=[]
t=[]
y1=[]
y2=[]
y3=[]
# reading results file ***************************
print('reading results from out1.txt and out2.txt')
for line1 in f1:
line1 = line1.split()
t10=t10+clkdt
t1.append(t10)
f1.close()
for line in f2:
line2 = line.split()
y10=int(line2[0],16) #python interpretes unsigned
#if y10>=2**(n-1):
# y10=y10-2**n
y20=int(line2[1],16)
#if y20>=2**(n-1):
# y20=y20-2**n
y30=int(line2[2],16)
#if y30>=2**(n-1):
# y30=y30-2**n
print('y: ', y10, ' ', y20, ' ',y30)
y1.append(y10)
y2.append(y20)
y3.append(y30)
t.append(t0)
t0=t0+dt
f2.close()
print('finished reading results')
t1=array(t1)
t=array(t)
y1=array(y1)
y2=array(y2)
y3=array(y3)
print('plotting ...')
plot(t,y1,'b',t,y2,'r',t,y3,'g')
title('y1 b,y2 r,y3 g')
grid()
show()