Source code for rtl

"""
===========
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()
[docs] def connect_inputs(self): '''Assigns all IOS.Members[name].Data to self.iofile_bundle.Members[ioname].Data ''' for ioname,io in self.IOS.Members.items(): if ioname in self.iofile_bundle.Members: val=self.iofile_bundle.Members[ioname] # File type inputs are driven by the file.Data, not the input field if not isinstance(self.IOS.Members[val.name].Data,rtl_iofile) \ and val.dir == 'in': # Data must be properly shaped self.iofile_bundle.Members[ioname].Data=self.IOS.Members[ioname].Data
[docs] def extract_vlogfiles(self): """Return extracted verilog files from ``self.rtlfiles`` """ vlogfiles = [] for module in self.rtlfiles: _, file_ext = os.path.splitext(module) if file_ext in [".sv", ".v"]: vlogfiles += [module] return vlogfiles
[docs] def extract_vhdlfiles(self): """Return extracted vhdl files from ``self.rtlfiles`` """ vhdlfiles = [] for module in self.rtlfiles: _, file_ext = os.path.splitext(module) if file_ext in [".vhd", ".vhdl"]: vhdlfiles += [module] return vhdlfiles
# Define if the signals are signed or not # Can these be deducted?
[docs] def format_ios(self): '''Verilog module does not contain information if the bus is signed or not. Prior to writing output file, the type of the connecting wire defines how the bus values are interpreted. ''' for ioname,val in self.iofile_bundle.Members.items(): if val.ionames: for assocname in val.ionames: if val.dir == 'out': if ((val.datatype == 'sint' ) or (val.datatype == 'scomplex')): self.tb.connectors.Members[assocname].type='signed' self.tb.connectors.Members[assocname].ioformat=val.ioformat else: self.print_log(type='F', msg='List of associated ionames not defined for IO %s\n. Provide it as list of strings' %(ioname))
[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