Skip to content

Commit

Permalink
Added support Questa Visualizer.
Browse files Browse the repository at this point in the history
  • Loading branch information
LarsAsplund committed Dec 6, 2024
1 parent bc01d74 commit 9c3a1eb
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 49 deletions.
175 changes: 134 additions & 41 deletions vunit/sim_if/modelsim.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@ class ModelSimInterface(VsimSimulatorMixin, SimulatorInterface): # pylint: disa
BooleanOption("modelsim.three_step_flow"),
]

@staticmethod
def add_arguments(parser):
"""
Add command line arguments
"""
group = parser.add_argument_group("modelsim/questa", description="ModelSim/Questa specific flags")
group.add_argument(
"--debugger",
choices=["original", "visualizer"],
default="original",
help="Debugger to use.",
)

@classmethod
def from_args(cls, args, output_path, **kwargs):
"""
Expand All @@ -63,6 +76,7 @@ def from_args(cls, args, output_path, **kwargs):
output_path=output_path,
persistent=persistent,
gui=args.gui,
debugger=args.debugger,
)

@classmethod
Expand Down Expand Up @@ -90,7 +104,7 @@ def supports_coverage():
"""
return True

def __init__(self, prefix, output_path, persistent=False, gui=False):
def __init__(self, prefix, output_path, *, persistent=False, gui=False, debugger="original"):
SimulatorInterface.__init__(self, output_path, gui)
VsimSimulatorMixin.__init__(
self,
Expand All @@ -102,6 +116,7 @@ def __init__(self, prefix, output_path, persistent=False, gui=False):
self._coverage_files = set()
assert not (persistent and gui)
self._create_modelsim_ini()
self._debugger = debugger
# Contains design already optimized, i.e. the optimized design can be reused
self._optimized_designs = {}
# Contains locks for each library. If locked, a design belonging to the library
Expand Down Expand Up @@ -248,6 +263,15 @@ def _optimize_design(self, config):

return config.sim_options.get("modelsim.three_step_flow", False)

def _early_load_in_gui_mode(self): # pylint: disable=unused-argument
"""
Return True if design is to be loaded on the first vsim call rather than
in the second vsim call embedded in the script file.
This is required for Questa Visualizer.
"""
return self._debugger == "visualizer"

@staticmethod
def _design_to_optimize(config):
"""
Expand Down Expand Up @@ -282,13 +306,28 @@ def _create_optimize_function(self, config):
design_to_optimize = self._design_to_optimize(config)
optimized_design = self._to_optimized_design(design_to_optimize)

vopt_library_flags = []
design_file_directory = None
for library in self._libraries:
vopt_library_flags += ["-L", library.name]
if library.name == config.library_name:
design_file_directory = library.directory

if not design_file_directory:
raise RuntimeError(f"Failed to find library directory for {config.library_name}")

design_file = str(Path(design_file_directory) / f"{optimized_design}.bin")

vopt_flags = [
self._vopt_extra_args(config),
f"{design_to_optimize}",
f"-work {{{config.library_name}}}",
"-work",
f"{{{config.library_name}}}",
"-quiet",
f"-floatgenerics+{config.entity_name}.",
f"-o {{{optimized_design}}}",
"-designfile",
f"{{{fix_path(design_file)}}}",
]

# There is a known bug in modelsim that prevents the -modelsimini flag from accepting
Expand All @@ -297,8 +336,7 @@ def _create_optimize_function(self, config):
modelsimini_option = f"-modelsimini {fix_path(self._sim_cfg_file_name)!s}"
vopt_flags.insert(0, modelsimini_option)

for library in self._libraries:
vopt_flags += ["-L", library.name]
vopt_flags += vopt_library_flags

tcl = """
proc vunit_optimize {{vopt_extra_args ""}} {"""
Expand Down Expand Up @@ -478,18 +516,7 @@ def _create_load_function(self, test_suite_name, config, output_path, optimize_d
Create the vunit_load TCL function that runs the vsim command and loads the design
"""

if optimize_design:
simulation_target = self._to_optimized_design(self._design_to_optimize(config))
else:
simulation_target = self._design_to_optimize(config)

set_generic_str = " ".join(
(
f"-g/{config.entity_name!s}/{name!s}={encode_generic_value(value)!s}"
for name, value in config.generics.items()
)
)
pli_str = " ".join(f"-pli {{{fix_path(name)!s}}}" for name in config.sim_options.get("pli", []))
vsim_flags = self._get_vsim_flags(config, output_path, optimize_design)

if config.sim_options.get("enable_coverage", False):
coverage_file = str(Path(output_path) / "coverage.ucdb")
Expand All @@ -498,32 +525,8 @@ def _create_load_function(self, test_suite_name, config, output_path, optimize_d
f"coverage save -onexit -testname {{{test_suite_name!s}}} -assert -directive "
f"-cvg -codeAll {{{fix_path(coverage_file)!s}}}"
)
coverage_args = "-coverage"
else:
coverage_save_cmd = ""
coverage_args = ""

vsim_flags = [
f"-wlf {{{fix_path(str(Path(output_path) / 'vsim.wlf'))!s}}}",
f"-work {{{config.library_name}}}",
"-quiet",
"-t ps",
# for correct handling of verilog fatal/finish
"-onfinish stop",
pli_str,
set_generic_str,
simulation_target,
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, f"-modelsimini {fix_path(self._sim_cfg_file_name)!s}")

for library in self._libraries:
vsim_flags += ["-L", library.name]

vhdl_assert_stop_level_mapping = {"warning": 1, "error": 2, "failure": 3}

Expand Down Expand Up @@ -566,6 +569,84 @@ def _create_load_function(self, test_suite_name, config, output_path, optimize_d

return tcl

def _common_vsim_flags(self, config, output_path, optimize_design):
"""Return vsim flags to normal and early load mode."""
if optimize_design:
simulation_target = self._to_optimized_design(self._design_to_optimize(config))
else:
simulation_target = self._design_to_optimize(config)

pli_str = " ".join(f"-pli {{{fix_path(name)!s}}}" for name in config.sim_options.get("pli", []))

if config.sim_options.get("enable_coverage", False):
coverage_args = "-coverage"
else:
coverage_args = ""

vsim_flags = [
simulation_target,
"-wlf",
f"{fix_path(str(Path(output_path) / 'vsim.wlf'))}",
"-work",
f"{config.library_name}",
"-quiet",
pli_str,
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(1, "-modelsimini")
vsim_flags.insert(2, f"{fix_path(self._sim_cfg_file_name)}")

for library in self._libraries:
vsim_flags += ["-L", library.name]

return vsim_flags

def _get_vsim_flags(self, config, output_path, optimize_design):
"""Return vsim flags for load function."""
vsim_flags = self._common_vsim_flags(config, output_path, optimize_design)

set_generic_str = " ".join(
(
f"-g/{config.entity_name!s}/{name!s}={encode_generic_value(value)!s}"
for name, value in config.generics.items()
)
)

vsim_flags += [
set_generic_str,
"-t ps",
# for correct handling of Verilog fatal/finish
"-onfinish stop",
]

return vsim_flags

def _get_load_flags(self, config, output_path, optimize_design):
"""
Return extra flags needed for the first vsim call in GUI mode when early load is enabled.
This is required for Questa Visualizer.
"""
vsim_flags = self._common_vsim_flags(config, output_path, optimize_design)

set_generic_str = " ".join(
(
f"-g/{config.entity_name!s}/{name!s}={encode_generic_value_for_file(value)!s}"
for name, value in config.generics.items()
)
)
generics_file_name = Path(output_path) / "generics.flags"
write_file(str(generics_file_name), set_generic_str)

vsim_flags += ["-visualizer", "-f", f"{fix_path(str(generics_file_name))}"]

return vsim_flags

@staticmethod
def _create_run_function():
"""
Expand Down Expand Up @@ -666,7 +747,7 @@ def get_env():

def encode_generic_value(value):
"""
Ensure values with space in them are quoted
Ensure values with space in them are quoted properly for the command line
"""
s_value = str(value)
if " " in s_value:
Expand All @@ -676,6 +757,18 @@ def encode_generic_value(value):
return s_value


def encode_generic_value_for_file(value):
"""
Ensure values with space in them are quoted properly for argument files
"""
s_value = str(value)
if " " in s_value:
return f"'\"{s_value}\"'"
if "," in s_value:
return f"'\"{s_value}\"'"
return s_value


def parse_modelsimini(file_name):
"""
Parse a modelsim.ini file
Expand Down
50 changes: 42 additions & 8 deletions vunit/sim_if/vsim_simulator_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,19 +290,24 @@ def _source_tcl_file(file_name, config, message):
)
return tcl

def _create_gui_script(self, common_file_name, config):
def _create_gui_script(self, common_file_name, config, include_vunit_load=True):
"""
Create the user facing script which loads common functions and prints a help message
"""
tcl = f'source "{fix_path(common_file_name)!s}"\n'
tcl += self._create_user_init_function(config)
tcl += "if {![vunit_load]} {\n"
tcl += " vunit_user_init\n"
tcl += " vunit_help\n"
tcl += "}\n"
if include_vunit_load:
tcl += "if {![vunit_load]} {\n"
tcl += " vunit_user_init\n"
tcl += " vunit_help\n"
tcl += "}\n"
else:
tcl += "vunit_user_init\n"
tcl += "vunit_help\n"

return tcl

def _run_batch_file(self, batch_file_name, gui=False):
def _run_batch_file(self, batch_file_name, gui=False, extra_args=None):
"""
Run a test bench in batch by invoking a new vsim process from the command line
"""
Expand All @@ -317,6 +322,9 @@ def _run_batch_file(self, batch_file_name, gui=False):
f'source "{fix_path(batch_file_name)!s}"',
]

if extra_args:
args += extra_args

proc = Process(args, cwd=str(Path(self._sim_cfg_file_name).parent))
proc.consume_output()
except Process.NonZeroExitCode:
Expand Down Expand Up @@ -354,6 +362,23 @@ def _optimize(self, config, script_path): # pylint: disable=unused-argument
"""
return False

def _early_load_in_gui_mode(self): # pylint: disable=unused-argument
"""
Return True if design is to be loaded on the first vsim call rather than
in the second vsim call embedded in the script file.
This is required for Questa Visualizer.
"""
return False

def _get_load_flags(self, config, output_path, optimize_design): # pylint: disable=unused-argument
"""
Return extra flags needed for the first vsim call in GUI mode when early load is enabled.
This is required for Questa Visualizer.
"""
return []

def simulate(self, output_path, test_suite_name, config, elaborate_only):
"""
Run a test bench
Expand All @@ -375,14 +400,23 @@ def simulate(self, output_path, test_suite_name, config, elaborate_only):
test_suite_name, config, script_path, output_path, optimize_design=optimize_design
),
)
write_file(str(gui_file_name), self._create_gui_script(str(common_file_name), config))

early_load = self._gui and self._early_load_in_gui_mode()
write_file(
str(gui_file_name),
self._create_gui_script(str(common_file_name), config, include_vunit_load=not early_load),
)
write_file(
str(batch_file_name),
self._create_batch_script(str(common_file_name), elaborate_only),
)

if self._gui:
return self._run_batch_file(str(gui_file_name), gui=True)
return self._run_batch_file(
str(gui_file_name),
gui=True,
extra_args=self._get_load_flags(config, output_path, optimize_design) if early_load else None,
)

if self._persistent_shell is not None:
return self._run_persistent(str(common_file_name), load_only=elaborate_only)
Expand Down

0 comments on commit 9c3a1eb

Please sign in to comment.