diff --git a/edalize/tools/modelsim.py b/edalize/tools/modelsim.py new file mode 100644 index 000000000..612f6c5a3 --- /dev/null +++ b/edalize/tools/modelsim.py @@ -0,0 +1,220 @@ +# Copyright edalize contributors +# Licensed under the 2-Clause BSD License, see LICENSE for details. +# SPDX-License-Identifier: BSD-2-Clause + +import os +import logging + +from edalize.tools.edatool import Edatool +from edalize.utils import EdaCommands + +logger = logging.getLogger(__name__) + + +class Modelsim(Edatool): + description = "ModelSim simulator from Mentor Graphics" + + TOOL_OPTIONS = { + "compilation_mode": { + "type": "str", + "desc": "Common or separate compilation, sep - for separate compilation, common - for common compilation", + }, + "vcom_options": { + "type": "str", + "desc": "Additional options for compilation with vcom", + }, + "vlog_options": { + "type": "str", + "desc": "Additional options for compilation with vlog", + }, + "vsim_options": { + "type": "str", + "desc": "Additional run options for vsim", + }, + # run_options? + } + + def _write_build_rtl_tcl_file(self, tcl_main): + incdirs = [] + libs = [] + vlog_files = [] + + tcl_build_rtl = open(os.path.join(self.work_root, "edalize_build_rtl.tcl"), "w") + + # Fill up incdirs with all include directories, before looping through all files. + for f in self.files: + self._add_include_dir(f, incdirs) + + vlog_include_dirs = ["+incdir+" + d.replace("\\", "/") for d in incdirs] + + common_compilation = self.tool_options.get("compilation_mode") == "common" + + for f in self.files: + if not f.get("logical_name"): + f["logical_name"] = "work" + if not f["logical_name"] in libs: + tcl_build_rtl.write("vlib {}\n".format(f["logical_name"])) + libs.append(f["logical_name"]) + if f["file_type"].startswith("verilogSource") or f["file_type"].startswith( + "systemVerilogSource" + ): + cmd = None + + if not f.get("is_include_file"): + # Add vlog command for non-include file types + vlog_files.append(f["name"]) + cmd = "vlog" + + args = [] + + args += self.tool_options.get("vlog_options", []) + + for k, v in self.vlogdefine.items(): + args += ["+define+{}={}".format(k, self._param_value_str(v))] + + if f["file_type"].startswith("systemVerilogSource"): + args += ["-sv"] + + args += vlog_include_dirs + elif f["file_type"].startswith("vhdlSource"): + cmd = "vcom" + if f["file_type"].endswith("-87"): + args = ["-87"] + if f["file_type"].endswith("-93"): + args = ["-93"] + if f["file_type"].endswith("-2008"): + args = ["-2008"] + else: + args = [] + + args += self.tool_options.get("vcom_options", []) + + elif f["file_type"] == "tclSource": + cmd = None + tcl_main.write("do {}\n".format(f["name"])) + elif f["file_type"] == "user": + cmd = None + else: + _s = "{} has unknown file type '{}'" + logger.warning(_s.format(f["name"], f["file_type"])) + cmd = None + if cmd and ((cmd != "vlog") or not common_compilation): + args += ["-quiet"] + args += ["-work", f["logical_name"]] + args += [f["name"].replace("\\", "/")] + tcl_build_rtl.write("{} {}\n".format(cmd, " ".join(args))) + if common_compilation: + args = self.tool_options.get("vlog_options", []) + for k, v in self.vlogdefine.items(): + args += ["+define+{}={}".format(k, self._param_value_str(v))] + + _vlog_files = [] + has_sv = False + for f in vlog_files: + _vlog_files.append(f["name"].replace("\\", "/")) + if f["file_type"].startswith("systemVerilogSource"): + has_sv = True + + if has_sv: + args += ["-sv"] + args += vlog_include_dirs + args += ["-quiet"] + args += ["-work", "work"] + args += ["-mfcu"] + tcl_build_rtl.write(f"vlog {' '.join(args)} {' '.join(_vlog_files)}") + + def write_config_files(self): + """ + Generate ModelSim specific makefile & TCL build files from template. + """ + + tcl_main = open(os.path.join(self.work_root, "edalize_main.tcl"), "w") + tcl_main.write("onerror { quit -code 1; }\n") + tcl_main.write("do edalize_build_rtl.tcl\n") + self._write_build_rtl_tcl_file(tcl_main) + tcl_main.close() + + self.render_template( + "modelsim-makefile.j2", + "modelsim-makefile", + self.template_vars, + ) + + def setup(self, edam): + super().setup(edam) + + depfiles = [] + + _parameters = [] + for key, value in self.vlogparam.items(): + _parameters += ["{}={}".format(key, self._param_value_str(value))] + for key, value in self.generic.items(): + _parameters += [ + "{}={}".format(key, self._param_value_str(value, bool_is_str=True)) + ] + _plusargs = [] + for key, value in self.plusarg.items(): + _plusargs += ["{}={}".format(key, self._param_value_str(value))] + + _vsim_options = self.tool_options.get("vsim_options", []) + _modules = [m["name"] for m in self.vpi_modules] + _clean_targets = " ".join(["clean_" + m for m in _modules]) + + self.template_vars = { + "toplevel": self.toplevel, + "name": self.name, + "vsim_options": " ".join(_vsim_options), + "parameters": " ".join(_parameters), + "plusargs": " ".join(_plusargs), + "modules": " ".join(_modules), + } + + commands = EdaCommands() + + commands.add( + ["make"] + + [ + "-f", + "modelsim-makefile", + "work", + ], + ["modelsim-build"], + depfiles, + ) + + commands.add( + ["make"] + + [ + "-f", + "modelsim-makefile", + "run", + ], + ["modelsim-run"], + ["modelsim-build"], + ) + + commands.add( + ["make"] + + [ + "-f", + "modelsim-makefile", + "run-gui", + ], + ["modelsim-run-gui"], + ["modelsim-build"], + ) + + commands.set_default_target("modelsim-build") + self.commands = commands + + def run(self): + args = ["modelsim-run"] + + # Set plusargs + if self.plusarg: + plusargs = [] + for key, value in self.plusarg.items(): + plusargs += ["{}={}".format(key, self._param_value_str(value))] + args.append("PLUSARGS=" + " ".join(plusargs)) + + return ("make", args, self.work_root) diff --git a/edalize/tools/templates/modelsim/modelsim-makefile.j2 b/edalize/tools/templates/modelsim/modelsim-makefile.j2 new file mode 100644 index 000000000..3aef76a45 --- /dev/null +++ b/edalize/tools/templates/modelsim/modelsim-makefile.j2 @@ -0,0 +1,44 @@ +#Generated by Edalize +ifndef MODEL_TECH +$(error Environment variable MODEL_TECH was not found. It should be set to /bin) +endif + +CC ?= gcc +CFLAGS := -fPIC -fno-stack-protector -g -std=c99 +CXXFLAGS := -fPIC -fno-stack-protector -g + +LD ?= ld +LDFLAGS := -shared -E + +#Try to determine if ModelSim is 32- or 64-bit. +#To manually override, set the environment MTI_VCO_MODE to 32 or 64 +ifeq ($(findstring 64, $(shell $(MODEL_TECH)/../vco)),) +CFLAGS += -m32 +CXXFLAGS += -m32 +LDFLAGS += -melf_i386 +endif + +RM ?= rm +INCS := -I$(MODEL_TECH)/../include + +VSIM ?= $(MODEL_TECH)/vsim + +TOPLEVEL := {{ toplevel }} +VPI_MODULES := {{ modules }} +PARAMETERS ?= {{ parameters }} +PLUSARGS ?= {{ plusargs }} +VSIM_OPTIONS ?= {{ vsim_options }} +EXTRA_OPTIONS ?= $(VSIM_OPTIONS) $(addprefix -g,$(PARAMETERS)) $(addprefix +,$(PLUSARGS)) + +all: work $(VPI_MODULES) + +run: work $(VPI_MODULES) + $(VSIM) -c $(addprefix -pli ,$(VPI_MODULES)) $(EXTRA_OPTIONS) -do "run -all; quit -code [expr [coverage attribute -name TESTSTATUS -concise] >= 2 ? [coverage attribute -name TESTSTATUS -concise] : 0]; exit" $(TOPLEVEL) + +run-gui: work $(VPI_MODULES) + $(VSIM) -gui $(addprefix -pli ,$(VPI_MODULES)) $(EXTRA_OPTIONS) $(TOPLEVEL) + +work: + $(VSIM) -c -do "do edalize_main.tcl; exit" + +clean: {{ clean_targets }} \ No newline at end of file diff --git a/tests/flows/sim/modelsim/Makefile b/tests/flows/sim/modelsim/Makefile new file mode 100644 index 000000000..a5955519c --- /dev/null +++ b/tests/flows/sim/modelsim/Makefile @@ -0,0 +1,22 @@ +#Auto generated by Edalize + +all: post_build + +pre_build: + +modelsim-build: | pre_build + $(EDALIZE_LAUNCHER) make -f modelsim-makefile work + +modelsim-run: modelsim-build + $(EDALIZE_LAUNCHER) make -f modelsim-makefile run + +modelsim-run-gui: modelsim-build + $(EDALIZE_LAUNCHER) make -f modelsim-makefile run-gui + +post_build: modelsim-build + +pre_run: + +run: pre_run + +post_run: run diff --git a/tests/flows/sim/modelsim/edalize_build_rtl.tcl b/tests/flows/sim/modelsim/edalize_build_rtl.tcl new file mode 100644 index 000000000..36640eb11 --- /dev/null +++ b/tests/flows/sim/modelsim/edalize_build_rtl.tcl @@ -0,0 +1,9 @@ +vlib work +vlog +define+vlogdefine_bool=1 +define+vlogdefine_int=42 +define+vlogdefine_str=hello -sv +incdir+. -quiet -work work sv_file.sv +vlog +define+vlogdefine_bool=1 +define+vlogdefine_int=42 +define+vlogdefine_str=hello +incdir+. -quiet -work work vlog_file.v +vlog +define+vlogdefine_bool=1 +define+vlogdefine_int=42 +define+vlogdefine_str=hello +incdir+. -quiet -work work vlog05_file.v +vcom -quiet -work work vhdl_file.vhd +vlib libx +vcom -quiet -work libx vhdl_lfile +vcom -2008 -quiet -work work vhdl2008_file +vlog +define+vlogdefine_bool=1 +define+vlogdefine_int=42 +define+vlogdefine_str=hello -sv +incdir+. -quiet -work work another_sv_file.sv diff --git a/tests/flows/sim/modelsim/edalize_main.tcl b/tests/flows/sim/modelsim/edalize_main.tcl new file mode 100644 index 000000000..ec4861eb3 --- /dev/null +++ b/tests/flows/sim/modelsim/edalize_main.tcl @@ -0,0 +1,3 @@ +onerror { quit -code 1; } +do edalize_build_rtl.tcl +do tcl_file.tcl diff --git a/tests/flows/sim/modelsim/modelsim-makefile b/tests/flows/sim/modelsim/modelsim-makefile new file mode 100644 index 000000000..4a6f60362 --- /dev/null +++ b/tests/flows/sim/modelsim/modelsim-makefile @@ -0,0 +1,44 @@ +#Generated by Edalize +ifndef MODEL_TECH +$(error Environment variable MODEL_TECH was not found. It should be set to /bin) +endif + +CC ?= gcc +CFLAGS := -fPIC -fno-stack-protector -g -std=c99 +CXXFLAGS := -fPIC -fno-stack-protector -g + +LD ?= ld +LDFLAGS := -shared -E + +#Try to determine if ModelSim is 32- or 64-bit. +#To manually override, set the environment MTI_VCO_MODE to 32 or 64 +ifeq ($(findstring 64, $(shell $(MODEL_TECH)/../vco)),) +CFLAGS += -m32 +CXXFLAGS += -m32 +LDFLAGS += -melf_i386 +endif + +RM ?= rm +INCS := -I$(MODEL_TECH)/../include + +VSIM ?= $(MODEL_TECH)/vsim + +TOPLEVEL := top_module +VPI_MODULES := +PARAMETERS ?= vlogparam_bool=1 vlogparam_int=42 vlogparam_str=hello +PLUSARGS ?= plusarg_bool=1 plusarg_int=42 plusarg_str=hello +VSIM_OPTIONS ?= +EXTRA_OPTIONS ?= $(VSIM_OPTIONS) $(addprefix -g,$(PARAMETERS)) $(addprefix +,$(PLUSARGS)) + +all: work $(VPI_MODULES) + +run: work $(VPI_MODULES) + $(VSIM) -c $(addprefix -pli ,$(VPI_MODULES)) $(EXTRA_OPTIONS) -do "run -all; quit -code [expr [coverage attribute -name TESTSTATUS -concise] >= 2 ? [coverage attribute -name TESTSTATUS -concise] : 0]; exit" $(TOPLEVEL) + +run-gui: work $(VPI_MODULES) + $(VSIM) -gui $(addprefix -pli ,$(VPI_MODULES)) $(EXTRA_OPTIONS) $(TOPLEVEL) + +work: + $(VSIM) -c -do "do edalize_main.tcl; exit" + +clean: \ No newline at end of file diff --git a/tests/test_flow_sim.py b/tests/test_flow_sim.py new file mode 100644 index 000000000..532a6593c --- /dev/null +++ b/tests/test_flow_sim.py @@ -0,0 +1,17 @@ +import filecmp +import os +from .edalize_flow_common import flow_fixture +from .edalize_common import tests_dir + + +def test_flow_sim_modelsim(flow_fixture): + flow_options = { + "tool": "modelsim", + } + + ff = flow_fixture("sim", flow_options=flow_options, ref_subdir="modelsim") + + ff.compare_makefile() + ff.compare_config_files( + ["edalize_build_rtl.tcl", "edalize_main.tcl", "modelsim-makefile"] + )