def test_list_of_string_option(self): option = ListOfStringOption("optname") self._test_ok(option, ["hello", "foo"]) self._test_ok(option, [u"hello"]) self._test_not_ok( option, [True], "Option 'optname' must be a list of strings. " "Got [True]") self._test_not_ok( option, [["foo"]], "Option 'optname' must be a list of strings. " "Got [['foo']]") self._test_not_ok( option, "foo", "Option 'optname' must be a list of strings. " "Got 'foo'")
def _extract_sim_options(self): """ Return all supported sim options """ result = dict((opt.name, opt) for opt in [ VHDLAssertLevelOption(), BooleanOption("disable_ieee_warnings"), ListOfStringOption("pli") ]) for sim_class in self.supported_simulators(): for opt in sim_class.sim_options: assert hasattr(opt, "name") assert hasattr(opt, "validate") assert opt.name.startswith(sim_class.name + ".") assert opt.name not in result result[opt.name] = opt return result
class ActiveHDLInterface(SimulatorInterface): """ Active HDL interface """ name = "activehdl" supports_gui_flag = True package_users_depend_on_bodies = True compile_options = [ ListOfStringOption("activehdl.vcom_flags"), ListOfStringOption("activehdl.vlog_flags"), ] sim_options = [ ListOfStringOption("activehdl.vsim_flags"), ListOfStringOption("activehdl.vsim_flags.gui"), StringOption("activehdl.init_file.gui"), ] @classmethod def from_args(cls, output_path, args): """ Create new instance from command line arguments object """ return cls(prefix=cls.find_prefix(), library_cfg=join(output_path, "library.cfg"), gui=args.gui) @classmethod def find_prefix_from_path(cls): return cls.find_toolchain(["vsim", "avhdl"]) @classmethod def supports_vhdl_package_generics(cls): """ Returns True when this simulator supports VHDL package generics """ proc = Process([join(cls.find_prefix(), 'vcom'), '-version'], env=cls.get_env()) consumer = VersionConsumer() proc.consume_output(consumer) if consumer.major is not None: return consumer.minor >= 1 if consumer.major == 10 else consumer.major > 10 return False def __init__(self, prefix, library_cfg="library.cfg", gui=False): SimulatorInterface.__init__(self) self._library_cfg = abspath(library_cfg) self._prefix = prefix self._gui = gui self._create_library_cfg() self._libraries = [] def setup_library_mapping(self, project): """ Setup library mapping """ mapped_libraries = self._get_mapped_libraries() for library in project.get_libraries(): self._libraries.append(library) self.create_library(library.name, library.directory, mapped_libraries) def compile_source_file_command(self, source_file): """ Returns the command to compile a single source_file """ if source_file.is_vhdl: return self.compile_vhdl_file_command(source_file) elif source_file.is_any_verilog: return self.compile_verilog_file_command(source_file) LOGGER.error("Unknown file type: %s", source_file.file_type) raise CompileError def compile_vhdl_file_command(self, source_file): """ Returns the command to compile a VHDL file """ return ([ join(self._prefix, 'vcom'), '-quiet', '-j', dirname(self._library_cfg) ] + source_file.compile_options.get("activehdl.vcom_flags", []) + [ '-' + source_file.get_vhdl_standard(), '-work', source_file.library.name, source_file.name ]) def compile_verilog_file_command(self, source_file): """ Returns the command to compile a Verilog file """ args = [join(self._prefix, 'vlog'), '-quiet', '-lc', self._library_cfg] args += source_file.compile_options.get("activehdl.vlog_flags", []) args += ['-work', source_file.library.name, source_file.name] for library in self._libraries: args += ["-l", library.name] for include_dir in source_file.include_dirs: args += ["+incdir+%s" % include_dir] for key, value in source_file.defines.items(): args += ["+define+%s=%s" % (key, value)] return args def create_library(self, library_name, path, mapped_libraries=None): """ Create and map a library_name to path """ mapped_libraries = mapped_libraries if mapped_libraries is not None else {} if not file_exists(dirname(abspath(path))): os.makedirs(dirname(abspath(path))) if not file_exists(path): proc = Process([join(self._prefix, 'vlib'), library_name, path], cwd=dirname(self._library_cfg), env=self.get_env()) proc.consume_output(callback=None) if library_name in mapped_libraries and mapped_libraries[ library_name] == path: return proc = Process([join(self._prefix, 'vmap'), library_name, path], cwd=dirname(self._library_cfg), env=self.get_env()) proc.consume_output(callback=None) def _create_library_cfg(self): """ Create the library.cfg file if it does not exist """ if file_exists(self._library_cfg): return with open(self._library_cfg, "w") as ofile: ofile.write('$INCLUDE = "%s"\n' % join(self._prefix, "..", "vlib", "library.cfg")) _library_re = re.compile(r'([a-zA-Z_]+)\s=\s"(.*)"') def _get_mapped_libraries(self): """ Get mapped libraries from library.cfg file """ with open(self._library_cfg, "r") as fptr: text = fptr.read() libraries = {} for line in text.splitlines(): match = self._library_re.match(line) if match is None: continue key = match.group(1) value = match.group(2) libraries[key] = abspath( join(dirname(self._library_cfg), dirname(value))) return libraries def _vsim_extra_args(self, config): """ Determine vsim_extra_args """ vsim_extra_args = [] vsim_extra_args = config.sim_options.get("activehdl.vsim_flags", vsim_extra_args) if self._gui: vsim_extra_args = config.sim_options.get( "activehdl.vsim_flags.gui", vsim_extra_args) return " ".join(vsim_extra_args) def _create_load_function(self, config): """ Create the vunit_load TCL function that runs the vsim command and loads the design """ set_generic_str = "\n ".join( ('set vunit_generic_%s {%s}' % (name, value) for name, value in config.generics.items())) set_generic_name_str = " ".join( ('-g/%s/%s=${vunit_generic_%s}' % (config.entity_name, name, name) for name in config.generics)) pli_str = " ".join("-pli \"%s\"" % fix_path(name) for name in config.sim_options.get('pli', [])) vsim_flags = [ pli_str, set_generic_name_str, "-lib", config.library_name, config.entity_name, self._vsim_extra_args(config) ] if config.architecture_name is not None: vsim_flags.append(config.architecture_name) if config.sim_options.get("disable_ieee_warnings", False): vsim_flags.append("-ieee_nowarn") vhdl_assert_stop_level_mapping = dict(warning=1, error=2, failure=3) tcl = """ proc vunit_load {{}} {{ {set_generic_str} set vsim_failed [catch {{ vsim {vsim_flags} }}] if {{${{vsim_failed}}}} {{ return 1 }} global breakassertlevel set breakassertlevel {breaklevel} global builtinbreakassertlevel set builtinbreakassertlevel $breakassertlevel set no_vhdl_test_runner_exit [catch {{examine /run_base_pkg/runner.exit_simulation}}] if {{${{no_vhdl_test_runner_exit}}}} {{ echo {{Error: No vunit test runner package used}} return 1 }} return 0 }} """.format(set_generic_str=set_generic_str, vsim_flags=" ".join(vsim_flags), breaklevel=vhdl_assert_stop_level_mapping[ config.vhdl_assert_stop_level]) return tcl @staticmethod def _create_run_function(): """ Create the vunit_run function to run the test bench """ return """ proc vunit_run {} { set has_vhdl_runner [expr ![catch {examine /run_base_pkg/runner}]] if {${has_vhdl_runner}} { set status_boolean "/run_base_pkg/runner.exit_without_errors" set true_value true } else { echo "No finish mechanism detected" return 1; } run -all set failed [expr [examine ${status_boolean}]!=${true_value}] if {$failed} { catch { # tb command can fail when error comes from pli echo "" echo "Stack trace result from 'bt' command" bt } } return $failed } """ def _create_common_script(self, config): """ Create tcl script with functions common to interactive and batch modes """ tcl = "" tcl += self._create_load_function(config) tcl += self._create_run_function() return tcl @staticmethod def _create_batch_script(common_file_name, load_only=False): """ Create tcl script to run in batch mode """ batch_do = "" batch_do += "source \"%s\"\n" % fix_path(common_file_name) batch_do += "set failed [vunit_load]\n" batch_do += "if {$failed} {quit -code 1}\n" if not load_only: batch_do += "set failed [vunit_run]\n" batch_do += "if {$failed} {quit -code 1}\n" batch_do += "quit -code 0\n" return batch_do def _create_gui_script(self, common_file_name, config): """ Create the user facing script which loads common functions and prints a help message """ tcl = "" tcl += 'source "%s"\n' % fix_path(common_file_name) tcl += 'workspace create workspace\n' tcl += 'design create -a design .\n' for library in self._libraries: tcl += "vmap %s %s\n" % (library.name, fix_path(library.directory)) tcl += 'vunit_load\n' init_file = config.sim_options.get(self.name + ".init_file.gui", None) if init_file is not None: tcl += 'source "%s"\n' % fix_path(abspath(init_file)) tcl += 'puts "VUnit help: Design already loaded. Use run -all to run the test."\n' return tcl def _run_batch_file(self, batch_file_name, gui, cwd): """ Run a test bench in batch by invoking a new vsim process from the command line """ todo = "@do -tcl \"\"%s\"\"" % fix_path(batch_file_name) if not gui: todo = "@onerror {quit -code 1};" + todo try: args = [ join(self._prefix, "vsim"), "-gui" if gui else "-c", "-l", join(dirname(batch_file_name), "transcript"), '-do', todo ] proc = Process(args, cwd=cwd, env=self.get_env()) proc.consume_output() except Process.NonZeroExitCode: return False return True def simulate(self, output_path, test_suite_name, config, elaborate_only): """ Run a test bench """ common_file_name = join(output_path, "common.tcl") batch_file_name = join(output_path, "batch.tcl") gui_file_name = join(output_path, "gui.tcl") write_file(common_file_name, self._create_common_script(config)) write_file(gui_file_name, self._create_gui_script(common_file_name, config)) write_file(batch_file_name, self._create_batch_script(common_file_name, elaborate_only)) if self._gui: gui_path = join(output_path, "gui") renew_path(gui_path) return self._run_batch_file(gui_file_name, gui=True, cwd=gui_path) return self._run_batch_file(batch_file_name, gui=False, cwd=dirname(self._library_cfg))
class IncisiveInterface(SimulatorInterface): # pylint: disable=too-many-instance-attributes """ Interface for the Cadence Incisive simulator """ name = "incisive" supports_gui_flag = True package_users_depend_on_bodies = False compile_options = [ ListOfStringOption("incisive.irun_vhdl_flags"), ListOfStringOption("incisive.irun_verilog_flags"), ] sim_options = [ ListOfStringOption("incisive.irun_sim_flags") ] @staticmethod def add_arguments(parser): """ Add command line arguments """ group = parser.add_argument_group("Incisive irun", description="Incisive irun-specific flags") group.add_argument("--cdslib", default=None, help="The cds.lib file to use. If not given, VUnit maintains its own cds.lib file.") group.add_argument("--hdlvar", default=None, help="The hdl.var file to use. If not given, VUnit does not use a hdl.var file.") @classmethod def from_args(cls, args, output_path, **kwargs): """ Create new instance from command line arguments object """ return cls(prefix=cls.find_prefix(), output_path=output_path, log_level=args.log_level, gui=args.gui, cdslib=args.cdslib, hdlvar=args.hdlvar) @classmethod def find_prefix_from_path(cls): """ Find incisive simulator from PATH environment variable """ return cls.find_toolchain(['irun']) @staticmethod def supports_vhdl_contexts(): """ Returns True when this simulator supports VHDL 2008 contexts """ return False def __init__(self, # pylint: disable=too-many-arguments prefix, output_path, gui=False, log_level=None, cdslib=None, hdlvar=None): SimulatorInterface.__init__(self, output_path, gui) self._prefix = prefix self._libraries = [] self._log_level = log_level if cdslib is None: self._cdslib = abspath(join(output_path, 'cds.lib')) else: self._cdslib = abspath(cdslib) self._hdlvar = hdlvar self._cds_root_irun = self.find_cds_root_irun() self._create_cdslib() def find_cds_root_irun(self): """ Finds irun cds root """ return subprocess.check_output([join(self._prefix, 'cds_root'), 'irun']).splitlines()[0] def find_cds_root_virtuoso(self): """ Finds virtuoso cds root """ try: return subprocess.check_output([join(self._prefix, 'cds_root'), 'virtuoso']).splitlines()[0] except subprocess.CalledProcessError: return None def _create_cdslib(self): """ Create the cds.lib file in the output directory if it does not exist """ cds_root_virtuoso = self.find_cds_root_virtuoso() if cds_root_virtuoso is None: contents = """\ ## cds.lib: Defines the locations of compiled libraries. softinclude {0}/tools/inca/files/cds.lib # needed for referencing the library 'basic' for cells 'cds_alias', 'cds_thru' etc. in analog models: # NOTE: 'virtuoso' executable not found! # define basic ".../tools/dfII/etc/cdslib/basic" define work "{1}/libraries/work" """.format(self._cds_root_irun, self._output_path) else: contents = """\ ## cds.lib: Defines the locations of compiled libraries. softinclude {0}/tools/inca/files/cds.lib # needed for referencing the library 'basic' for cells 'cds_alias', 'cds_thru' etc. in analog models: define basic "{1}/tools/dfII/etc/cdslib/basic" define work "{2}/libraries/work" """.format(self._cds_root_irun, cds_root_virtuoso, self._output_path) write_file(self._cdslib, contents) def setup_library_mapping(self, project): """ Compile project using vhdl_standard """ mapped_libraries = self._get_mapped_libraries() for library in project.get_libraries(): self._libraries.append(library) self.create_library(library.name, library.directory, mapped_libraries) def compile_source_file_command(self, source_file): """ Returns the command to compile a single source file """ if source_file.is_vhdl: return self.compile_vhdl_file_command(source_file) if source_file.is_any_verilog: return self.compile_verilog_file_command(source_file) raise CompileError @staticmethod def _vhdl_std_opt(vhdl_standard): """ Convert standard to format of irun command line flag """ if vhdl_standard == VHDL.STD_2002: return "-v200x -extv200x" if vhdl_standard == VHDL.STD_2008: return "-v200x -extv200x" if vhdl_standard == VHDL.STD_1993: return "-v93" raise ValueError("Invalid VHDL standard %s" % vhdl_standard) def compile_vhdl_file_command(self, source_file): """ Returns command to compile a VHDL file """ cmd = join(self._prefix, 'irun') args = [] args += ['-compile'] args += ['-nocopyright'] args += ['-licqueue'] args += ['-nowarn DLCPTH'] # "cds.lib Invalid path" args += ['-nowarn DLCVAR'] # "cds.lib Invalid environment variable ''." args += ['%s' % self._vhdl_std_opt(source_file.get_vhdl_standard())] args += ['-work work'] args += ['-cdslib "%s"' % self._cdslib] args += self._hdlvar_args() args += ['-log "%s"' % join(self._output_path, "irun_compile_vhdl_file_%s.log" % source_file.library.name)] if not self._log_level == "debug": args += ['-quiet'] else: args += ['-messages'] args += ['-libverbose'] args += source_file.compile_options.get('incisive.irun_vhdl_flags', []) args += ['-nclibdirname "%s"' % dirname(source_file.library.directory)] args += ['-makelib %s' % source_file.library.directory] args += ['"%s"' % source_file.name] args += ['-endlib'] argsfile = join(self._output_path, "irun_compile_vhdl_file_%s.args" % source_file.library.name) write_file(argsfile, "\n".join(args)) return [cmd, '-f', argsfile] def compile_verilog_file_command(self, source_file): """ Returns commands to compile a Verilog file """ cmd = join(self._prefix, 'irun') args = [] args += ['-compile'] args += ['-nocopyright'] args += ['-licqueue'] # "Ignored unexpected semicolon following SystemVerilog description keyword (endfunction)." args += ['-nowarn UEXPSC'] # "cds.lib Invalid path" args += ['-nowarn DLCPTH'] # "cds.lib Invalid environment variable ''." args += ['-nowarn DLCVAR'] args += ['-work work'] args += source_file.compile_options.get('incisive.irun_verilog_flags', []) args += ['-cdslib "%s"' % self._cdslib] args += self._hdlvar_args() args += ['-log "%s"' % join(self._output_path, "irun_compile_verilog_file_%s.log" % source_file.library.name)] if not self._log_level == "debug": args += ['-quiet'] else: args += ['-messages'] args += ['-libverbose'] for include_dir in source_file.include_dirs: args += ['-incdir "%s"' % include_dir] # for "disciplines.vams" etc. args += ['-incdir "%s/tools/spectre/etc/ahdl/"' % self._cds_root_irun] for key, value in source_file.defines.items(): args += ['-define %s=%s' % (key, value.replace('"', '\\"'))] args += ['-nclibdirname "%s"' % dirname(source_file.library.directory)] args += ['-makelib %s' % source_file.library.name] args += ['"%s"' % source_file.name] args += ['-endlib'] argsfile = join(self._output_path, "irun_compile_verilog_file_%s.args" % source_file.library.name) write_file(argsfile, "\n".join(args)) return [cmd, '-f', argsfile] def create_library(self, library_name, library_path, mapped_libraries=None): """ Create and map a library_name to library_path """ mapped_libraries = mapped_libraries if mapped_libraries is not None else {} if not file_exists(dirname(abspath(library_path))): os.makedirs(dirname(abspath(library_path))) if library_name in mapped_libraries and mapped_libraries[library_name] == library_path: return cds = CDSFile.parse(self._cdslib) cds[library_name] = library_path cds.write(self._cdslib) def _get_mapped_libraries(self): """ Get mapped libraries from cds.lib file """ cds = CDSFile.parse(self._cdslib) return cds def simulate(self, # pylint: disable=too-many-locals output_path, test_suite_name, config, elaborate_only=False): """ Elaborates and Simulates with entity as top level using generics """ script_path = join(output_path, self.name) launch_gui = self._gui is not False and not elaborate_only if elaborate_only: steps = ['elaborate'] else: steps = ['elaborate', 'simulate'] for step in steps: cmd = join(self._prefix, 'irun') args = [] if step == 'elaborate': args += ['-elaborate'] args += ['-nocopyright'] args += ['-licqueue'] # args += ['-dumpstack'] # args += ['-gdbsh'] # args += ['-rebuild'] # args += ['-gdb'] # args += ['-gdbelab'] args += ['-errormax 10'] args += ['-nowarn WRMNZD'] args += ['-nowarn DLCPTH'] # "cds.lib Invalid path" args += ['-nowarn DLCVAR'] # "cds.lib Invalid environment variable ''." args += ['-ncerror EVBBOL'] # promote to error: "bad boolean literal in generic association" args += ['-ncerror EVBSTR'] # promote to error: "bad string literal in generic association" args += ['-ncerror EVBNAT'] # promote to error: "bad natural literal in generic association" args += ['-work work'] args += ['-nclibdirname "%s"' % (join(self._output_path, "libraries"))] # @TODO: ugly args += config.sim_options.get('incisive.irun_sim_flags', []) args += ['-cdslib "%s"' % self._cdslib] args += self._hdlvar_args() args += ['-log "%s"' % join(script_path, "irun_%s.log" % step)] if not self._log_level == "debug": args += ['-quiet'] else: args += ['-messages'] # args += ['-libverbose'] args += self._generic_args(config.entity_name, config.generics) for library in self._libraries: args += ['-reflib "%s"' % library.directory] if launch_gui: args += ['-access +rwc'] # args += ['-linedebug'] args += ['-gui'] else: args += ['-access +r'] args += ['-input "@run"'] if config.architecture_name is None: # we have a SystemVerilog toplevel: args += ['-top %s' % join('%s.%s:sv' % (config.library_name, config.entity_name))] else: # we have a VHDL toplevel: args += ['-top %s' % join('%s.%s:%s' % (config.library_name, config.entity_name, config.architecture_name))] argsfile = "%s/irun_%s.args" % (script_path, step) write_file(argsfile, "\n".join(args)) if not run_command([cmd, '-f', relpath(argsfile, script_path)], cwd=script_path, env=self.get_env()): return False return True def _hdlvar_args(self): """ Return hdlvar argument if available """ if self._hdlvar is None: return [] return ['-hdlvar "%s"' % self._hdlvar] @staticmethod def _generic_args(entity_name, generics): """ Create irun arguments for generics/parameters """ args = [] for name, value in generics.items(): if _generic_needs_quoting(value): args += ['''-gpg "%s.%s => \\"%s\\""''' % (entity_name, name, value)] else: args += ['''-gpg "%s.%s => %s"''' % (entity_name, name, value)] return args
class RivieraProInterface(VsimSimulatorMixin, SimulatorInterface): """ Riviera Pro interface """ name = "rivierapro" supports_gui_flag = True package_users_depend_on_bodies = True compile_options = [ ListOfStringOption("rivierapro.vcom_flags"), ListOfStringOption("rivierapro.vlog_flags"), ] sim_options = [ ListOfStringOption("rivierapro.vsim_flags"), ListOfStringOption("rivierapro.vsim_flags.gui"), ListOfStringOption("rivierapro.init_files.after_load"), StringOption("rivierapro.init_file.gui"), ] @classmethod def from_args(cls, args, output_path, **kwargs): """ Create new instance from command line arguments object """ persistent = not (args.unique_sim or args.gui) return cls(prefix=cls.find_prefix(), output_path=output_path, persistent=persistent, gui=args.gui) @classmethod def find_prefix_from_path(cls): """ Find RivieraPro toolchain. Must have vsim and vsimsa binaries but no avhdl.exe """ def no_avhdl(path): return not file_exists(join(path, "avhdl.exe")) return cls.find_toolchain(["vsim", "vsimsa"], constraints=[no_avhdl]) @classmethod def get_osvvm_coverage_api(cls): """ Returns simulator name when OSVVM coverage API is supported, None otherwise. """ proc = Process([join(cls.find_prefix(), 'vcom'), '-version'], env=cls.get_env()) consumer = VersionConsumer() proc.consume_output(consumer) if consumer.year is not None: if (consumer.year == 2016 and consumer.month >= 10) or (consumer.year > 2016): return cls.name return None @classmethod def supports_vhdl_package_generics(cls): """ Returns True when this simulator supports VHDL package generics """ return True def __init__(self, prefix, output_path, persistent=False, gui=False): SimulatorInterface.__init__(self, output_path, gui) VsimSimulatorMixin.__init__(self, prefix, persistent, sim_cfg_file_name=join(output_path, "library.cfg")) self._create_library_cfg() self._libraries = [] self._coverage_files = set() def add_simulator_specific(self, project): """ Add builtin (global) libraries """ built_in_libraries = self._get_mapped_libraries(self._builtin_library_cfg) for library_name in built_in_libraries: # A user might shadow a built in library with their own version if not project.has_library(library_name): project.add_builtin_library(library_name) def setup_library_mapping(self, project): """ Setup library mapping """ mapped_libraries = self._get_mapped_libraries(self._sim_cfg_file_name) for library in project.get_libraries(): self._libraries.append(library) self.create_library(library.name, library.directory, mapped_libraries) def compile_source_file_command(self, source_file): """ Returns the command to compile a single source_file """ if source_file.is_vhdl: return self.compile_vhdl_file_command(source_file) if source_file.is_any_verilog: return self.compile_verilog_file_command(source_file) LOGGER.error("Unknown file type: %s", source_file.file_type) raise CompileError def compile_vhdl_file_command(self, source_file): """ Returns the command to compile a VHDL file """ return ([join(self._prefix, 'vcom'), '-quiet', '-j', dirname(self._sim_cfg_file_name)] + source_file.compile_options.get("rivierapro.vcom_flags", []) + ['-' + source_file.get_vhdl_standard(), '-work', source_file.library.name, source_file.name]) def compile_verilog_file_command(self, source_file): """ Returns the command to compile a Verilog file """ args = [join(self._prefix, 'vlog'), '-quiet', '-lc', self._sim_cfg_file_name] if source_file.is_system_verilog: args += ['-sv2k12'] args += source_file.compile_options.get("rivierapro.vlog_flags", []) args += ['-work', source_file.library.name, source_file.name] for library in self._libraries: args += ["-l", library.name] for include_dir in source_file.include_dirs: args += ["+incdir+%s" % include_dir] for key, value in source_file.defines.items(): args += ["+define+%s=%s" % (key, value)] return args def create_library(self, library_name, path, mapped_libraries=None): """ Create and map a library_name to path """ mapped_libraries = mapped_libraries if mapped_libraries is not None else {} if not file_exists(dirname(abspath(path))): os.makedirs(dirname(abspath(path))) if not file_exists(path): proc = Process([join(self._prefix, 'vlib'), library_name, path], cwd=dirname(self._sim_cfg_file_name), env=self.get_env()) proc.consume_output(callback=None) if library_name in mapped_libraries and mapped_libraries[library_name] == path: return proc = Process([join(self._prefix, 'vmap'), library_name, path], cwd=dirname(self._sim_cfg_file_name), env=self.get_env()) proc.consume_output(callback=None) def _create_library_cfg(self): """ Create the library.cfg file if it does not exist """ if file_exists(self._sim_cfg_file_name): return with open(self._sim_cfg_file_name, "w") as ofile: ofile.write('$INCLUDE = "%s"\n' % self._builtin_library_cfg) @property def _builtin_library_cfg(self): return join(self._prefix, "..", "vlib", "library.cfg") _library_re = re.compile(r'([a-zA-Z_0-9]+)\s=\s(.*)') def _get_mapped_libraries(self, library_cfg_file): """ Get mapped libraries by running vlist on the working directory """ lines = [] proc = Process([join(self._prefix, 'vlist')], cwd=dirname(library_cfg_file)) proc.consume_output(callback=lines.append) libraries = {} for line in lines: match = self._library_re.match(line) if match is None: continue key = match.group(1) value = match.group(2) libraries[key] = abspath(join(dirname(library_cfg_file), dirname(value))) return libraries def _create_load_function(self, test_suite_name, # pylint: disable=unused-argument config, output_path): """ Create the vunit_load TCL function that runs the vsim command and loads the design """ set_generic_str = " ".join(('-g/%s/%s=%s' % (config.entity_name, name, format_generic(value)) for name, value in config.generics.items())) pli_str = " ".join("-pli \"%s\"" % fix_path(name) for name in config.sim_options.get('pli', [])) vsim_flags = ["-dataset {%s}" % fix_path(join(output_path, "dataset.asdb")), pli_str, set_generic_str] if config.sim_options.get("enable_coverage", False): coverage_file_path = join(output_path, "coverage.acdb") self._coverage_files.add(coverage_file_path) vsim_flags += ["-acdb_file {%s}" % coverage_file_path] vsim_flags += [self._vsim_extra_args(config)] if config.sim_options.get("disable_ieee_warnings", False): vsim_flags.append("-ieee_nowarn") # Add the the testbench top-level unit last as coverage is # only collected for the top-level unit specified last vsim_flags += ["-lib", config.library_name, config.entity_name] if config.architecture_name is not None: vsim_flags.append(config.architecture_name) tcl = """ proc vunit_load {{}} {{ # Make the variable 'aldec' visible; otherwise, the Matlab interface # is broken because vsim does not find the library aldec_matlab_cosim. global aldec set vsim_failed [catch {{ eval vsim {{{vsim_flags}}} }}] if {{${{vsim_failed}}}} {{ return true }} if {{[_vunit_source_init_files_after_load]}} {{ return true }} vhdlassert.break {break_level} vhdlassert.break -builtin {break_level} return false }} """.format(vsim_flags=" ".join(vsim_flags), break_level=config.vhdl_assert_stop_level) return tcl def _vsim_extra_args(self, config): """ Determine vsim_extra_args """ vsim_extra_args = [] vsim_extra_args = config.sim_options.get("rivierapro.vsim_flags", vsim_extra_args) if self._gui: vsim_extra_args = config.sim_options.get("rivierapro.vsim_flags.gui", vsim_extra_args) return " ".join(vsim_extra_args) @staticmethod def _create_run_function(): """ Create the vunit_run function to run the test bench """ return """ proc _vunit_run_failure {} { catch { # tb command can fail when error comes from pli echo "Stack trace result from 'bt' command" bt } } proc _vunit_run {} { proc on_break {} { resume } onbreak {on_break} run -all } proc _vunit_sim_restart {} { restart } """ def merge_coverage(self, file_name, args=None): """ Merge coverage from all test cases, """ # Teardown to ensure acdb file was written. self._persistent_shell.teardown() merge_command = "acdb merge" for coverage_file in self._coverage_files: if file_exists(coverage_file): merge_command += " -i {%s}" % coverage_file.replace('\\', '/') else: LOGGER.warning("Missing coverage file: %s", coverage_file) if args is not None: merge_command += " " + " ".join("{%s}" % arg for arg in args) merge_command += " -o {%s}" % file_name.replace('\\', '/') merge_script_name = join(self._output_path, "acdb_merge.tcl") with open(merge_script_name, "w") as fptr: fptr.write(merge_command + "\n") vcover_cmd = [join(self._prefix, 'vsim'), '-c', '-do', 'source %s; quit;' % merge_script_name.replace('\\', '/')] print("Merging coverage files into %s..." % file_name) vcover_merge_process = Process(vcover_cmd, env=self.get_env()) vcover_merge_process.consume_output() print("Done merging coverage files")
class ModelSimInterface(VsimSimulatorMixin, SimulatorInterface): # pylint: disable=too-many-instance-attributes """ Mentor Graphics ModelSim interface The interface supports both running each simulation in separate vsim processes or re-using the same vsim process to avoid startup-overhead (persistent=True) """ name = "modelsim" supports_gui_flag = True package_users_depend_on_bodies = False compile_options = [ ListOfStringOption("modelsim.vcom_flags"), ListOfStringOption("modelsim.vlog_flags"), ] sim_options = [ ListOfStringOption("modelsim.vsim_flags"), ListOfStringOption("modelsim.vsim_flags.gui"), ListOfStringOption("modelsim.init_files.after_load"), StringOption("modelsim.init_file.gui"), ] @classmethod def from_args(cls, output_path, args): """ Create new instance from command line arguments object """ persistent = not (args.unique_sim or args.gui) return cls(prefix=cls.find_prefix(), output_path=output_path, persistent=persistent, coverage=args.coverage, gui=args.gui) @classmethod def find_prefix_from_path(cls): """ Find first valid modelsim toolchain prefix """ def has_modelsim_ini(path): return os.path.isfile(join(path, "..", "modelsim.ini")) return cls.find_toolchain(["vsim"], constraints=[has_modelsim_ini]) @classmethod def supports_vhdl_package_generics(cls): """ Returns True when this simulator supports VHDL package generics """ return True def __init__(self, prefix, output_path, persistent=False, gui=False, coverage=None): SimulatorInterface.__init__(self, output_path, gui) VsimSimulatorMixin.__init__(self, prefix, persistent, sim_cfg_file_name=join( output_path, "modelsim.ini")) self._libraries = [] self._coverage = coverage self._coverage_files = set() assert not (persistent and gui) self._create_modelsim_ini() def _create_modelsim_ini(self): """ Create the modelsim.ini file if it does not exist """ if file_exists(self._sim_cfg_file_name): return parent = dirname(self._sim_cfg_file_name) if not file_exists(parent): os.makedirs(parent) original_modelsim_ini = os.environ.get( "VUNIT_MODELSIM_INI", join(self._prefix, "..", "modelsim.ini")) with open(original_modelsim_ini, 'rb') as fread: with open(self._sim_cfg_file_name, 'wb') as fwrite: fwrite.write(fread.read()) def add_simulator_specific(self, project): """ Add libraries from modelsim.ini file and add coverage flags """ mapped_libraries = self._get_mapped_libraries() for library_name in mapped_libraries: if not project.has_library(library_name): project.add_builtin_library(library_name) if self._coverage is None: return # Add coverage options for source_file in project.get_source_files_in_order(): if not source_file.compile_options.get("disable_coverage", False): source_file.add_compile_option("modelsim.vcom_flags", ["+cover=" + self._coverage]) source_file.add_compile_option("modelsim.vlog_flags", ["+cover=" + self._coverage]) def setup_library_mapping(self, project): """ Setup library mapping """ mapped_libraries = self._get_mapped_libraries() for library in project.get_libraries(): self._libraries.append(library) self.create_library(library.name, library.directory, mapped_libraries) def compile_source_file_command(self, source_file): """ Returns the command to compile a single source file """ if source_file.is_vhdl: return self.compile_vhdl_file_command(source_file) elif source_file.is_any_verilog: return self.compile_verilog_file_command(source_file) LOGGER.error("Unknown file type: %s", source_file.file_type) raise CompileError def compile_vhdl_file_command(self, source_file): """ Returns the command to compile a vhdl file """ return ([ join(self._prefix, 'vcom'), '-quiet', '-modelsimini', self._sim_cfg_file_name ] + source_file.compile_options.get("modelsim.vcom_flags", []) + [ '-' + source_file.get_vhdl_standard(), '-work', source_file.library.name, source_file.name ]) def compile_verilog_file_command(self, source_file): """ Returns the command to compile a verilog file """ args = [ join(self._prefix, 'vlog'), '-quiet', '-modelsimini', self._sim_cfg_file_name ] if source_file.is_system_verilog: args += ["-sv"] args += source_file.compile_options.get("modelsim.vlog_flags", []) args += ['-work', source_file.library.name, source_file.name] for library in self._libraries: args += ["-L", library.name] for include_dir in source_file.include_dirs: args += ["+incdir+%s" % include_dir] for key, value in source_file.defines.items(): args += ["+define+%s=%s" % (key, value)] return args def create_library(self, library_name, path, mapped_libraries=None): """ Create and map a library_name to path """ mapped_libraries = mapped_libraries if mapped_libraries is not None else {} if not file_exists(dirname(abspath(path))): os.makedirs(dirname(abspath(path))) if not file_exists(path): proc = Process([join(self._prefix, 'vlib'), '-unix', path], env=self.get_env()) proc.consume_output(callback=None) if library_name in mapped_libraries and mapped_libraries[ library_name] == path: return cfg = parse_modelsimini(self._sim_cfg_file_name) cfg.set("Library", library_name, path) write_modelsimini(cfg, self._sim_cfg_file_name) def _get_mapped_libraries(self): """ Get mapped libraries from modelsim.ini file """ cfg = parse_modelsimini(self._sim_cfg_file_name) libraries = dict(cfg.items("Library")) if "others" in libraries: del libraries["others"] return libraries def _create_load_function(self, test_suite_name, config, output_path): """ Create the vunit_load TCL function that runs the vsim command and loads the design """ set_generic_str = " ".join( ('-g/%s/%s=%s' % (config.entity_name, name, encode_generic_value(value)) for name, value in config.generics.items())) pli_str = " ".join("-pli {%s}" % fix_path(name) for name in config.sim_options.get('pli', [])) if config.architecture_name is None: architecture_suffix = "" else: architecture_suffix = "(%s)" % config.architecture_name if self._coverage is None: coverage_save_cmd = "" coverage_args = "" else: coverage_file = join(output_path, "coverage.ucdb") self._coverage_files.add(coverage_file) coverage_save_cmd = ( "coverage save -onexit -testname {%s} -assert -directive -cvg -codeAll {%s}" % (test_suite_name, fix_path(coverage_file))) coverage_args = "-coverage" vsim_flags = [ "-wlf {%s}" % fix_path(join(output_path, "vsim.wlf")), "-quiet", "-t ps", # for correct handling of verilog fatal/finish "-onfinish stop", pli_str, set_generic_str, config.library_name + "." + config.entity_name + architecture_suffix, coverage_args, self._vsim_extra_args(config) ] # There is a known bug in modelsim that prevents the -modelsimini flag from accepting # a space in the path even with escaping, see issue #36 if " " not in self._sim_cfg_file_name: vsim_flags.insert( 0, "-modelsimini %s" % fix_path(self._sim_cfg_file_name)) for library in self._libraries: vsim_flags += ["-L", library.name] vhdl_assert_stop_level_mapping = dict(warning=1, error=2, failure=3) tcl = """ proc vunit_load {{{{vsim_extra_args ""}}}} {{ set vsim_failed [catch {{ eval vsim ${{vsim_extra_args}} {{{vsim_flags}}} }}] if {{${{vsim_failed}}}} {{ echo Command 'vsim ${{vsim_extra_args}} {vsim_flags}' failed echo Bad flag from vsim_extra_args? return true }} if {{[_vunit_source_init_files_after_load]}} {{ return true }} global BreakOnAssertion set BreakOnAssertion {break_on_assert} global NumericStdNoWarnings set NumericStdNoWarnings {no_warnings} global StdArithNoWarnings set StdArithNoWarnings {no_warnings} {coverage_save_cmd} return false }} """.format(coverage_save_cmd=coverage_save_cmd, vsim_flags=" ".join(vsim_flags), break_on_assert=vhdl_assert_stop_level_mapping[ config.vhdl_assert_stop_level], no_warnings=1 if config.sim_options.get("disable_ieee_warnings", False) else 0) return tcl @staticmethod def _create_run_function(): """ Create the vunit_run function to run the test bench """ return """ proc _vunit_run_failure {} { catch { # tb command can fail when error comes from pli echo "Stack trace result from 'tb' command" echo [tb] echo echo "Surrounding code from 'see' command" echo [see] } } proc _vunit_run {} { proc on_break {} { resume } onbreak {on_break} run -all } proc _vunit_sim_restart {} { restart -f } """ def _vsim_extra_args(self, config): """ Determine vsim_extra_args """ vsim_extra_args = [] vsim_extra_args = config.sim_options.get("modelsim.vsim_flags", vsim_extra_args) if self._gui: vsim_extra_args = config.sim_options.get("modelsim.vsim_flags.gui", vsim_extra_args) return " ".join(vsim_extra_args) def post_process(self, output_path): """ Merge coverage from all test cases, top hierarchy level is removed since it has different name in each test case """ if self._coverage is None: return # Teardown to ensure ucdb file was written. del self._persistent_shell merged_coverage_file = join(output_path, "merged_coverage.ucdb") vcover_cmd = [ join(self._prefix, 'vcover'), 'merge', '-strip', '1', merged_coverage_file ] for coverage_file in self._coverage_files: if file_exists(coverage_file): vcover_cmd.append(coverage_file) else: LOGGER.warning("Missing coverage file: %s", coverage_file) print("Merging coverage files into %s..." % merged_coverage_file) vcover_merge_process = Process(vcover_cmd, env=self.get_env()) vcover_merge_process.consume_output() print("Done merging coverage files") @staticmethod def get_env(): """ Remove MODELSIM environment variable """ remove = ("MODELSIM", ) env = os.environ.copy() for key in remove: if key in env.keys(): del env[key] return env
class GHDLInterface(SimulatorInterface): """ Interface for GHDL simulator """ name = "ghdl" supports_gui_flag = True supports_colors_in_gui = True compile_options = [ ListOfStringOption("ghdl.flags"), ] sim_options = [ ListOfStringOption("ghdl.sim_flags"), ListOfStringOption("ghdl.elab_flags"), ] @staticmethod def add_arguments(parser): """ Add command line arguments """ group = parser.add_argument_group("ghdl", description="GHDL specific flags") group.add_argument("--gtkwave-fmt", choices=["vcd", "ghw"], default=None, help="Save .vcd or .ghw to open in gtkwave") group.add_argument("--gtkwave-args", default="", help="Arguments to pass to gtkwave") @classmethod def from_args(cls, args, output_path, **kwargs): """ Create instance from args namespace """ prefix = cls.find_prefix() return cls(output_path=output_path, prefix=prefix, gui=args.gui, gtkwave_fmt=args.gtkwave_fmt, gtkwave_args=args.gtkwave_args, backend=cls.determine_backend(prefix)) @classmethod def find_prefix_from_path(cls): """ Find first valid ghdl toolchain prefix """ return cls.find_toolchain(["ghdl"]) def __init__(self, # pylint: disable=too-many-arguments output_path, prefix, gui=False, gtkwave_fmt=None, gtkwave_args="", backend="llvm"): SimulatorInterface.__init__(self, output_path, gui) self._prefix = prefix self._project = None if gui and (not self.find_executable('gtkwave')): raise RuntimeError( "Cannot find the gtkwave executable in the PATH environment variable. GUI not possible") self._gui = gui self._gtkwave_fmt = "ghw" if gui and gtkwave_fmt is None else gtkwave_fmt self._gtkwave_args = gtkwave_args self._backend = backend self._vhdl_standard = None @staticmethod def determine_backend(prefix): """ Determine the GHDL backend """ mapping = { "mcode code generator": "mcode", "llvm code generator": "llvm", "GCC back-end code generator": "gcc" } output = subprocess.check_output([join(prefix, "ghdl"), "--version"]).decode() for name, backend in mapping.items(): if name in output: LOGGER.debug("Detected GHDL %s", name) return backend LOGGER.error("Could not detect known LLVM backend by parsing 'ghdl --version'") print("Expected to find one of %r" % mapping.keys()) print("== Output of 'ghdl --version'" + ("=" * 60)) print(output) print("=============================" + ("=" * 60)) raise AssertionError("No known GHDL back-end could be detected from running 'ghdl --version'") def _has_output_flag(self): """ Returns if backend supports output flag """ return self._backend in ("llvm", "gcc") def setup_library_mapping(self, project): """ Setup library mapping """ self._project = project for library in project.get_libraries(): if not exists(library.directory): os.makedirs(library.directory) vhdl_standards = set(source_file.get_vhdl_standard() for source_file in project.get_source_files_in_order() if source_file.is_vhdl) if not vhdl_standards: self._vhdl_standard = '2008' elif len(vhdl_standards) != 1: raise RuntimeError("GHDL cannot handle mixed VHDL standards, found %r" % list(vhdl_standards)) else: self._vhdl_standard = list(vhdl_standards)[0] def compile_source_file_command(self, source_file): """ Returns the command to compile a single source_file """ if source_file.is_vhdl: return self.compile_vhdl_file_command(source_file) LOGGER.error("Unknown file type: %s", source_file.file_type) raise CompileError @staticmethod def _std_str(vhdl_standard): """ Convert standard to format of GHDL command line flag """ if vhdl_standard == "2002": return "02" if vhdl_standard == "2008": return "08" if vhdl_standard == "93": return "93" raise ValueError("Invalid VHDL standard %s" % vhdl_standard) def compile_vhdl_file_command(self, source_file): """ Returns the command to compile a vhdl file """ cmd = [join(self._prefix, 'ghdl'), '-a', '--workdir=%s' % source_file.library.directory, '--work=%s' % source_file.library.name, '--std=%s' % self._std_str(source_file.get_vhdl_standard())] for library in self._project.get_libraries(): cmd += ["-P%s" % library.directory] cmd += source_file.compile_options.get("ghdl.flags", []) cmd += [source_file.name] return cmd def _get_sim_command(self, config, output_path): """ Return GHDL simulation command """ cmd = [join(self._prefix, 'ghdl')] cmd += ['--elab-run'] cmd += ['--std=%s' % self._std_str(self._vhdl_standard)] cmd += ['--work=%s' % config.library_name] cmd += ['--workdir=%s' % self._project.get_library(config.library_name).directory] cmd += ['-P%s' % lib.directory for lib in self._project.get_libraries()] if self._has_output_flag(): cmd += ['-o', join(output_path, "%s-%s" % (config.entity_name, config.architecture_name))] cmd += config.sim_options.get("ghdl.elab_flags", []) cmd += [config.entity_name, config.architecture_name] cmd += config.sim_options.get("ghdl.sim_flags", []) for name, value in config.generics.items(): cmd += ['-g%s=%s' % (name, value)] cmd += ['--assert-level=%s' % config.vhdl_assert_stop_level] if config.sim_options.get("disable_ieee_warnings", False): cmd += ["--ieee-asserts=disable"] return cmd def simulate(self, # pylint: disable=too-many-locals output_path, test_suite_name, config, elaborate_only): """ Simulate with entity as top level using generics """ script_path = join(output_path, self.name) if not exists(script_path): os.makedirs(script_path) cmd = self._get_sim_command(config, script_path) if elaborate_only: cmd += ["--no-run"] if self._gtkwave_fmt is not None: data_file_name = join(script_path, "wave.%s" % self._gtkwave_fmt) if exists(data_file_name): os.remove(data_file_name) if self._gtkwave_fmt == "ghw": cmd += ['--wave=%s' % data_file_name] elif self._gtkwave_fmt == "vcd": cmd += ['--vcd=%s' % data_file_name] else: data_file_name = None status = True try: proc = Process(cmd) proc.consume_output() except Process.NonZeroExitCode: status = False if self._gui and not elaborate_only: cmd = ["gtkwave"] + shlex.split(self._gtkwave_args) + [data_file_name] stdout.write("%s\n" % " ".join(cmd)) subprocess.call(cmd) return status
class ActiveHDLInterface(SimulatorInterface): """ Active HDL interface """ name = "activehdl" supports_gui_flag = True package_users_depend_on_bodies = True compile_options = [ ListOfStringOption("activehdl.vcom_flags"), ListOfStringOption("activehdl.vlog_flags"), ] sim_options = [ ListOfStringOption("activehdl.vsim_flags"), ListOfStringOption("activehdl.vsim_flags.gui"), StringOption("activehdl.init_file.gui"), ] @classmethod def from_args(cls, args, output_path, **kwargs): """ Create new instance from command line arguments object """ return cls(prefix=cls.find_prefix(), output_path=output_path, gui=args.gui) @classmethod def find_prefix_from_path(cls): return cls.find_toolchain(["vsim", "avhdl"]) @classmethod def supports_vhdl_package_generics(cls): """ Returns True when this simulator supports VHDL package generics """ proc = Process([join(cls.find_prefix(), "vcom"), "-version"], env=cls.get_env()) consumer = VersionConsumer() proc.consume_output(consumer) if consumer.major is not None: return consumer.minor >= 1 if consumer.major == 10 else consumer.major > 10 return False def __init__(self, prefix, output_path, gui=False): SimulatorInterface.__init__(self, output_path, gui) self._library_cfg = join(output_path, "library.cfg") self._prefix = prefix self._create_library_cfg() self._libraries = [] self._coverage_files = set() def setup_library_mapping(self, project): """ Setup library mapping """ mapped_libraries = self._get_mapped_libraries() for library in project.get_libraries(): self._libraries.append(library) self.create_library(library.name, library.directory, mapped_libraries) def compile_source_file_command(self, source_file): """ Returns the command to compile a single source_file """ if source_file.is_vhdl: return self.compile_vhdl_file_command(source_file) if source_file.is_any_verilog: return self.compile_verilog_file_command(source_file) LOGGER.error("Unknown file type: %s", source_file.file_type) raise CompileError @staticmethod def _std_str(vhdl_standard): """ Convert standard to format of Active-HDL command line flag """ if vhdl_standard <= VHDL.STD_2008: return "-%s" % vhdl_standard raise ValueError("Invalid VHDL standard %s" % vhdl_standard) def compile_vhdl_file_command(self, source_file): """ Returns the command to compile a VHDL file """ return ([ join(self._prefix, "vcom"), "-quiet", "-j", dirname(self._library_cfg) ] + source_file.compile_options.get("activehdl.vcom_flags", []) + [ self._std_str(source_file.get_vhdl_standard()), "-work", source_file.library.name, source_file.name, ]) def compile_verilog_file_command(self, source_file): """ Returns the command to compile a Verilog file """ args = [join(self._prefix, "vlog"), "-quiet", "-lc", self._library_cfg] args += source_file.compile_options.get("activehdl.vlog_flags", []) args += ["-work", source_file.library.name, source_file.name] for library in self._libraries: args += ["-l", library.name] for include_dir in source_file.include_dirs: args += ["+incdir+%s" % include_dir] for key, value in source_file.defines.items(): args += ["+define+%s=%s" % (key, value)] return args def create_library(self, library_name, path, mapped_libraries=None): """ Create and map a library_name to path """ mapped_libraries = mapped_libraries if mapped_libraries is not None else {} if not file_exists(dirname(abspath(path))): os.makedirs(dirname(abspath(path))) if not file_exists(path): proc = Process( [join(self._prefix, "vlib"), library_name, path], cwd=dirname(self._library_cfg), env=self.get_env(), ) proc.consume_output(callback=None) if library_name in mapped_libraries and mapped_libraries[ library_name] == path: return proc = Process( [join(self._prefix, "vmap"), library_name, path], cwd=dirname(self._library_cfg), env=self.get_env(), ) proc.consume_output(callback=None) def _create_library_cfg(self): """ Create the library.cfg file if it does not exist """ if file_exists(self._library_cfg): return with open(self._library_cfg, "w") as ofile: ofile.write('$INCLUDE = "%s"\n' % join(self._prefix, "..", "vlib", "library.cfg")) _library_re = re.compile(r'([a-zA-Z_]+)\s=\s"(.*)"') def _get_mapped_libraries(self): """ Get mapped libraries from library.cfg file """ with open(self._library_cfg, "r") as fptr: text = fptr.read() libraries = {} for line in text.splitlines(): match = self._library_re.match(line) if match is None: continue key = match.group(1) value = match.group(2) libraries[key] = abspath( join(dirname(self._library_cfg), dirname(value))) return libraries def _vsim_extra_args(self, config): """ Determine vsim_extra_args """ vsim_extra_args = [] vsim_extra_args = config.sim_options.get("activehdl.vsim_flags", vsim_extra_args) if self._gui: vsim_extra_args = config.sim_options.get( "activehdl.vsim_flags.gui", vsim_extra_args) return " ".join(vsim_extra_args) def _create_load_function(self, config, output_path): """ Create the vunit_load TCL function that runs the vsim command and loads the design """ set_generic_str = "\n ".join( ("set vunit_generic_%s {%s}" % (name, value) for name, value in config.generics.items())) set_generic_name_str = " ".join( ("-g/%s/%s=${vunit_generic_%s}" % (config.entity_name, name, name) for name in config.generics)) pli_str = " ".join('-pli "%s"' % fix_path(name) for name in config.sim_options.get("pli", [])) vsim_flags = [ pli_str, set_generic_name_str, "-lib", config.library_name, config.entity_name, ] if config.architecture_name is not None: vsim_flags.append(config.architecture_name) if config.sim_options.get("enable_coverage", False): coverage_file_path = join(output_path, "coverage.acdb") self._coverage_files.add(coverage_file_path) vsim_flags += ["-acdb_file {%s}" % fix_path(coverage_file_path)] vsim_flags += [self._vsim_extra_args(config)] if config.sim_options.get("disable_ieee_warnings", False): vsim_flags.append("-ieee_nowarn") # Add the the testbench top-level unit last as coverage is # only collected for the top-level unit specified last vhdl_assert_stop_level_mapping = dict(warning=1, error=2, failure=3) tcl = """ proc vunit_load {{}} {{ {set_generic_str} set vsim_failed [catch {{ vsim {vsim_flags} }}] if {{${{vsim_failed}}}} {{ return true }} global breakassertlevel set breakassertlevel {breaklevel} global builtinbreakassertlevel set builtinbreakassertlevel $breakassertlevel return false }} """.format( set_generic_str=set_generic_str, vsim_flags=" ".join(vsim_flags), breaklevel=vhdl_assert_stop_level_mapping[ config.vhdl_assert_stop_level], ) return tcl @staticmethod def _create_run_function(): """ Create the vunit_run function to run the test bench """ return """ proc vunit_run {} { run -all if {![is_test_suite_done]} { catch { # tb command can fail when error comes from pli echo "" echo "Stack trace result from 'bt' command" bt } return true; } return false; } """ def merge_coverage(self, file_name, args=None): """ Merge coverage from all test cases, """ merge_command = "onerror {quit -code 1}\n" merge_command += "acdb merge" for coverage_file in self._coverage_files: if file_exists(coverage_file): merge_command += " -i {%s}" % fix_path(coverage_file) else: LOGGER.warning("Missing coverage file: %s", coverage_file) if args is not None: merge_command += " " + " ".join("{%s}" % arg for arg in args) merge_command += " -o {%s}" % fix_path(file_name) + "\n" merge_script_name = join(self._output_path, "acdb_merge.tcl") with open(merge_script_name, "w") as fptr: fptr.write(merge_command + "\n") vcover_cmd = [ join(self._prefix, "vsimsa"), "-tcl", "%s" % fix_path(merge_script_name), ] print("Merging coverage files into %s..." % file_name) vcover_merge_process = Process(vcover_cmd, env=self.get_env()) vcover_merge_process.consume_output() print("Done merging coverage files") def _create_common_script(self, config, output_path): """ Create tcl script with functions common to interactive and batch modes """ tcl = "" tcl += get_is_test_suite_done_tcl(get_result_file_name(output_path)) tcl += self._create_load_function(config, output_path) tcl += self._create_run_function() return tcl @staticmethod def _create_batch_script(common_file_name, load_only=False): """ Create tcl script to run in batch mode """ batch_do = "" batch_do += 'source "%s"\n' % fix_path(common_file_name) batch_do += "set failed [vunit_load]\n" batch_do += "if {$failed} {quit -code 1}\n" if not load_only: batch_do += "set failed [vunit_run]\n" batch_do += "if {$failed} {quit -code 1}\n" batch_do += "quit -code 0\n" return batch_do def _create_gui_script(self, common_file_name, config): """ Create the user facing script which loads common functions and prints a help message """ tcl = "" tcl += 'source "%s"\n' % fix_path(common_file_name) tcl += "workspace create workspace\n" tcl += "design create -a design .\n" for library in self._libraries: tcl += "vmap %s %s\n" % (library.name, fix_path(library.directory)) tcl += "vunit_load\n" init_file = config.sim_options.get(self.name + ".init_file.gui", None) if init_file is not None: tcl += 'source "%s"\n' % fix_path(abspath(init_file)) tcl += ( 'puts "VUnit help: Design already loaded. Use run -all to run the test."\n' ) return tcl def _run_batch_file(self, batch_file_name, gui, cwd): """ Run a test bench in batch by invoking a new vsim process from the command line """ todo = '@do -tcl ""%s""' % fix_path(batch_file_name) if not gui: todo = "@onerror {quit -code 1};" + todo try: args = [ join(self._prefix, "vsim"), "-gui" if gui else "-c", "-l", join(dirname(batch_file_name), "transcript"), "-do", todo, ] proc = Process(args, cwd=cwd, env=self.get_env()) proc.consume_output() except Process.NonZeroExitCode: return False return True def simulate(self, output_path, test_suite_name, config, elaborate_only): """ Run a test bench """ script_path = join(output_path, self.name) common_file_name = join(script_path, "common.tcl") batch_file_name = join(script_path, "batch.tcl") gui_file_name = join(script_path, "gui.tcl") write_file(common_file_name, self._create_common_script(config, output_path)) write_file(gui_file_name, self._create_gui_script(common_file_name, config)) write_file(batch_file_name, self._create_batch_script(common_file_name, elaborate_only)) if self._gui: gui_path = join(script_path, "gui") renew_path(gui_path) return self._run_batch_file(gui_file_name, gui=True, cwd=gui_path) return self._run_batch_file(batch_file_name, gui=False, cwd=dirname(self._library_cfg))
class RivieraProInterface(VsimSimulatorMixin, SimulatorInterface): """ Riviera Pro interface """ name = "rivierapro" supports_gui_flag = True package_users_depend_on_bodies = True compile_options = [ ListOfStringOption("rivierapro.vcom_flags"), ListOfStringOption("rivierapro.vlog_flags"), ] sim_options = [ ListOfStringOption("rivierapro.vsim_flags"), ListOfStringOption("rivierapro.vsim_flags.gui"), ListOfStringOption("rivierapro.init_files.after_load"), StringOption("rivierapro.init_file.gui"), ] @classmethod def from_args(cls, output_path, args): """ Create new instance from command line arguments object """ persistent = not (args.unique_sim or args.gui) return cls(prefix=cls.find_prefix(), library_cfg=join(output_path, "library.cfg"), persistent=persistent, coverage=args.coverage, gui=args.gui) @classmethod def find_prefix_from_path(cls): """ Find RivieraPro toolchain. Must have vsim and vsimsa binaries but no avhdl.exe """ def no_avhdl(path): return not file_exists(join(path, "avhdl.exe")) return cls.find_toolchain(["vsim", "vsimsa"], constraints=[no_avhdl]) @classmethod def get_osvvm_coverage_api(cls): """ Returns simulator name when OSVVM coverage API is supported, None otherwise. """ proc = Process([join(cls.find_prefix(), 'vcom'), '-version'], env=cls.get_env()) consumer = VersionConsumer() proc.consume_output(consumer) if consumer.year is not None: if (consumer.year == 2016 and consumer.month >= 10) or (consumer.year > 2016): return cls.name return None @classmethod def supports_vhdl_package_generics(cls): """ Returns True when this simulator supports VHDL package generics """ return True def __init__(self, prefix, library_cfg="library.cfg", persistent=False, gui=False, coverage=None): SimulatorInterface.__init__(self) VsimSimulatorMixin.__init__(self, prefix, persistent, gui, library_cfg) self._create_library_cfg() self._libraries = [] self._coverage = coverage self._coverage_files = set() def add_simulator_specific(self, project): """ Add coverage flags """ if self._coverage is None: return # Add coverage options for source_file in project.get_source_files_in_order(): source_file.add_compile_option("rivierapro.vcom_flags", ['-coverage', self._coverage]) source_file.add_compile_option("rivierapro.vlog_flags", ['-coverage', self._coverage]) def setup_library_mapping(self, project): """ Setup library mapping """ mapped_libraries = self._get_mapped_libraries() for library in project.get_libraries(): self._libraries.append(library) self.create_library(library.name, library.directory, mapped_libraries) def compile_source_file_command(self, source_file): """ Returns the command to compile a single source_file """ if source_file.file_type == 'vhdl': return self.compile_vhdl_file_command(source_file) elif source_file.file_type == 'verilog': return self.compile_verilog_file_command(source_file) LOGGER.error("Unknown file type: %s", source_file.file_type) raise CompileError def compile_vhdl_file_command(self, source_file): """ Returns the command to compile a VHDL file """ return ([join(self._prefix, 'vcom'), '-quiet', '-j', dirname(self._sim_cfg_file_name)] + source_file.compile_options.get("rivierapro.vcom_flags", []) + ['-' + source_file.get_vhdl_standard(), '-work', source_file.library.name, source_file.name]) def compile_verilog_file_command(self, source_file): """ Returns the command to compile a Verilog file """ args = [join(self._prefix, 'vlog'), '-quiet', '-sv2k12', '-lc', self._sim_cfg_file_name] args += source_file.compile_options.get("rivierapro.vlog_flags", []) args += ['-work', source_file.library.name, source_file.name] for library in self._libraries: args += ["-l", library.name] for include_dir in source_file.include_dirs: args += ["+incdir+%s" % include_dir] for key, value in source_file.defines.items(): args += ["+define+%s=%s" % (key, value)] return args def create_library(self, library_name, path, mapped_libraries=None): """ Create and map a library_name to path """ mapped_libraries = mapped_libraries if mapped_libraries is not None else {} if not file_exists(dirname(abspath(path))): os.makedirs(dirname(abspath(path))) if not file_exists(path): proc = Process([join(self._prefix, 'vlib'), library_name, path], cwd=dirname(self._sim_cfg_file_name), env=self.get_env()) proc.consume_output(callback=None) if library_name in mapped_libraries and mapped_libraries[library_name] == path: return proc = Process([join(self._prefix, 'vmap'), library_name, path], cwd=dirname(self._sim_cfg_file_name), env=self.get_env()) proc.consume_output(callback=None) def _create_library_cfg(self): """ Create the library.cfg file if it does not exist """ if file_exists(self._sim_cfg_file_name): return with open(self._sim_cfg_file_name, "w") as ofile: ofile.write('$INCLUDE = "%s"\n' % join(self._prefix, "..", "vlib", "library.cfg")) _library_re = re.compile(r'([a-zA-Z_0-9]+)\s=\s"(.*)"') def _get_mapped_libraries(self): """ Get mapped libraries from library.cfg file """ with open(self._sim_cfg_file_name, "r") as fptr: text = fptr.read() libraries = {} for line in text.splitlines(): match = self._library_re.match(line) if match is None: continue key = match.group(1) value = match.group(2) libraries[key] = abspath(join(dirname(self._sim_cfg_file_name), dirname(value))) return libraries def _create_load_function(self, test_suite_name, # pylint: disable=unused-argument config, output_path): """ Create the vunit_load TCL function that runs the vsim command and loads the design """ set_generic_str = " ".join(('-g/%s/%s=%s' % (config.entity_name, name, format_generic(value)) for name, value in config.generics.items())) pli_str = " ".join("-pli \"%s\"" % fix_path(name) for name in config.sim_options.get('pli', [])) if self._coverage is None: coverage_args = "" coverage_file = "" else: coverage_args = "-acdb_cov " + self._coverage coverage_file_path = join(output_path, "coverage.acdb") self._coverage_files.add(coverage_file_path) coverage_file = "-acdb_file {%s}" % coverage_file_path vsim_flags = ["-dataset {%s}" % fix_path(join(output_path, "dataset.asdb")), pli_str, set_generic_str, "-lib", config.library_name, config.entity_name, coverage_args, coverage_file, self._vsim_extra_args(config)] if config.architecture_name is not None: vsim_flags.append(config.architecture_name) if config.sim_options.get("disable_ieee_warnings", False): vsim_flags.append("-ieee_nowarn") tcl = """ proc vunit_load {{}} {{ # Make the variable 'aldec' visible; otherwise, the Matlab interface # is broken because vsim does not find the library aldec_matlab_cosim. global aldec set vsim_failed [catch {{ eval vsim {{{vsim_flags}}} }}] if {{${{vsim_failed}}}} {{ return 1 }} if {{[_vunit_source_init_files_after_load]}} {{ return 1 }} set no_vhdl_test_runner_exit [catch {{examine /vunit_lib.run_base_pkg/runner.exit_simulation}}] set no_verilog_test_runner_exit [catch {{examine /\\\\package vunit_lib.vunit_pkg\\\\/__runner__}}] if {{${{no_vhdl_test_runner_exit}} && ${{no_verilog_test_runner_exit}}}} {{ echo {{Error: No vunit test runner package used}} return 1 }} vhdlassert.break {break_level} vhdlassert.break -builtin {break_level} return 0 }} """.format(vsim_flags=" ".join(vsim_flags), break_level=config.vhdl_assert_stop_level) return tcl def _vsim_extra_args(self, config): """ Determine vsim_extra_args """ vsim_extra_args = [] vsim_extra_args = config.sim_options.get("rivierapro.vsim_flags", vsim_extra_args) if self._gui: vsim_extra_args = config.sim_options.get("rivierapro.vsim_flags.gui", vsim_extra_args) return " ".join(vsim_extra_args) @staticmethod def _create_run_function(): """ Create the vunit_run function to run the test bench """ return """ proc vunit_run {} { proc on_break {} { resume } onbreak {on_break} set has_vhdl_runner [expr ![catch {examine /vunit_lib.run_base_pkg/runner}]] set has_verilog_runner [expr ![catch {examine /\\\\package vunit_lib.vunit_pkg\\\\/__runner__}]] if {${has_vhdl_runner}} { set status_boolean "/vunit_lib.run_base_pkg/runner.exit_without_errors" set true_value true } elseif {${has_verilog_runner}} { set status_boolean "/\\\\package vunit_lib.vunit_pkg\\\\/__runner__.exit_without_errors" set true_value 1 } else { echo "No finish mechanism detected" return 1; } run -all set failed [expr [examine ${status_boolean}]!=${true_value}] if {$failed} { catch { # tb command can fail when error comes from pli echo "" echo "Stack trace result from 'bt' command" bt } } return $failed } proc _vunit_sim_restart {} { restart } """ def post_process(self, output_path): """ Merge coverage from all test cases, """ if self._coverage is None: return # Teardown to ensure acdb file was written. del self._persistent_shell merged_coverage_file = join(output_path, "merged_coverage.acdb") merge_command = "acdb merge" for coverage_file in self._coverage_files: if file_exists(coverage_file): merge_command += " -i {%s}" % coverage_file.replace('\\', '/') else: LOGGER.warning("Missing coverage file: %s", coverage_file) merge_command += " -o {%s}" % merged_coverage_file.replace('\\', '/') vcover_cmd = [join(self._prefix, 'vsim'), '-c', '-do', '%s; quit;' % merge_command] print("Merging coverage files into %s..." % merged_coverage_file) vcover_merge_process = Process(vcover_cmd, env=self.get_env()) vcover_merge_process.consume_output() print("Done merging coverage files")