"""
===========
RTL package
===========
Simulation interface package for The System Development Kit
Provides utilities to import verilog modules and VHDL entities to
python environment and sutomatically generate testbenches for the
most common simulation cases.
Initially written by Marko Kosunen, 2017
"""
import os
import sys
import subprocess
import shlex
from abc import *
from thesdk import *
import numpy as np
import pandas as pd
from functools import reduce
import shutil
import re
from rtl.connector import indent, rtl_connector_bundle, verilog_connector_bundle
from rtl.testbench import testbench as vtb
from rtl.rtl_iofile import rtl_iofile as rtl_iofile
from rtl.sv.sv import sv as sv
from rtl.vhdl.vhdl import vhdl as vhdl
from rtl.icarus.icarus import icarus as icarus
from rtl.questasim.questasim import questasim as questasim
from rtl.ghdl.ghdl import ghdl as ghdl
from rtl.verilator.verilator import verilator as verilator
[docs]
class rtl(questasim,icarus,verilator,ghdl,vhdl,sv,thesdk,metaclass=abc.ABCMeta):
"""Adding this class as a superclass enforces the definitions
for rtl simulations in the subclasses.
"""
def __init__(self):
pass
@property
def lang(self):
""" str : Language of the testbench to support multilanguage simulators.
Default vhdl | sv (default)
"""
if not hasattr(self,'_lang'):
self._lang = 'sv'
if self.model == 'icarus' and self._lang != 'sv':
self.print_log(t='I', msg='Only verilog supported by Icarus')
self._lang = 'sv'
elif self.model == 'ghdl' and self._lang != 'vhdl':
self.print_log(t='I', msg='Only VHDL supported by GHDL')
self._lang = 'vhdl'
elif self.model == 'verilator' and self._lang != 'sv':
self.print_log(t='I', msg='Only verilog supported by Verilator')
self._lang = 'sv'
return self._lang
@lang.setter
def lang(self,value):
self._lang = value
@property
def preserve_rtlfiles(self):
"""True | False (default)
If True, do not delete testbench and copy of DUT after simulations. Useful for
debugging testbench generation.
"""
if not hasattr(self,'_preserve_rtlfiles'):
self._preserve_rtlfiles=False
return self._preserve_rtlfiles
@preserve_rtlfiles.setter
def preserve_rtlfiles(self,value):
self._preserve_rtlfiles=value
@property
def interactive_rtl(self):
""" True | False (default)
Launch simulator in local machine with GUI."""
if not hasattr(self,'_interactive_rtl'):
self._interactive_rtl = False
return self._interactive_rtl
@interactive_rtl.setter
def interactive_rtl(self,value):
self._interactive_rtl=value
@property
def lsf_submission(self):
"""
Defines submission prefix from thesdk.GLOBALS['LSFSUBMISSION'].
[ ToDo ] Transfer definition to thesdk entity.
Usually something like 'bsub -K'
"""
if not hasattr(self, '_lsf_submission'):
if self.has_lsf:
self._lsf_submission=thesdk.GLOBALS['LSFSUBMISSION']+' '
else:
self._lsf_submission=''
return self._lsf_submission
@property
def rtl_timescale(self):
"""
Defines the rtl timescale. This is the time unit shown by the simulator
and used in testbench delays. Default '1ps'
"""
if not hasattr(self, '_rtl_timescale'):
self._rtl_timescale = '1ps'
return self._rtl_timescale
@rtl_timescale.setter
def rtl_timescale(self, val):
self._rtl_timescale = val
@property
def rtl_timescale_num(self):
"""
Returns rtl_timescale as a floating point number. Determined from rtl_timescale.
No setter.
"""
val = self.rtl_timescale
match = re.match(r'(\d+)(\D+)', val.replace(" ",""))
if match:
number = int(match.group(1))
text = match.group(2)
else:
self.print_log(type='F', msg=f"Invalid rtl_timescale string: {val}. Allowed e.g. 1ps")
if text == "ms": number *= 1e-3
elif text == "us": number *= 1e-6
elif text == "ns": number *= 1e-9
elif text == "ps": number *= 1e-12
elif text == "fs": number *= 1e-15
else:
self.print_log(type='F', msg=f"Invalid rtl_timescale unit: {val}. Allowed ms,us,ns,ps,fs")
return number
@property
def rtl_timeprecision_num(self):
"""
Returns rtl_timeprecision as a floating point number. Determined from rtl_timeprecision.
No setter.
"""
val = self.rtl_timescale
match = re.match(r'(\d+)(\D+)', val.replace(" ",""))
if match:
number = int(match.group(1))
text = match.group(2)
else:
self.print_log(type='F', msg=f"Invalid rtl_timeprecision string: {val}. Allowed e.g. 1ps")
if text == "ms": number *= 1e-3
elif text == "us": number *= 1e-6
elif text == "ns": number *= 1e-9
elif text == "ps": number *= 1e-12
elif text == "fs": number *= 1e-15
else:
self.print_log(type='F', msg=f"Invalid rtl_timeprecision unit: {val}. Allowed ms,us,ns,ps,fs")
return number
@property
def rtl_timeunit(self):
"""
Defines rtl time unit. OBSOLETE: use rtl_timescale.
"""
if not hasattr(self, '_rtl_timeunit'):
self.print_log(type='O', msg='Use `rtl_timescale` and `rtl_timeprecision` instead')
self._rtl_timeunit = '1 ps'
return self._rtl_timeunit
@rtl_timeunit.setter
def rtl_timeunit(self, val):
self.print_log(type='O', msg='Use `rtl_timescale` and `rtl_timeprecision` instead')
self._rtl_timeunit = val
@property
def rtl_timeprecision(self):
"""
Defines rtl time precision. This is the smallest time step representable in simulation.
This should be less than equal to ``rtl_timeunit``. Default '1 ps'.
"""
if not hasattr(self, '_rtl_timeprecision'):
self._rtl_timeprecision = self.rtl_timescale
return self._rtl_timeprecision
@rtl_timeprecision.setter
def rtl_timeprecision(self, val):
self._rtl_timeprecision = val
@property
def sim_opt_dict(self):
'''Preset abstraction dictionary for running optimization on the simulation.
The actual dictionary is implemented in the simulator class. Returns
Dict with empty strings for simulators that do not use this property'
Valid values:
- 'no-opt' - no optimizations, full visibility to signals
- 'opt-visible' - optimized with full visibility.
- 'full-opt' - fully optimized, might lose visibility to a lot of signals. Simulation may not work.
- 'top-visible' - optimized while keeping top level (testbench) signals.
- 'top-dut-visible' - optimized while keeping top level (testbench) and DUT signals on the first
hierarchy level
'''
if not hasattr(self, '_sim_opt_dict'):
if self.model == 'sv':
self._sim_opt_dict = self.questasim_sim_opt_dict
elif self.model == 'vhdl':
self._sim_opt_dict = self.questasim_sim_opt_dict
else:
self._sim_opt_dict = {
'no-opt' : '',
'opt-visible' : '',
'full-opt' : '',
'top-visible' : '',
'top-dut-visible' : ''
}
return self._sim_opt_dict
@property
def sim_optimization(self):
'''Simulation optimization option. The value in this property
is used to fetch the simulator-specific simulation parameters from
sim_opt_dict and use it for self.velogsimargs.
Default: 'opt-visible' for non-interactive simulations, 'no-opt'
for interactive simulations.
Set to 'None' is you wish to set self.vlogsimargs manually.
'''
if not hasattr(self, '_sim_optimization'):
if not self.interactive_rtl:
self._sim_optimization = 'opt-visible'
else:
self._sim_optimization = 'no-opt'
return self._sim_optimization
@sim_optimization.setter
def sim_optimization(self, value):
if value in list(self.sim_opt_dict.keys()) + [None ]:
self._sim_optimization = value
else:
self.print_log(type='F', msg=(
f'Key not found in sim_opt_dict: {value}. '
f'Available keys: {self.sim_opt_dict.keys()}'))
@property
def add_tb_timescale(self):
"""Bool : Defines if timescale directive is added to testbench. Can
be used in cases where submodules have timescale directives, and
you wish to control that from the testbench toplevel. Effective only for
self.lang = 'sv'
Default: False
"""
if not hasattr(self,'_add_tb_timescale'):
self._add_tb_timescale = False
return self._add_tb_timescale
@add_tb_timescale.setter
def add_tb_timescale(self,val):
self._add_tb_timescale = val
@property
def name(self):
''' Name of the entity
Extracted from the _classfile attribute
'''
if not hasattr(self, '_name'):
self._name=os.path.splitext(os.path.basename(self._classfile))[0]
return self._name
@name.setter
def name(self, name):
self._name = name
@property
def rtlmisc(self):
"""List<String>
List of manual commands to be pasted to the testbench. The strings are
pasted to their own lines (no linebreaks needed), and the syntax is
unchanged.
Example: creating a custom clock::
self.rtlmisc = []
self.rtlmisc.append('reg clock2;')
self.rtlmisc.append('initial clock2=\'b0;')
self.rtlmisc.append('always #(c_Ts2/2.0) clock2 = !clock2;')
"""
if not hasattr(self, '_rtlmisc'):
self._rtlmisc = []
return self._rtlmisc
@rtlmisc.setter
def rtlmisc(self,value):
self._rtlmisc = value
@property
def rtlsimpath(self):
'''HDL source directory for rtl simulations
self.simpath + '/rtl'
Returns
-------
self.simpath + '/rtl'
'''
if not hasattr(self, '_rtlsimpath'):
self._rtlsimpath = os.path.join(self.simpath, 'rtl')
try:
if not os.path.exists(self._rtlsimpath):
self.print_log(type='I', msg='Creating %s' % self._rtlsimpath)
os.makedirs(self._rtlsimpath)
except:
self.print_log(type='E', msg='Failed to create %s' % self.rtlsimpath)
return self._rtlsimpath
[docs]
def delete_rtlsimpath(self):
''' Deletes all files in rtlsimpath
'''
if os.path.exists(self.rtlsimpath):
try:
for target in os.listdir(self.rtlsimpath):
targetpath = '%s/%s' % (self.rtlsimpath,target)
if self.preserve_rtlfiles:
self.print_log(type='I',msg='Preserving %s' % targetpath)
else:
if os.path.isdir(targetpath):
shutil.rmtree(targetpath)
else:
os.remove(targetpath)
self.print_log(type='I',msg='Removing %s' % targetpath)
except:
self.print_log(type='W',msg='Could not remove %s' % targetpath)
if not self.preserve_rtlfiles:
try:
shutil.rmtree(self.rtlsimpath)
self.print_log(type='I',msg='Removing %s' % self.rtlsimpath)
except:
self.print_log(type='W',msg='Could not remove %s' %self.rtlsimpath)
@property
def simdut(self):
''' Source file for Device Under Test in simulations directory
Returns
-------
self.rtlsimpath + self.name + self.vlogext for 'sv' model
self.rtlsimpath + self.name + '.vhd' for 'vhdl' model
'''
if not hasattr(self, '_simdut'):
if self.model == 'icarus':
self._simdut = self.icarus_simdut
elif self.model == 'sv':
self._simdut = self.questasim_simdut
elif self.model == 'vhdl':
self._simdut = self.questasim_simdut
elif self.model == 'ghdl':
self._simdut = self.ghdl_simdut
elif self.model == 'verilator':
self._simdut = self.verilator_simdut
else:
self.print_log(type='F', msg='Unsupported model %s' % self.model)
return self._simdut
@property
def simtb(self):
''' Testbench source file in simulations directory.
This file and it's format is dependent on the language(s)
supported by the simulator.
'''
if not hasattr(self, '_simtb'):
if self.model == 'icarus':
self._simtb = self.icarus_simtb
elif self.model == 'sv' or self.model=='vhdl':
self._simtb = self.questasim_simtb
elif self.model == 'ghdl':
self._simtb = self.ghdl_simtb
elif self.model == 'verilator':
self._simtb = self.verilator_simtb
return self._simtb
@property
def rtlworkpath(self):
'''Work library directory for rtl compilations
self.simpath +'/work'
Returns
-------
self.simpath +'/work'
'''
if not hasattr(self, '_rtlworkpath'):
self._rtlworkpath = self.simpath +'/work'
return self._rtlworkpath
[docs]
def delete_rtlworkpath(self):
''' Deletes compilation directory
Not a deleter decorator, because does not delete
the property.
'''
if os.path.exists(self.rtlworkpath):
try:
shutil.rmtree(self.rtlworkpath)
self.print_log(type='D',msg='Removing %s' % self.rtlworkpath)
except:
self.print_log(type='W',msg='Could not remove %s' %self.rtlworkpath)
@property
def rtlparameters(self):
'''Dictionary of parameters passed to the simulator
during the simulation invocation.
Example:
{'name' : (type,value) }
'''
if not hasattr(self, '_rtlparameters'):
self._rtlparameters = dict()
return self._rtlparameters
@rtlparameters.setter
def rtlparameters(self,value):
self._rtlparameters = value
@rtlparameters.deleter
def rtlparameters(self):
self._rtlparameters = None
@property
def vlogmodulefiles(self):
'''List of verilog modules to be compiled in addition of DUT
'''
if not hasattr(self, '_vlogmodulefiles'):
self._vlogmodulefiles = []
return self._vlogmodulefiles
@vlogmodulefiles.setter
def vlogmodulefiles(self,value):
self.print_log(type='O', msg=(
'Use rtlfiles for both VHDL and Verilog files instead of vlogmodulefiles.')
)
self._vlogmodulefiles = value
@vlogmodulefiles.deleter
def vlogmodulefiles(self):
self._vlogmodulefiles = None
@property
def vloglibfilemodules(self):
'''List of verilog modules to be compiled in addition to DUT
provided in a file given by the 'VLOGLIBFILE' global variable in TheSDK.config
'''
if not hasattr(self, '_vloglibfilemodules'):
try:
libfile = thesdk.GLOBALS['VLOGLIBFILE']
if libfile == '':
raise ValueError
else:
self._vloglibfilemodules = list()
if libfile == '':
self.print_log(type='W',msg='Global TheSDK variable VLOGLIBFILE not set.')
else:
self.print_log(type='I',msg='Using VLOGLIBFILE: %s' % libfile)
try:
with open(libfile, 'r') as fd:
modulefiles = [line.strip() for line in fd.readlines()]
self._vloglibfilemodules.extend(modulefiles)
except Exception as e:
self.print_log(type='F',msg='Could not read verilog module files from VLOGLIBFILE:\n\t%s' % e)
except:
self._vloglibfilemodules = []
return self._vloglibfilemodules
@vloglibfilemodules.setter
def vloglibfilemodules(self,value):
self._vloglibfilemodules = value
@vloglibfilemodules.deleter
def vloglibfilemodules(self):
self._vloglibfilemodules = None
@property
def rtlfiles(self):
'''List of VHDL and Verilog files to be compiled.
File suffices should be '.vhd', '.vhdl', '.sv', or '.v'.
Order determines compile order.
'''
if not hasattr(self, '_rtlfiles'):
self._rtlfiles = []
return self._rtlfiles
@rtlfiles.setter
def rtlfiles(self, value):
self._rtlfiles = value
@property
def vhdlentityfiles(self):
'''List of VHDL entity files to be compiled in addition to DUT
'''
if not hasattr(self, '_vhdlentityfiles'):
self._vhdlentityfiles = []
return self._vhdlentityfiles
@vhdlentityfiles.setter
def vhdlentityfiles(self,value):
self.print_log(type='O', msg=(
'Use rtlfiles for both VHDL and Verilog files instead of vhdlentityfiles.')
)
self._vhdlentityfiles = value
@vhdlentityfiles.deleter
def vhdlentityfiles(self):
self._vhdlentityfiles = None
@property
def vhdllibfileentities(self):
'''List of VHDL entities to be compiled in addition to DUT
provided in a file given by the 'VHDLLIBFILE' global variable in TheSDK.config
'''
if not hasattr(self, '_vhdllibfileentities'):
try:
libfile = thesdk.GLOBALS['VHDLLIBFILE']
if libfile == '':
raise ValueError
else:
self._vhdllibfileentities = list()
if libfile == '':
self.print_log(type='W',msg='Global TheSDK variable VHDLLIBFILE not set.')
else:
self.print_log(type='I',msg='Using VHDLLIBFILE: %s' % libfile)
try:
with open(libfile, 'r') as fd:
modulefiles = [line.strip() for line in fd.readlines()]
self._vhdllibfileentities.extend(modulefiles)
except Exception as e:
self.print_log(type='F',msg='Could not read verilog module files from VHDLLIBFILE:\n\t%s' % e)
except:
self._vhdllibfileentities = []
return self._vhdllibfileentities
@property
def interactive_control_contents(self):
''' Content of the interactive rtl control file (.do -file).
If this property is set, a new dofile gets written to the simulation
path. This takes precedence over the file pointed by
`interactive_controlfile`.
For example, the contents can be defined in the top testbench as::
self.interactive_control_contents="""
add wave -position insertpoint \\
sim/:tb_inverter:A \\
sim/:tb_inverter:clock \\
sim/:tb_inverter:Z
run -all
wave zoom full
"""
'''
if not hasattr(self, '_interactive_control_contents'):
self._interactive_control_contents = ''
return self._interactive_control_contents
@interactive_control_contents.setter
def interactive_control_contents(self,value):
self._interactive_control_contents = value
@property
def simulator_control_contents(self):
''' Content of the simulator rtl control file.
If this property is set, a new controlfile is written to the simulation
path. This takes precedence over the file pointed by
`simulator_controlfile`.
For example, the contents can be defined in the top testbench as::
self.simulator_control_contents="""
# Path format for signals in entities :
/top/sub/clock
"""
'''
if not hasattr(self, '_simulator_control_contents'):
self._simulator_control_contents = ''
return self._simulator_control_contents
@simulator_control_contents.setter
def simulator_control_contents(self,value):
self._simulator_control_contents = value
@property
def simulator_controlfile(self):
''' Path to simulator control file.
Different simulators use different ways to control the simulation. In simulators
with integrated waveform viewer, there is only one control,file e.g 'dofile' and
Questasim . Other option, for example used with GHDL is to control the simulator with
'--read-wave-opt=<file>'. This property provides a method to set that filepath. It is mapped
to an argument of a proper option with the simulator specific class.
The content of the file can be defined in `simulator_control_contents`. If the
content is not set in 'simulator_control_contents` -property, the control file
read from this file path. Default is set in simulator specific property for each simulator.
'''
if self.model == 'icarus':
(controlfiledir, controlfile, generatedcontrolfile ) = self.icarus_controlfilepaths
elif self.model == 'sv':
(controlfiledir, controlfile, generatedcontrolfile ) = self.questasim_controlfilepaths
elif self.model == 'vhdl':
(controlfiledir, controlfile, generatedcontrolfile ) = self.questasim_controlfilepaths
elif self.model == 'ghdl':
(controlfiledir, controlfile, generatedcontrolfile ) = self.ghdl_controlfilepaths
elif self.model == 'verilator':
(controlfiledir, controlfile, generatedcontrolfile ) = self.verilator_controlfilepaths
else:
self.print_log(type='F', msg='Unsupported model %s' % self.model)
if not hasattr(self, '_simulator_controlfile'):
if not os.path.exists(controlfiledir):
self.print_log(type='I',msg='Creating %s' % controlfiledir)
os.makedirs(controlfiledir)
# Simulator control contents always overrdes the file
if self.simulator_control_contents != '':
# Give a warning if default/custom path contains a do-file already
if os.path.isfile(controlfile):
self.print_log(type='W',msg='Simulator control file %s ignored and simulator_control_contents used instead.' % controlfile)
# Write simulator_control_contents to a temporary file
self.print_log(type='I',msg='Writing simulator_control_contents to file %s' % generatedcontrolfile)
with open(generatedcontrolfile,'w') as fileptr:
fileptr.write(self.simulator_control_contents)
self._simulator_controlfile = generatedcontrolfile
# Use default control file location
else:
if os.path.isfile(controlfile):
self._simulator_controlfile = controlfile
else:
self._simulator_controlfile = ''
return self._simulator_controlfile
@simulator_controlfile.setter
def simulator_controlfile(self,value):
self._simulator_controlfile = value
@property
def interactive_controlfile(self):
''' Path to interactive rtl control file used in interactive simulations.
The content of the file can be defined in `interactive_control_contents`. If the
content is not set in `interactive_control` -property, the do-file is
read from this file path. Default path is
`./interactive_control_files/modelsim/dofile.do`.
'''
if self.model == 'icarus':
(dofiledir, dofile, obsoletedofile, generateddofile) = self.icarus_dofilepaths
elif self.model == 'sv':
(dofiledir, dofile, obsoletedofile, generateddofile) = self.questasim_dofilepaths
elif self.model == 'vhdl':
(dofiledir, dofile, obsoletedofile, generateddofile) = self.questasim_dofilepaths
elif self.model == 'ghdl':
(dofiledir, dofile, obsoletedofile, generateddofile) = self.ghdl_dofilepaths
elif self.model == 'verilator':
(dofiledir, dofile, obsoletedofile, generateddofile) = self.verilator_dofilepaths
else:
self.print_log(type='F', msg='Unsupported model %s' % self.model)
if not hasattr(self, '_interactive_controlfile'):
# No contents or path given -> use default path (or obsolete path)
if os.path.exists(obsoletedofile):
self.print_log(type='O',msg='Found obsoleted do-file in %s' % obsoletedofile)
self.print_log(type='O',msg='To fix the obsolete warning:')
self.print_log(type='O',msg='Move the obsoleted file %s to the default path %s' % (obsoletedofile,dofile))
self.print_log(type='O',msg='Or, set a custom do-file path to self.interactive_controlfile.')
self.print_log(type='O',msg='Or, define the do-file contents in self.interactive_control_contents in your testbench.')
self.print_log(type='O',msg='Using the obsoleted file for now.')
self._interactive_controlfile = obsoletedofile
else:
# Use default do-file location if it exists
if os.path.isfile(dofile):
self._interactive_controlfile = dofile
else:
self._interactive_controlfile = ''
if not os.path.exists(dofiledir):
self.print_log(type='I',msg='Creating %s' % dofiledir)
os.makedirs(dofiledir)
# Property interactive_control_contents already given and new temporary
# file not yet created -> create new file and use that
if self.interactive_control_contents != '':
# Give a warning if default/custom path contains a do-file already
if os.path.isfile(dofile):
self.print_log(type='W',msg='Interactive control file %s ignored and interactive_control_contents used instead.' % dofile)
# Write interactive_control_contents to a temporary file
self.print_log(type='I',msg='Writing interactive_control_contents to file %s' % generateddofile)
with open(generateddofile,'w') as dofileptr:
dofileptr.write(self.interactive_control_contents)
self._interactive_controlfile = generateddofile
if not hasattr(self,'_interactive_controlfile'):
self._interactive_controlfile = ''
return self._interactive_controlfile
@interactive_controlfile.setter
def interactive_controlfile(self,value):
self._interactive_controlfile = value
@property
def workdir(self):
'''str : Directory where to run the external simulation command.
If set, we cd to this directory before execution. If not, the
simulator specific default is used.
Default
-------
None
'''
if not hasattr(self, '_workdir'):
self._workdir = None
return self._workdir
@workdir.setter
def workdir(self, dir):
self._workdir = dir
@property
def rtlcmd(self):
'''Command used for simulation invocation
Compiled from various parameters. See source for details.
'''
if not hasattr(self, '_rtlcmd'):
if self.model == 'icarus':
return self.icarus_rtlcmd
elif self.model=='sv':
return self.questasim_rtlcmd
elif self.model=='vhdl':
return self.questasim_rtlcmd
elif self.model=='ghdl':
return self.ghdl_rtlcmd
elif self.model=='verilator':
return self.verilator_rtlcmd
else:
self.print_log(type='F', msg='Model %s not supported' %(self.model))
return self._rtlcmd
# Just to give the freedom to set this if needed
@rtlcmd.setter
def rtlcmd(self,value):
self._rtlcmd=value
@rtlcmd.deleter
def rtlcmd(self):
self._rtlcmd=None
[docs]
def create_connectors(self):
'''Creates connector definitions from
1) From a iofile that is provided in the Data
attribute of an IO.
2) IOS of the verilog DUT
'''
#currently only sv connectors are supported
self.sv_create_connectors()
# Define if the signals are signed or not
# Can these be deducted?
[docs]
def copy_or_relink(self,**kwargs):
''' If the source is a symlink, create the target as a link to original target.
otherwise, copy the file.
Parameters
----------
src : str
Path to source file
dst : str
Path to destination file.
'''
src=kwargs.get('src')
dst=kwargs.get('dst')
if os.path.islink(src):
if not os.path.islink(dst):
os.symlink(os.path.join(os.path.dirname(src), os.readlink(src)), dst)
else:
os.remove(dst)
os.symlink(os.path.join(os.path.dirname(src), os.readlink(src)), dst)
else:
shutil.copyfile(src, dst, follow_symlinks=False)
[docs]
def copy_rtl_sources(self):
''' Copy rtl sources to self.rtlsimpath
'''
self.print_log(type='I', msg='Copying rtl sources to %s' % self.rtlsimpath)
vlog_model = self.model in ['sv', 'icarus', 'verilator']
vhdl_model = self.model in ['ghdl', 'vhdl']
src = self.vlogsrc if vlog_model else self.vhdlsrc
# source generated to self.rtlsimpath
simdut_exists = os.path.isfile(self.simdut)
# source in static location (/sv/entity.sv or /vhdl/entity/vhd)
src_exists = os.path.isfile(src)
# Barename of DUT
dut_bname = os.path.basename(src)
tb_bname = os.path.basename(self.simtb)
# Merge all different modules to one
# This also maintains backward compatibility
self.rtlfiles += self.vloglibfilemodules + self.vlogmodulefiles + \
self.vhdllibfileentities + self.vhdlentityfiles
# Append dut to rtlfiles
if dut_bname not in self.rtlfiles:
self.rtlfiles += [dut_bname]
# Copy files if they exist under sv/ or vhdl/
for modfile in self.rtlfiles:
_, file_ext = os.path.splitext(modfile)
lang = "vlog" if file_ext in [".v", ".sv"] else "vhdl"
tgt_dir = self.vlogsrcpath if lang == "vlog" else self.vhdlsrcpath
srcfile = os.path.join(tgt_dir, modfile)
dstfile = os.path.join(self.rtlsimpath, modfile)
if os.path.isfile(dstfile):
self.print_log(type='I', msg='Using externally generated source: %s' % modfile)
else:
self.print_log(type='I', msg='Copying %s to %s' % (srcfile, dstfile))
self.copy_or_relink(src=srcfile,dst=dstfile)
# Append testbench to rtlfiles
if tb_bname not in self.rtlfiles:
self.rtlfiles += [tb_bname]
# flush cached writes to disk
output = subprocess.check_output("sync %s" % self.rtlsimpath, shell=True)
output = output.decode('utf-8')
if len(output) != 0:
print(output)
[docs]
def execute_rtl_sim(self):
'''Runs the rtl simulation in external simulator
'''
filetimeout=60 #File appearance timeout in seconds
count=0
files_ok=False
while not files_ok:
count +=1
if count >filetimeout:
self.print_log(type='F', msg='Verilog infile writing timeout')
for name, file in self.iofile_bundle.Members.items():
if file.dir=='in':
files_ok=True
files_ok=files_ok and os.path.isfile(file.file)
time.sleep(int(1)) #Wait for one second
#Remove existing output files before execution
for name, file in self.iofile_bundle.Members.items():
if file.dir=='out':
try:
#Still keep the file in the infiles list
os.remove(file.name)
except:
pass
if self.interactive_rtl:
self.print_log(type='I', msg="""
Running RTL simulation in interactive mode.
Add the probes in the simulation as you wish.
To finish the simulation, run the simulation to end and exit.""")
try:
if self.workdir:
self.print_log(type='I', msg=f"Executing in directory {self.workdir}")
execpath=self.workdir
else:
self.print_log(type='I', msg=f"Executing in directory {self.rtlsimpath}")
execpath=self.rtlsimpath
self.print_log(type='I', msg="Running external command %s\n" %(self.rtlcmd) )
rtlcmd = f"cd {execpath} && {self._rtlcmd}"
output = subprocess.check_output(rtlcmd, shell=True)
self.print_log(type='I', msg='Simulator output:\n'+output.decode('utf-8'))
except subprocess.CalledProcessError as e:
output = e.output
self.print_log(type='F', msg='Simulator output:\n'+output.decode('utf-8'))
count=0
files_ok=False
while not files_ok:
count +=1
if count >filetimeout:
self.print_log(type='F', msg="Verilog outfile timeout")
time.sleep(int(1))
for name, file in self.iofile_bundle.Members.items():
if file.dir=='out':
files_ok=True
files_ok=files_ok and os.path.isfile(file.file)
@property
def assignment_matchlist(self):
'''List, which signals are connected in assignment stage during testbench generation
Should be a list of strings, where a string is the signal name
'''
if not hasattr(self, '_assignment_matchlist'):
self._assignment_matchlist = []
return self._assignment_matchlist
@assignment_matchlist.setter
def assignment_matchlist(self, matchlist):
self._assignment_matchlist = matchlist
@property
def custom_connectors(self):
'''Custom connectors to be added to the testbench
Should be a e.g. a rtl_connector_bundle
'''
if not hasattr(self, '_custom_connectors'):
self._custom_connectors = rtl_connector_bundle()
return self._custom_connectors
@custom_connectors.setter
def custom_connectors(self, bundle):
self._custom_connectors = bundle
[docs]
def add_connectors(self):
'''Adds custom connectors to the testbench.
Also connects rtl matchlist to testbench matchlist.
Custom connectors should be saved in self.custom_connectors
Matchlist for these connectors should be saved in self.assignment_matchlist
'''
self.tb.connectors.update(bundle=self.custom_connectors.Members)
self.tb.assignment_matchlist += self.assignment_matchlist
[docs]
def run_rtl(self):
'''1) Copies rtl sources to a temporary simulation directory
2) Creates a testbench
3) Defines the contens of the testbench
4) Creates connectors
5) Connects inputs
6) Defines IO conditions
7) Defines IO formats in testbench
8) Generates testbench contents
9) Exports the testbench to file
10) Writes input files
11) Executes the simulation
12) Read outputfiles
13) Connects the outputs
14) Cleans up the intermediate files
You should overload this method while creating the simulation
and debugging the testbench.
'''
if self.load_state != '':
# Loading a previously stored state
self._read_state()
else:
self.copy_rtl_sources()
self.tb=vtb(parent=self,lang=self.lang)
self.tb.define_testbench()
self.add_connectors()
self.create_connectors()
self.connect_inputs()
if hasattr(self,'define_io_conditions'):
self.define_io_conditions() # Local, this is dependent on how you
# control the simulation
# i.e. when you want to read an write your IO's
self.format_ios()
self.tb.generate_contents()
self.tb.export(force=True)
self.write_infile()
self.execute_rtl_sim()
self.read_outfile()
self.connect_outputs()
# Save entity state
if self.save_state:
self._write_state()
# Clean simulation results
self.delete_iofile_bundle()
self.delete_rtlworkpath()
self.delete_rtlsimpath()
#This writes all infile
[docs]
def write_infile(self):
''' Writes the input files
'''
for name, val in self.iofile_bundle.Members.items():
if val.dir=='in':
self.iofile_bundle.Members[name].write()
#This reads all outfiles
[docs]
def read_outfile(self):
'''Reads the oputput files
'''
for name, val in self.iofile_bundle.Members.items():
if val.dir=='out':
self.iofile_bundle.Members[name].read()
[docs]
def connect_outputs(self):
'''Connects the ouput data from files to corresponding output IOs
'''
for name, val in self.iofile_bundle.Members.items():
if val.dir=='out':
self.IOS.Members[name].Data=self.iofile_bundle.Members[name].Data