Initialisation behaviour can be problematic

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()

Stefan,

I think I don’t agree with you about your statement.
Try:

dummy.convert(hdl='Verilog', initial_values=True)  # convert outputs unit_test.v and tb_unit_test.v

This will add initialization in the generated V* code.
Both simulator and synthesizer will accept that.
The generated Verilog code can be optimized as Sigasi recommends replacing some old-type constructs with newer ones.

Also, if MyHDL simulation detects an overflow I would check my code why.

Best regards,
Josy

Stefan,

I compiled a myhdl.vpi back in 2020. I checked that it works with your code.
Unfortunately I didn’t document how I compiled it. But IMO we can add the compiled .vpi to the repository: @jck what do you think?

Regards,
Josy

Hi josy,

I also compiled a myhdl.vpi on xubuntu just by running the command make in the same directory as the c source.

Unfortunately on windows 10 it’s not so easy. There is no make command available and gmake does’nt do it for some reason. I installed cygwin and tried with no success but did not insist. I think people have done it with the cygwin shell. So if you have a myhdl.vpi for windows 10 I’m interested.

Apart from that i fully agree with your comments on my post…

(I’m about to rewrite the simulation the new syntax way and that is not as difficult as i thought)

Kind regards

Stefan

Thanks josy for the tip about initialize_values=True !

This makes my code work the way i expected it. Both in myhdl block simulation as cosim.
(I would think this setting should be default - but there may be reasons for not?)

Then the multiplication:
it’s tempting to write only

		moda0.next=M0*ma0          

But this is implicitly assuming that the final result is only the upper n bits of the 2n result of the multiplication. And myhdl complains…
So indeed it is better to program explicitely

#helper
m=2*n
XH0     = Signal(intbv(0,min=-2**m,max=2**m))

XH0.next=M0*ma0     # *4) need a intermediate Signal to store the full length
modb0.next=XH0[:n]       # then slice off the lower n

And yes - the new way of writing statements is the way to go. So i rewrote the testbench and did some fresh up of the unit under test and the report to provide here.

tb_unittest.py:

'''
Stefan Karrer 18.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 #data length parameter

tend=2e-3
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

# i/o signals
CLKIN    = Signal(bool(0))
CLK8M    = Signal(bool(0))
RESET_N  = ResetSignal(1,0,True)       # *2)  initialize to 1
RELAY    = Signal(bool(0))

#internal signals
fastcounter       = Signal(intbv(0, min=0, max=2**25))   #this counter runs at CLKIN, 12=8kHz,
clk8k  = Signal(bool(0))
RELAY_trig    = Signal(bool(0))
pwmcounter    = Signal(modbv(0, min=0,max=2**11))   #this counter is fix 11 bits for 16MHz  and 128us loop

# probes needed for debug
y10=Signal(intbv(0,min=-2**n,max=2**n))
y20=Signal(intbv(0,min=-2**n,max=2**n))
y30=Signal(intbv(0,min=-2**n,max=2**n))

print('translating unit_test to Verilog. Creates a cosim testbench interface')
unit_test(CLKIN,clk8k,RESET_N,y10,y20,y30,M,Wn,dt,n).convert(hdl='Verilog', initial_values=True) # *2) set initial_values True !!!

@block
def tb_unittest():

	@always(delay(31))
	def osc():
		CLKIN.next = not CLKIN

	@always_comb   #CLKIN is 16MHz
	def clockwork():
		CLK8M.next=fastcounter[0]
		clk8k.next=fastcounter[10]
		RELAY_trig.next =fastcounter[24]

	@always_seq(CLKIN.posedge, reset=RESET_N)
	def counters():
		pwmcounter.next=pwmcounter+1
		fastcounter.next=fastcounter+1

	@always(CLKIN.negedge)
	def out1():
		#print('ns', now(),' ', clk8k,moda0,modb0,'gate:',gate)
		f1.write(str(now())+' '+str(clk8k)+'\n')

	@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')

	@block
	def unit_testX():
		# A Cosimulation object, used to simulate Verilog modules
		os.system("iverilog -o unit_test.o unit_test.v tb_unit_test.v")
		if cosim:
			return Cosimulation("vvp -m ./myhdl.vpi unit_test.o", CLKIN=CLKIN, clk8k=clk8k, RESET_N=RESET_N, y10=y10, y20=y20, y30=y30)
		else:
			return unit_test(CLKIN,clk8k,RESET_N,y10,y20,y30,M,Wn,dt,n)

	return instances(), unit_testX()



#choose to simulate pure python or cosim
cosim=True       #get same results with cosim. But execution is slower
if cosim:
	print("running  cosimulation")
else:
	print("running  myhdl block")

dut=tb_unittest()
dut.run_sim(tend*1e9)

f1.close()
f2.close()

report(n) #plot results

unit_test.py:

#unit_test.py 16.10.2022 sk testing multiply and add
from myhdl import *
from math import pi

@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))


	#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) if initial_values is not True !!!
	def loop():
		XH0.next=M0*ma0          # *4) need a intermediate Signal to store the full result of 2n length
		modb0.next=XH0[:n]       # then slice off the lower n
		#if Wt0>2**(n-1):        # *5a) let an overflow happen. myhdl detects this. not so cosim.
		if Wt0>(2**(n-1)-Wt0):   # *5b) no overflow here
			Wt0.next=0
		else:
			Wt0.next=Wt0+Wndt0

	return instances()

report.py:

'''
sk 16.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

	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()


	plot(t,y1,'b',t,y2,'r',t,y3,'g')
	title('y1 b,y2 r,y3 g')
	grid()
	show()

Hi Stefan,

I used/installed Msys2 to compile the .vpi; Google tells me I visited their website twice :slight_smile:
As mentioned above, I’d like to add it to the repo; but first I would like to test it more. I will send you this .vpi by DM

Best regards,
Josy

I think the myhdl.vpi for windows 10 issue should be discussed in a separate thread.
I’ll open a new one.

I just want to say that above files are meant to be a good framework for anyone who wants to develop some myhdl unit without prior knowledge.

  • The unit is embedded in a testbench that writes out to display and files.
  • The files are then read by a report.py and displayed graphically with matplotlib which gives all new possibilities like filtering output data i.e. when pwm signals are analyzed.
    (not used in the framework).
  • In addition the .vcd can be written by switch to further analyze time series with gtkwave.
  • The unit itself is synthesizable, data is initialized (thank you josy).
  • both myhdl block simulation as well as cosimulation can be used on a linux machine.

I just forgot to give the dump.insert file which contains the dump command to be added the (translated) testbench verilog file:

initial begin
    $dumpfile("dump_result.vcd");
    $dumpvars();
end

The new thread i call “how to use cosimulation on a windows machine?”