Skip to content

Commit

Permalink
WIP: Added support Questa Visualizer.
Browse files Browse the repository at this point in the history
  • Loading branch information
LarsAsplund committed Dec 9, 2024
1 parent bc01d74 commit 98ae3b7
Show file tree
Hide file tree
Showing 2 changed files with 235 additions and 71 deletions.
241 changes: 182 additions & 59 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 @@ -473,23 +511,31 @@ def _optimize(self, config, script_path):

return True

def _create_load_function(self, test_suite_name, config, output_path, optimize_design):
def _load_setup(self, config, output_path, optimize_design):
"""
Create the vunit_load TCL function that runs the vsim command and loads the design
Return setup part of load function that 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)
vsim_flags = " ".join(self._get_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()
)
)
pli_str = " ".join(f"-pli {{{fix_path(name)!s}}}" for name in config.sim_options.get("pli", []))
tcl = f"""
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
}}
"""

return tcl

def _load_init(self, test_suite_name, config, output_path):
"""
Return initialiation part ofter loading design.
"""

if config.sim_options.get("enable_coverage", False):
coverage_file = str(Path(output_path) / "coverage.ucdb")
Expand All @@ -498,73 +544,138 @@ 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 = ""

vhdl_assert_stop_level_mapping = {"warning": 1, "error": 2, "failure": 3}
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

tcl = f"""
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 tcl

def _create_load_function(self, test_suite_name, config, output_path, optimize_design):
"""
Create the vunit_load TCL function that runs the vsim command and loads the design
"""

tcl = """
proc vunit_load {{vsim_extra_args ""}} {"""

early_load = self._gui and self._early_load_in_gui_mode()
if not early_load:
tcl += self._load_setup(config, output_path, optimize_design)

tcl += self._load_init(test_suite_name, config, output_path)
tcl += """
return false
}
"""

return tcl

def _common_vsim_flags(self, config, 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)

if config.sim_options.get("enable_coverage", False):
coverage_args = "-coverage"
else:
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,
"-work",
f"{config.library_name}",
"-quiet",
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}")
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]

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

tcl = """
proc vunit_load {{vsim_extra_args ""}} {"""
def _get_vsim_flags(self, config, output_path, optimize_design):
"""Return vsim flags for load function."""
vsim_flags = self._common_vsim_flags(config, optimize_design)

tcl += """
set vsim_failed [catch {{
eval vsim ${{vsim_extra_args}} {{{vsim_flags}}}
}}]
pli_str = " ".join(f"-pli {{{fix_path(name)!s}}}" for name in config.sim_options.get("pli", []))

if {{${{vsim_failed}}}} {{
echo Command 'vsim ${{vsim_extra_args}} {vsim_flags}' failed
echo Bad flag from vsim_extra_args?
return true
}}
set_generic_str = " ".join(
(
f"-g/{config.entity_name!s}/{name!s}={encode_generic_value_for_tcl(value)!s}"
for name, value in config.generics.items()
)
)

if {{[_vunit_source_init_files_after_load]}} {{
return true
}}
vsim_flags += [
"-wlf",
f"{{{fix_path(str(Path(output_path) / 'vsim.wlf'))}}}",
pli_str,
set_generic_str,
"-t ps",
# for correct handling of Verilog fatal/finish
"-onfinish stop",
]

global BreakOnAssertion
set BreakOnAssertion {break_on_assert}
return vsim_flags

global NumericStdNoWarnings
set NumericStdNoWarnings {no_warnings}
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.
global StdArithNoWarnings
set StdArithNoWarnings {no_warnings}
This is required for Questa Visualizer.
"""
vsim_flags = self._common_vsim_flags(config, optimize_design)

{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,
pli_str = " ".join(f"-pli {fix_path(name)}" for name in config.sim_options.get("pli", []))

set_generic_str = " ".join(
(
f"-g/{config.entity_name!s}/{name!s}={encode_generic_value_for_args(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)

return tcl
vsim_flags += [
"-wlf",
f"{fix_path(str(Path(output_path) / 'vsim.wlf'))}",
pli_str,
"-visualizer",
"-f",
f"{fix_path(str(generics_file_name))}",
]

return vsim_flags

@staticmethod
def _create_run_function():
Expand Down Expand Up @@ -664,9 +775,9 @@ def get_env():
return env


def encode_generic_value(value):
def encode_generic_value_for_tcl(value):
"""
Ensure values with space in them are quoted
Ensure values with space and commas in them are quoted properly for TCL files.
"""
s_value = str(value)
if " " in s_value:
Expand All @@ -676,6 +787,18 @@ def encode_generic_value(value):
return s_value


def encode_generic_value_for_args(value):
"""
Ensure values with space and commas 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
Loading

0 comments on commit 98ae3b7

Please sign in to comment.