diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13e3bc782..951d7b415 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,11 +37,11 @@ jobs: #- { icon: 🍎, name: macos } #- { icon: 🧊, name: windows } pyver: - - '3.6' - '3.7' - '3.8' - '3.9' - '3.10' + - '3.11' runs-on: ${{ matrix.os.name }}-latest name: ${{ matrix.os.icon }} ${{ matrix.os.name }} | ${{ matrix.pyver }} steps: diff --git a/.gitignore b/.gitignore index 9b20286ae..34676b70c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,10 @@ doc/_build/ __pycache__ /.eggs /.tox +.vscode/ +build/ +dist/ +venv/ +.project +.pydevproject +/.pytest_cache/ diff --git a/NEWS b/NEWS new file mode 100644 index 000000000..c2d216bcd --- /dev/null +++ b/NEWS @@ -0,0 +1,27 @@ +0.4.1 2022-12-26 Olof Kindgren +====================================================== +* New flows: f4pga +* New backends: slang, Questa Formal +* Migrated to flow API: icarus +* icarus: Add vvp_options, support multiple top levels +* ise: Support FPGA families with spaces in name +* openlane: Support SystemVerilog +* vcs: Support launchers +* verilator: Fix include files +* vivado: Support launchers, fix pnr=none, fix include dirs on windows +* yosys: Only use -pvector for edif +* el_docker: Support additional docker args, update openlane version +* Don't capture output of script hooks + +0.4.0 2022-07-12 Olof Kindgren +====================================================== +* New backends: OpenFPGA, VPR, Mistral, gatemate +* symbiflow: Support Quicklogic +* xsim: :Fix VHDL generics quotation +* ISE: Added option to Specify JTAG chain index, fix windows path handling +* verilator: Support EDALIZE_LAUNCHER, limit CPU usage +* icestorm: Add icepack_options +* el_docker: Run as user instead of root +* vcs: Support boolean plusargs without value +* yosys: Support verilog output +* Flow API: Add get_tool_options function diff --git a/edalize/edatool.py b/edalize/edatool.py index 44a29586d..fecfd6911 100644 --- a/edalize/edatool.py +++ b/edalize/edatool.py @@ -4,7 +4,10 @@ import argparse from collections import OrderedDict +from importlib import import_module import os +from pkgutil import walk_packages + import subprocess import logging import sys @@ -21,6 +24,32 @@ except ImportError: _mswindows = False +NON_TOOL_PACKAGES = [ + "flows", + "tools", + "utils", + "vunit_hooks", + "reporting", + "ise_reporting", + "vivado_reporting", + "quartus_reporting", +] + + +def get_edatool(name): + return getattr(import_module(f"edalize.{name}"), name.capitalize()) + + +def walk_tool_packages(): + for _, pkg_name, _ in walk_packages([os.path.dirname(__file__)], "edalize."): + pkg_parts = pkg_name.split(".") + if not pkg_parts[1] in NON_TOOL_PACKAGES: + yield pkg_parts[1] + + +def get_edatools(): + return [get_edatool(pkg) for pkg in walk_tool_packages()] + def subprocess_run_3_9( *popenargs, input=None, capture_output=False, timeout=None, check=False, **kwargs @@ -121,7 +150,7 @@ def __init__(self, edam=None, work_root=None, eda_api=None, verbose=True): except KeyError: raise RuntimeError("Missing required parameter 'name'") - self.tool_options = edam.get("tool_options", {}).get(_tool_name, {}) + self.tool_options = edam.get("tool_options", {}).get(_tool_name, {}).copy() self.files = edam.get("files", []) self.toplevel = edam.get("toplevel", []) @@ -404,7 +433,6 @@ def _run_scripts(self, scripts, hook_name): script["cmd"], cwd=self.work_root, env=_env, - capture_output=not self.verbose, check=True, ) except FileNotFoundError as e: diff --git a/edalize/f4pga.py b/edalize/f4pga.py new file mode 100644 index 000000000..139a63d53 --- /dev/null +++ b/edalize/f4pga.py @@ -0,0 +1,54 @@ +# Copyright edalize contributors +# Licensed under the 2-Clause BSD License, see LICENSE for details. +# SPDX-License-Identifier: BSD-2-Clause + +from edalize.edatool import Edatool +from edalize.flows.f4pga import F4pga as F4pga_underlying + + +class F4pga(Edatool): + """Edalize front-end F4PGA interface""" + + @classmethod + def get_doc(cls, api_ver): + if api_ver == 0: + return { + "description": "F4PGA is an open-source flow for generating bitstreams from verilog/systemverilog code using Yosys and VPR/NextPNR. Currently only supports Xilinx 7-series boards with support for more coming in the future.", + "members": [ + { + "name": "device", + "type": "str", + "desc": "(Required) The device type identifier. Example: 'artix7'", + }, + { + "name": "part", + "type": "str", + "desc": "(Required) The FPGA part specifier. Example: 'xc7a35tcpg236-1'", + }, + { + "name": "chip", + "type": "str", + "desc": "(Required) The FPGA chip specifier used by F4PGA. Example: 'xc7a50t_test'", + }, + { + "name": "arch", + "type": "str", + "desc": "(Optional) The architecture specifier. Currently defaults to xilinx and is thus optional.", + }, + { + "name": "pnr", + "type": "str", + "desc": "(Optional) The name of the place and route tool to use. Defaults to VPR and is thus optional.", + }, + ], + } + + def __init__(self, edam=None, work_root=None, eda_api=None, verbose=True): + super().__init__(edam, work_root, eda_api, verbose) + self.f4pga = F4pga_underlying(edam, work_root, verbose) + + def configure_main(self): + self.f4pga.configure() + + def run_main(self): + pass diff --git a/edalize/filelist.py b/edalize/filelist.py new file mode 100644 index 000000000..2afb940a6 --- /dev/null +++ b/edalize/filelist.py @@ -0,0 +1,189 @@ +# Copyright edalize contributors +# Licensed under the 2-Clause BSD License, see LICENSE for details. +# SPDX-License-Identifier: BSD-2-Clause + +import os, sys, re +import logging + +from edalize.edatool import Edatool +from pathlib import Path + +logger = logging.getLogger(__name__) + + +class Filelist(Edatool): + + # Currently only +define+ is supported in the VC file. Some tools allow parameters to be passed but the syntax is tool specific. + argtypes = ["vlogdefine"] + + @classmethod + def get_doc(cls, api_ver): + if api_ver == 0: + return { + "description": "The filelist backend writes out a VC file to be used in tool flows not yet supported by Edalize", + "members": [ + { + "name": "outpath", + "type": "String", + "desc": "Specify the output path for the VC file, can be absolute or relative to parent core file", + }, + { + "name": "mode", + "type": "String", + "desc": "Select the output path style. Legal values are *absolute* or *relative*", + }, + ], + "lists": [ + { + "name": "libext", + "type": "String", + "desc": "Provide a list of library extensions", + } + ], + } + + def configure_main(self): + + core_file = Path(self.edam["top_core"]) + core_name = core_file.name + core_path = core_file.parent + + # If the user hasn't specified an output path, then default to the top core file location. + # REVISIT: should the default be to write the VC file to the working directory? + if not "outpath" in self.tool_options: + self.tool_options["outpath"] = core_path + else: + outpath = self.tool_options["outpath"] + + if outpath.startswith("/"): + # outpath is absolute + self.tool_options["outpath"] = Path(outpath) + elif outpath.startswith("$"): + # outpath is using environment variable + self.tool_options["outpath"] = Path(self._resolve_env_var(outpath)) + else: + # outpath is relative to core_file + self.tool_options["outpath"] = core_path / outpath + + logger.debug("outpath={}".format(self.tool_options["outpath"])) + + if self.tool_options["outpath"].suffix != "": + # If the user has sepcified a full path with a filename, then use that... + outfile = self.tool_options["outpath"] + else: + # ...else use the name of the core file. + outfile = self.tool_options["outpath"] / core_name.replace(".core", ".f") + + # Validate the filelist mode + # REVISIT: if we are writing to the working directory, what file paths should be used? + mode = self.tool_options.get("mode", "absolute") + if mode not in ["absolute", "relative"]: + raise RuntimeError('Illegal filelist mode "{}"'.format(mode)) + + # Write out the VC file + with outfile.open("w") as f: + for libext in self.tool_options.get("libext", []): + f.write("+libext+{}\n".format(libext)) + + (src_files, incdirs) = self._convert_paths( + *self._get_fileset_files(force_slash=True), mode + ) + + for key, value in self.vlogdefine.items(): + define_str = self._param_value_str(param_value=value) + f.write("+define+{}={}\n".format(key, define_str)) + + for id in incdirs: + f.write("+incdir+" + id + "\n") + + for src_file in src_files: + if src_file.file_type.startswith( + "systemVerilogSource" + ) or src_file.file_type.startswith("verilogSource"): + f.write(src_file.name + "\n") + + logger.info("wrote filelist to {}".format(outfile)) + + def build(self): + pass + + def run(self, args): + pass + + def _resolve_env_var(self, outpath): + """Resolves an environment variable in a path. + + The argument `outpath` should be an environment variable, $VAR, or a concatenated path of the + form $VAR/the/rest/of/the/path. + + Assumes that an environment variable will be of the form $VAR, $(VAR) or ${VAR}. The variable + itself will normally be of the form VAR or SOME_VAR but all alphanumeric names with underscores + will be detected. + + """ + + regex = re.compile( + r"""(\$ # + (?:[({])? # An optional opening brace or parnthesis + ([A-Za-z0-9_]+) # The environment variable + (?:[)}])? # An optional closing brace or parenthesis + (?:(/.+))? # The rest of the path if there is one + $)""", + re.VERBOSE, + ) + + if match := regex.match(outpath): + envvar = match.group(2) + therest = match.group(3) + try: + return os.environ[envvar] + (therest if therest else "") + except KeyError: + logger.error( + 'could not resolve environment variable in outpath="{}"'.format( + match.group(1) + ) + ) + raise RuntimeError() + else: + logger.error( + 'did not recognise environment variable in outpath="{}"'.format(outpath) + ) + raise RuntimeError() + + def _convert_paths(self, src_files, incdirs, mode): + """Converts the work_root relative file paths to absolute or core file relative. + + The `src_files` and `incdirs` lists returned from `_get_fileset_files` are relative paths to + the FuseSoC working directory, `work_root`. This isn't particularly usefull for a generic + VC file so we convert them to either absolute paths or relative to the parent core file. + + """ + _src_files = [] + _incdirs = [] + + for src_file in src_files: + src_file.name = self._resolve_path( + Path(self.work_root) / src_file.name, mode + ) + _src_files.append(src_file) + + for incdir in incdirs: + _incdirs.append(self._resolve_path(Path(self.work_root) / incdir, mode)) + + return (_src_files, _incdirs) + + def _resolve_path(self, path, mode): + """Generates correct file paths. + + Depending on the `mode` selected, we can generate absolute file paths or ones relative + to the output path where the .f file will be placed. + + """ + if mode == "absolute": + return str(path.resolve()) + elif mode == "relative": + return os.path.relpath( + path.resolve(), start=self.tool_options["outpath"].resolve() + ) + else: + raise RuntimeError("illegal mode {}".format(mode)) diff --git a/edalize/flows/edaflow.py b/edalize/flows/edaflow.py index c1db6e3bf..f53b135bc 100644 --- a/edalize/flows/edaflow.py +++ b/edalize/flows/edaflow.py @@ -73,6 +73,9 @@ def merge_dict(d1, d2): class Edaflow(object): + + FLOW = [] + @classmethod def get_flow_options(cls): flow_opts = cls.FLOW_OPTIONS.copy() @@ -90,6 +93,36 @@ def get_flow_options(cls): flow_opts[opt_name]["tool"] = tool_name return flow_opts + @classmethod + def get_tool_options(cls, flow_options): + return {} + + # Takes a list of tool names and a dict of pre-defined tool options + # Imports the tool class for every tool in the list, extracts their + # tool options and return then all, except for the ones listed in + # flow_defined_tool_options + @classmethod + def get_filtered_tool_options(cls, tools, flow_defined_tool_options): + tool_opts = {} + for tool_name in tools: + + # Get available tool options from each tool in the list + try: + class_tool_options = getattr( + import_module(f"edalize.tools.{tool_name}"), tool_name.capitalize() + ).get_tool_options() + except ModuleNotFoundError: + raise RuntimeError(f"Could not find tool '{tool_name}'") + # Add them to the dict unless they are already set by the flow + filtered_tool_options = flow_defined_tool_options.get(tool_name, {}) + for opt_name in class_tool_options: + # Filter out tool options that are already set by the flow + if not opt_name in filtered_tool_options: + tool_opts[opt_name] = class_tool_options[opt_name] + tool_opts[opt_name]["tool"] = tool_name + + return tool_opts + def extract_flow_options(self): # Extract flow options from the EDAM flow_options = {} @@ -106,7 +139,7 @@ def extract_flow_options(self): def extract_tool_options(self): tool_options = {} edam_flow_opts = self.edam.get("flow_options", {}) - for (tool_name, next_nodes, flow_defined_tool_options) in self.FLOW: + for (tool_name, next_nodes, flow_defined_tool_options) in self.flow: # Get the tool class ToolClass = getattr( @@ -122,7 +155,7 @@ def extract_tool_options(self): if opt_name in ToolClass.get_tool_options(): tool_options[tool_name] = merge_dict( tool_options[tool_name], - {opt_name: edam_flow_opts.pop(opt_name)}, + {opt_name: edam_flow_opts.get(opt_name)}, ) self.edam["tool_options"] = tool_options @@ -130,7 +163,7 @@ def extract_tool_options(self): def build_tool_graph(self): # Instantiate the tools nodes = {} - for (tool_name, next_nodes, flow_defined_tool_options) in self.FLOW: + for (tool_name, next_nodes, flow_defined_tool_options) in self.flow: # Instantiate the tool class tool_inst = getattr( @@ -186,7 +219,8 @@ def merge_edam(a, b): def add_scripts(self, depends, hook_name): last_script = depends - for script in self.hooks.get(hook_name, []): + hooks = self.edam.get("hooks", {}) + for script in hooks.get(hook_name, []): # _env = self.env.copy() # if 'env' in script: @@ -200,14 +234,19 @@ def add_scripts(self, depends, hook_name): last_script = script["name"] self.commands.add([], [hook_name], [last_script]) + def configure_flow(self, flow_options): + return self.FLOW + def __init__(self, edam, work_root, verbose=False): self.edam = edam - self.hooks = edam.get("hooks", {}) + self.commands = EdaCommands() # Extract all options that affects the flow rather than # just a single tool self.flow_options = self.extract_flow_options() + self.flow = self.configure_flow(self.flow_options) + # Rearrange tool_options so that each tool gets their # own tool_options self.extract_tool_options() @@ -216,7 +255,6 @@ def __init__(self, edam, work_root, verbose=False): self.stdout = None self.stderr = None - self.commands = EdaCommands() def set_run_command(self): self.commands.add([], ["run"], ["pre_run"]) diff --git a/edalize/flows/f4pga.py b/edalize/flows/f4pga.py new file mode 100644 index 000000000..aa8d9d3f5 --- /dev/null +++ b/edalize/flows/f4pga.py @@ -0,0 +1,241 @@ +# Copyright edalize contributors +# Licensed under the 2-Clause BSD License, see LICENSE for details. +# SPDX-License-Identifier: BSD-2-Clause + +import os.path + +from edalize.flows.edaflow import Edaflow + + +class F4pga(Edaflow): + """Edalize back-end implementation of F4PGA (Free and Open-Source Flow For FPGAs)""" + + """Currently supports Xilinx 7-series chips""" + + # F4PGA defined inputs to the underlying tools + FLOW_DEFINED_TOOL_OPTIONS = {} + + # Inputs to the F4PGA flow + FLOW_OPTIONS = { + # Required (used by Yosys) + "device": {"type": "str", "desc": "Example: 'artix7'"}, + # Required (used by Yosys) + "part": {"type": "str", "desc": "Example: 'xc7a35tcpg236-1'"}, + # Required (used by VPR) + "chip": {"type": "str", "desc": "Example: 'xc7a50t_test'"}, + # Optional, defaults to xilinx + "arch": { + "type": "str", + "desc": "Targeting architecture for Yosys. Currently supported is 'xilinx' and that is the default if none specified.", + }, + # Optional, defaults to VPR + "pnr": { + "type": "str", + "desc": "Place and route tool. Valid options are 'vpr'/'vtr' and 'nextpnr'. Defaults to VPR.", + }, + # Optional, if empty uses default F4PGA VPR options + "vpr_options": { + "type": "list", + "desc": "Options to VPR, if the standard F4PGA values are not sufficient", + }, + } + + # Standard F4PGA command line arguments to VPR + DEFAULT_VPR_OPTIONS = [ + "--max_router_iterations", + "500", + "--routing_failure_predictor", + "off", + "--router_high_fanout_threshold", + "-1", + "--constant_net_method", + "route", + "--route_chan_width", + "500", + "--router_heap", + "bucket", + "--clock_modeling", + "route", + "--place_delta_delay_matrix_calculation_method", + "dijkstra", + "--place_delay_model", + "delta", + "--router_lookahead", + "extended_map", + "--check_route", + "quick", + "--strict_checks", + "off", + "--allow_dangling_combinational_nodes", + "on", + "--disable_errors", + "check_unbuffered_edges:check_route", + "--congested_routing_iteration_threshold", + "0.8", + "--incremental_reroute_delay_ripup", + "off", + "--base_cost_type", + "delay_normalized_length_bounded", + "--bb_factor", + "10", + "--acc_fac", + "0.7", + "--astar_fac", + "1.8", + "--initial_pres_fac", + "2.828", + "--pres_fac_mult", + "1.2", + "--check_rr_graph", + "off", + "--suppress_warnings", + "sum_pin_class:check_unbuffered_edges:load_rr_indexed_data_T_values:check_rr_node:trans_per_R:check_route:set_rr_graph_tool_comment:calculate_average_switch", + ] + + # Creates the flow tree with Yosys and VPR or NextPNR nodes + def configure_flow(self, flow_options): + + # Set target + # toplevel = self.edam["toplevel"] + self.name = self.edam["name"] + self.commands.set_default_target(f"{self.name}.bit") + + # Set up nodes + synth_tool = "yosys" + self.pnr_tool = flow_options.get("pnr", "vpr") + if not self.pnr_tool in ["vpr", "nextpnr"]: + raise RuntimeError(f"F4PGA flow error: invalid P&R tool: {self.pnr_tool}") + + self.device = flow_options.get("device") + if not self.device: + raise RuntimeError("F4PGA flow error: missing 'device' specifier") + + self.part = flow_options.get("part") + if not self.part: + raise RuntimeError("F4PGA flow error: missing 'part' specifier") + + self.db_dir = "$(shell prjxray-config)/" + part_json = self.db_dir + f"{self.device}/{self.part}/part.json" + + chip = flow_options.get("chip") + if not chip: + raise RuntimeError("F4PGA flow error: missing 'chip' specifier") + + arch_dir = "${F4PGA_SHARE_DIR}/arch/" + self.arch_xml = arch_dir + f"{chip}/arch.timing.xml" + + # Set up node options + synth_options = {} + + # If arch is not configured, default to xilinx (will be changed in the future) + if "arch" in flow_options: + synth_options.update({"arch": flow_options.get("arch")}) + else: + synth_options.update({"arch": "xilinx"}) + + # If NextPNR, target JSON netlist, otherwise target EBLIF netlist for VPR + if self.pnr_tool == "nextpnr": + synth_options.update({"output_format": "json"}) + else: + synth_options.update({"output_format": "eblif"}) + + # If NextPNR, use built in Yosys template script, otherwise for VPR use custom F4PGA script + if self.pnr_tool != "nextpnr": + synth_options.update( + {"yosys_template": "$(shell python3 -m f4pga.wrappers.tcl)"} + ) + synth_options.update({"f4pga_synth_part_file": part_json}) + + pnr_options = {} + if self.pnr_tool == "vpr": + self.eblif_file = f"{self.name}.eblif" + pnr_options.update({"arch_xml": self.arch_xml}) + pnr_options.update( + { + "generate_constraints": [ + self.eblif_file, + f"{self.name}.net", + self.part, + chip, + self.arch_xml, + ] + } + ) + self.rr_graph_file = arch_dir + f"{chip}/rr_graph_{chip}.rr_graph.real.bin" + lookahead_file = arch_dir + f"{chip}/rr_graph_{chip}.lookahead.bin" + place_delay_file = arch_dir + f"{chip}/rr_graph_{chip}.place_delay.bin" + self.device_name = chip.replace("_", "-") + self.VPR_OPTIONS = flow_options.get("vpr_options", self.DEFAULT_VPR_OPTIONS) + pnr_options.update( + { + "vpr_options": [ + "--device", + self.device_name, + "--read_rr_graph", + self.rr_graph_file, + "--read_router_lookahead", + lookahead_file, + "--read_placement_delay_lookup", + place_delay_file, + ] + + self.VPR_OPTIONS + } + ) + elif self.pnr_tool == "nextpnr": + pnr_options.update({"arch": flow_options.get("arch", "xilinx")}) + + return [ + (synth_tool, [self.pnr_tool], synth_options), + (self.pnr_tool, [], pnr_options), + ] + + # Adds the FASM and bitstream generation + def configure_tools(self, nodes): + super().configure_tools(nodes) + + if self.pnr_tool != "nextpnr": + target = f"{self.name}.fasm" + depends = f"{self.name}.analysis" + command = ( + [ + "genfasm", + self.arch_xml, + self.eblif_file, + "--device", + self.device_name, + ] + + self.VPR_OPTIONS + + [ + "--read_rr_graph", + self.rr_graph_file, + ";", + "cat", + f"{self.name}_fasm_extra.fasm", + ">>", + f"{self.name}.fasm", + ";", + "mv", + "vpr_stdout.log", + "fasm.log", + ] + ) + self.commands.add(command, [target], [depends]) + + target = f"{self.name}.bit" + depends = f"{self.name}.fasm" + command = [ + "xcfasm", + "--db-root", + self.db_dir + self.device, + "--part", + self.part, + "--part_file", + self.db_dir + f"{self.device}/{self.part}/part.yaml", + "--sparse", + "--emit_pudc_b_pullup", + "--fn_in", + depends, + "--bit_out", + target, + ] + self.commands.add(command, [target], [depends]) diff --git a/edalize/flows/icestorm.py b/edalize/flows/icestorm.py index 21a363eea..84b0657e7 100644 --- a/edalize/flows/icestorm.py +++ b/edalize/flows/icestorm.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: BSD-2-Clause import os.path +from importlib import import_module from edalize.flows.edaflow import Edaflow @@ -12,13 +13,10 @@ class Icestorm(Edaflow): argtypes = ["vlogdefine", "vlogparam"] - FLOW = [ - ("yosys", ["nextpnr"], {"arch": "ice40", "output_format": "json"}), - ("nextpnr", ["icepack", "icetime"], {"arch": "ice40"}), - ("icepack", [], {}), - ("icetime", [], {}), - # ('icebox_stat', [], {}), - ] + FLOW_DEFINED_TOOL_OPTIONS = { + "yosys": {"arch": "ice40", "output_format": "json"}, + "nextpnr": {"arch": "ice40"}, + } FLOW_OPTIONS = { "frontends": { @@ -32,26 +30,43 @@ class Icestorm(Edaflow): }, } - def build_tool_graph(self): - # FIXME: This makes an assumption that the first tool in self.FLOW is - # a single entry point to the flow - next_tool = self.FLOW[0][0] + @classmethod + def get_flow_options(cls): + return cls.FLOW_OPTIONS.copy() - for frontend in reversed(self.flow_options.get("frontends", [])): - self.FLOW[0:0] = [(frontend, [next_tool], {})] - next_tool = frontend - return super().build_tool_graph() + @classmethod + def get_tool_options(cls, flow_options): + flow = flow_options.get("frontends", []) + flow += ["yosys"] + pnr = flow_options.get("pnr", "next") + if pnr == "next": + flow += ["nextpnr", "icepack", "icetime"] - def configure_tools(self, nodes): - super().configure_tools(nodes) + return cls.get_filtered_tool_options(flow, cls.FLOW_DEFINED_TOOL_OPTIONS) + + def extract_flow_options(self): + return { + k: v + for (k, v) in self.edam.get("flow_options", {}).items() + if k in self.get_flow_options() + } - # Set output from syntheis or pnr as default target depending on - # value of pnr flow option + def configure_flow(self, flow_options): name = self.edam["name"] - pnr = self.flow_options.get("pnr", "next") + # Check whether to run nextpnr or stop after synthesis + # and set output from syntheis or pnr as default target depending + # on value of pnr flow option + pnr = flow_options.get("pnr", "next") if pnr == "next": + flow = [ + ("yosys", ["nextpnr"], {"arch": "ice40", "output_format": "json"}), + ("nextpnr", ["icepack", "icetime"], {"arch": "ice40"}), + ("icepack", [], {}), + ("icetime", [], {}), + ] self.commands.set_default_target(name + ".bin") elif pnr == "none": + flow = [("yosys", [], {"arch": "ice40", "output_format": "json"})] self.commands.set_default_target(name + ".json") else: raise RuntimeError( @@ -60,6 +75,19 @@ def configure_tools(self, nodes): ) ) + # Add any user-specified frontends to the flow + next_tool = "yosys" + + for frontend in reversed(flow_options.get("frontends", [])): + flow[0:0] = [(frontend, [next_tool], {})] + next_tool = frontend + + return flow + + def configure_tools(self, nodes): + super().configure_tools(nodes) + + name = self.edam["name"] # Slot in statistics command which doesn't have an EdaTool class depends = name + ".asc" targets = name + ".stat" diff --git a/edalize/flows/lint.py b/edalize/flows/lint.py index 326170112..80fbaf6f9 100644 --- a/edalize/flows/lint.py +++ b/edalize/flows/lint.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: BSD-2-Clause import os.path +from importlib import import_module from edalize.flows.edaflow import Edaflow @@ -12,10 +13,10 @@ class Lint(Edaflow): argtypes = ["vlogdefine", "vlogparam"] - FLOW = [ - ("verilator", [], {"mode": "lint-only", "exe": "false", "make_options": []}), + FLOW_DEFINED_TOOL_OPTIONS = { + "verilator": {"mode": "lint-only", "exe": "false", "make_options": []}, # verible, spyglass, ascentlint, slang... - ] + } FLOW_OPTIONS = { "frontends": { @@ -29,23 +30,28 @@ class Lint(Edaflow): }, } - def build_tool_graph(self): + @classmethod + def get_tool_options(cls, flow_options): + flow = flow_options.get("frontends", []) + tool = flow_options.get("tool") + if not tool: + raise RuntimeError("Flow 'lint' requires flow option 'tool' to be set") + flow.append(tool) + + return cls.get_filtered_tool_options(flow, cls.FLOW_DEFINED_TOOL_OPTIONS) + + def configure_flow(self, flow_options): tool = self.flow_options.get("tool", "") if not tool: - raise RuntimeError( - "'lint' backend requires flow option 'tool' to be defined" - ) - tmp = [x for x in self.FLOW if x[0] == tool] - self.FLOW = tmp - - # Note: This makes an assumption that the first tool in self.FLOW is - # a single entry point to the flow - next_tool = self.FLOW[0][0] - - for frontend in reversed(self.flow_options.get("frontends", [])): - self.FLOW[0:0] = [(frontend, [next_tool], {})] + raise RuntimeError("Flow 'lint' requires flow option 'tool' to be set") + flow = [(tool, [], self.FLOW_DEFINED_TOOL_OPTIONS.get(tool, {}))] + # Add any user-specified frontends to the flow + next_tool = tool + + for frontend in reversed(flow_options.get("frontends", [])): + flow[0:0] = [(frontend, [next_tool], {})] next_tool = frontend - return super().build_tool_graph() + return flow def configure_tools(self, nodes): super().configure_tools(nodes) diff --git a/edalize/flows/sim.py b/edalize/flows/sim.py index 2fe4c9141..4484547dc 100644 --- a/edalize/flows/sim.py +++ b/edalize/flows/sim.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: BSD-2-Clause import os.path +from importlib import import_module from edalize.flows.edaflow import Edaflow @@ -12,10 +13,7 @@ class Sim(Edaflow): argtypes = ["plusarg", "vlogdefine", "vlogparam"] - FLOW = [ - ("verilator", [], {}), - # verible, spyglass, ascentlint, slang... - ] + FLOW_DEFINED_TOOL_OPTIONS = {} FLOW_OPTIONS = { "frontends": { @@ -29,28 +27,28 @@ class Sim(Edaflow): }, } - def build_tool_graph(self): - tool = self.flow_options.get("tool", "") - + @classmethod + def get_tool_options(cls, flow_options): + flow = flow_options.get("frontends", []) + tool = flow_options.get("tool") if not tool: raise RuntimeError("Flow 'sim' requires flow option 'tool' to be set") - known_tools = [x[0] for x in self.FLOW] - - self.FLOW = [x for x in self.FLOW if x[0] == tool] + flow.append(tool) - if not self.FLOW: - raise RuntimeError( - f"Unknown tool {tool!r}. Allowed options are {', '.join(known_tools)}" - ) + return cls.get_filtered_tool_options(flow, cls.FLOW_DEFINED_TOOL_OPTIONS) - # FIXME: This makes an assumption that the first tool in self.FLOW is - # a single entry point to the flow - next_tool = self.FLOW[0][0] + def configure_flow(self, flow_options): + tool = self.flow_options.get("tool", "") + if not tool: + raise RuntimeError("Flow 'sim' requires flow option 'tool' to be set") + flow = [(tool, [], {})] + # Add any user-specified frontends to the flow + next_tool = tool - for frontend in reversed(self.flow_options.get("frontends", [])): - self.FLOW[0:0] = [(frontend, [next_tool], {})] + for frontend in reversed(flow_options.get("frontends", [])): + flow[0:0] = [(frontend, [next_tool], {})] next_tool = frontend - return super().build_tool_graph() + return flow def configure_tools(self, nodes): super().configure_tools(nodes) diff --git a/edalize/flows/vivado.py b/edalize/flows/vivado.py index d75770f6a..d2e2655bb 100644 --- a/edalize/flows/vivado.py +++ b/edalize/flows/vivado.py @@ -12,30 +12,53 @@ class Vivado(Edaflow): argtypes = ["vlogdefine", "vlogparam"] - FLOW = [ - ("yosys", ["vivado"], {"arch": "xilinx", "output_format": "edif"}), - ("vivado", [], {}), - ] + FLOW_DEFINED_TOOL_OPTIONS = { + "yosys": {"arch": "xilinx", "output_format": "edif"}, + } FLOW_OPTIONS = { + "frontends": { + "type": "str", + "desc": "Tools to run before yosys (e.g. sv2v)", + "list": True, + }, "pnr": { - "type": "String", + "type": "str", "desc": "Select Place & Route tool.", }, "synth": { - "type": "String", + "type": "str", "desc": "Synthesis tool. Allowed values are vivado (default) and yosys.", }, } - def __init__(self, edam, work_root, verbose=False): - if edam.get("flow_options", {}).get("synth", {}) != "yosys": - # Remove from flow Yosys node - self.FLOW = [("vivado", [], {})] - Edaflow.__init__(self, edam, work_root, verbose) + @classmethod + def get_tool_options(cls, flow_options): + flow = flow_options.get("frontends", []) + + if flow_options.get("synth") == "yosys": + flow.append("yosys") + flow.append("vivado") + + return cls.get_filtered_tool_options(flow, cls.FLOW_DEFINED_TOOL_OPTIONS) + + def configure_flow(self, flow_options): + flow = [] + if flow_options.get("synth") == "yosys": + flow = [ + ("yosys", ["vivado"], self.FLOW_DEFINED_TOOL_OPTIONS["yosys"]), + ("vivado", [], {"synth": "yosys"}), + ] + next_tool = "yosys" + else: + flow = [("vivado", [], {})] + next_tool = "vivado" - def build_tool_graph(self): - return super().build_tool_graph() + # Add any user-specified frontends to the flow + for frontend in reversed(flow_options.get("frontends", [])): + flow[0:0] = [(frontend, [next_tool], {})] + next_tool = frontend + return flow def configure_tools(self, nodes): super().configure_tools(nodes) diff --git a/edalize/gatemate.py b/edalize/gatemate.py new file mode 100644 index 000000000..e4ae4d7a9 --- /dev/null +++ b/edalize/gatemate.py @@ -0,0 +1,112 @@ +# Copyright edalize contributors +# Licensed under the 2-Clause BSD License, see LICENSE for details. +# SPDX-License-Identifier: BSD-2-Clause + +import os.path +import re + +from edalize.edatool import Edatool +from edalize.utils import EdaCommands +from edalize.yosys import Yosys + + +class Gatemate(Edatool): + + argtypes = ["vlogdefine", "vlogparam"] + + @classmethod + def get_doc(cls, api_ver): + if api_ver == 0: + options = { + "lists": [ + { + "name": "p_r_options", + "type": "string", + "desc": "Additional option for p_r", + }, + ], + "members": [ + { + "name": "device", + "type": "String", + "desc": "Required device option for p_r command (e.g. CCGM1A1)", + }, + ], + } + + Edatool._extend_options(options, Yosys) + + return { + "description": "backend for CologneChip GateMate.", + "members": options["members"], + "lists": options["lists"], + } + + def configure_main(self): + (src_files, incdirs) = self._get_fileset_files() + synth_out = self.name + "_synth.v" + + device = self.tool_options.get("device") + if not device: + raise RuntimeError("Missing required option 'device' for p_r") + + match = re.search("^CCGM1A([1-9]{1,2})$", device) + if not match: + raise RuntimeError("{} is not known device name".format(device)) + + device_number = match.groups()[0] + if device_number not in ["1", "2", "4", "9", "16", "25"]: + raise RuntimeError("Rel. size {} is not unsupported".format(device_number)) + + ccf_file = None + + for f in src_files: + if f.file_type == "CCF": + if ccf_file: + raise RuntimeError( + "p_r only supports one ccf file. Found {} and {}".format( + ccf_file, f.name + ) + ) + else: + ccf_file = f.name + + # Pass trellis tool options to yosys + self.edam["tool_options"] = { + "yosys": { + "arch": "gatemate", + "output_format": "verilog", + "output_name": synth_out, + "yosys_synth_options": self.tool_options.get("yosys_synth_options", []), + "yosys_as_subtool": True, + "yosys_template": self.tool_options.get("yosys_template"), + }, + } + + yosys = Yosys(self.edam, self.work_root) + yosys.configure() + + # Write Makefile + commands = EdaCommands() + commands.commands = yosys.commands + + # PnR & image generation + targets = self.name + "_00.cfg.bit" + command = [ + "p_r", + "-A", + device_number, + "-i", + synth_out, + "-o", + self.name, + "-lib", + "ccag", + " ".join(self.tool_options.get("p_r_options", "")), + ] + if ccf_file is not None: + command += ["-ccf", ccf_file] + commands.add(command, [targets], [synth_out]) + + commands.set_default_target(targets) + commands.write(os.path.join(self.work_root, "Makefile")) diff --git a/edalize/icarus.py b/edalize/icarus.py index 4e62e4536..f0aacf993 100644 --- a/edalize/icarus.py +++ b/edalize/icarus.py @@ -13,10 +13,10 @@ all: $(VPI_MODULES) $(TARGET) $(TARGET): - iverilog -s$(TOPLEVEL) -c $(TARGET).scr -o $@ $(IVERILOG_OPTIONS) + iverilog $(TOPLEVEL) -c $(TARGET).scr -o $@ $(IVERILOG_OPTIONS) run: $(VPI_MODULES) $(TARGET) - vvp -n -M. -l icarus.log $(patsubst %.vpi,-m%,$(VPI_MODULES)) $(TARGET) -fst $(EXTRA_OPTIONS) + vvp -n -M. -l icarus.log $(patsubst %.vpi,-m%,$(VPI_MODULES)) $(VVP_OPTIONS) $(TARGET) -fst $(EXTRA_OPTIONS) clean: $(RM) $(VPI_MODULES) $(TARGET) @@ -53,6 +53,11 @@ def get_doc(cls, api_ver): "type": "String", "desc": "Additional options for iverilog", }, + { + "name": "vvp_options", + "type": "String", + "desc": "Additional options for vvp", + }, ], } @@ -64,11 +69,12 @@ def configure_main(self): f.write("+define+{}={}\n".format(key, self._param_value_str(value, ""))) for key, value in self.vlogparam.items(): - f.write( - "+parameter+{}.{}={}\n".format( - self.toplevel, key, self._param_value_str(value, '"') + for top in self.toplevel.split(" "): + f.write( + "+parameter+{}.{}={}\n".format( + top, key, self._param_value_str(value, '"') + ) ) - ) for id in incdirs: f.write("+incdir+" + id + "\n") timescale = self.tool_options.get("timescale") @@ -104,12 +110,21 @@ def configure_main(self): _vpi_modules = " ".join([m["name"] + ".vpi" for m in self.vpi_modules]) if _vpi_modules: f.write("VPI_MODULES := {}\n".format(_vpi_modules)) - f.write("TOPLEVEL := {}\n".format(self.toplevel)) + f.write( + "TOPLEVEL := {}\n".format( + " ".join(["-s" + x for x in self.toplevel.split()]) + ) + ) f.write( "IVERILOG_OPTIONS := {}\n".format( " ".join(self.tool_options.get("iverilog_options", [])) ) ) + f.write( + "VVP_OPTIONS := {}\n".format( + " ".join(self.tool_options.get("vvp_options", [])) + ) + ) if self.plusarg: plusargs = [] for key, value in self.plusarg.items(): diff --git a/edalize/ise.py b/edalize/ise.py index f5797cfdf..1a26cf73a 100644 --- a/edalize/ise.py +++ b/edalize/ise.py @@ -17,10 +17,10 @@ class Ise(Edatool): all: $(TOPLEVEL).bit $(TOPLEVEL).bit: $(NAME)_run.tcl $(NAME).xise - xtclsh $^ + $(EDALIZE_LAUNCHER) xtclsh $^ $(NAME).xise: $(NAME).tcl - xtclsh $< + $(EDALIZE_LAUNCHER) xtclsh $< """ TCL_RUN_FILE_TEMPLATE = """#Auto generated by Edalize @@ -42,7 +42,7 @@ class Ise(Edatool): }} project_new_exist_ok {design} -project set family {family} +project set family "{family}" project set device {device} project set package {package} project set speed {speed} @@ -86,12 +86,12 @@ def get_doc(cls, api_ver): }, { "name": "speed", - "type": "String", + "type": "Integer", "desc": "FPGA speed grade (e.g. -2)", }, { "name": "board_device_index", - "type": "String", + "type": "Integer", "desc": "Specifies the FPGA's device number in the JTAG chain, starting at 1", }, ], @@ -151,7 +151,9 @@ def _write_tcl_file(self): ) ) - (src_files, incdirs) = self._get_fileset_files() + (src_files, incdirs) = self._get_fileset_files( + force_slash=True + ) # ISE tcl doesn't like '\', so '/' is forced if incdirs: tcl_file.write( diff --git a/edalize/modelsim.py b/edalize/modelsim.py index 551228dd7..f9e7010ac 100644 --- a/edalize/modelsim.py +++ b/edalize/modelsim.py @@ -44,7 +44,7 @@ all: work $(VPI_MODULES) run: work $(VPI_MODULES) - $(VSIM) -do "run -all; quit -code [expr [coverage attribute -name TESTSTATUS -concise] >= 2 ? [coverage attribute -name TESTSTATUS -concise] : 0]; exit" -c $(addprefix -pli ,$(VPI_MODULES)) $(EXTRA_OPTIONS) $(TOPLEVEL) + $(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) @@ -79,6 +79,13 @@ def get_doc(cls, api_ver): if api_ver == 0: return { "description": "ModelSim simulator from Mentor Graphics", + "members": [ + { + "name": "compilation_mode", + "type": "String", + "desc": "Common or separate compilation, sep - for separate compilation, common - for common compilation", + } + ], "lists": [ { "name": "vcom_options", @@ -105,6 +112,10 @@ def _write_build_rtl_tcl_file(self, tcl_main): vlog_include_dirs = ["+incdir+" + d.replace("\\", "/") for d in incdirs] libs = [] + + vlog_files = [] + + common_compilation = self.tool_options.get("compilation_mode") == "common" for f in src_files: if not f.logical_name: f.logical_name = "work" @@ -114,6 +125,7 @@ def _write_build_rtl_tcl_file(self, tcl_main): if f.file_type.startswith("verilogSource") or f.file_type.startswith( "systemVerilogSource" ): + vlog_files.append(f) cmd = "vlog" args = [] @@ -147,11 +159,30 @@ def _write_build_rtl_tcl_file(self, tcl_main): _s = "{} has unknown file type '{}'" logger.warning(_s.format(f.name, f.file_type)) cmd = None - if cmd: + 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_makefile(self): vpi_make = open(os.path.join(self.work_root, "Makefile"), "w") diff --git a/edalize/openlane.py b/edalize/openlane.py index d107338ad..edb9e80e7 100644 --- a/edalize/openlane.py +++ b/edalize/openlane.py @@ -30,6 +30,8 @@ def configure_main(self): for f in src_files: if f.file_type.startswith("verilogSource"): files.append(f.name) + elif f.file_type.startswith("systemVerilogSource"): + files.append(f.name) elif f.file_type == "tclSource": tcl.append(f.name) diff --git a/edalize/quartus.py b/edalize/quartus.py index c2ac8a907..d20a5073b 100644 --- a/edalize/quartus.py +++ b/edalize/quartus.py @@ -49,7 +49,7 @@ def get_doc(cls, api_ver): }, { "name": "board_device_index", - "type": "String", + "type": "Integer", "desc": "Specifies the FPGA's device number in the JTAG chain. The device index specifies the device where the flash programmer looks for the Nios® II JTAG debug module. JTAG devices are numbered relative to the JTAG chain, starting at 1. Use the tool `jtagconfig` to determine the index.", }, { diff --git a/edalize/questaformal.py b/edalize/questaformal.py new file mode 100644 index 000000000..93ca87e76 --- /dev/null +++ b/edalize/questaformal.py @@ -0,0 +1,205 @@ +# 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.edatool import Edatool + +logger = logging.getLogger(__name__) + +MAKE_HEADER = """#Generated by Edalize + +QVERIFY ?= qverify + +QVERIFY_OPTIONS ?= {qverify_options} + +all: work + +run: work + $(QVERIFY) $(QVERIFY_OPTIONS) -do "do edalize_autocheck.tcl; exit" + +run-gui: work + $(QVERIFY) -gui + +work: + $(QVERIFY) -c -do "do edalize_main.tcl; exit" + +clean: {clean_targets} +""" + +VPI_MAKE_SECTION = """ +{name}_OBJS := {objs} +{name}_LIBS := {libs} +{name}_INCS := $(INCS) {incs} + +$({name}_OBJS): CPPFLAGS := $({name}_INCS) + +{name}: $({name}_OBJS) + $(LD) $(LDFLAGS) -o $@ $? $({name}_LIBS) + +clean_{name}: + $(RM) $({name}_OBJS) {name} +""" + + +class Questaformal(Edatool): + + argtypes = ["plusarg", "vlogdefine", "vlogparam", "generic"] + + @classmethod + def get_doc(cls, api_ver): + if api_ver == 0: + return { + "description": "Questa Formal from Mentor Graphics", + "lists": [ + { + "name": "vcom_options", + "type": "String", + "desc": "Additional options for compilation with vcom", + }, + { + "name": "vlog_options", + "type": "String", + "desc": "Additional options for compilation with vlog", + }, + { + "name": "qverify_options", + "type": "String", + "desc": "Additional run options for qverify", + }, + { + "name": "autocheck_options", + "type": "String", + "desc": "Options for Autochecker", + }, + ], + } + + def _write_build_rtl_tcl_file(self, tcl_main): + tcl_build_rtl = open(os.path.join(self.work_root, "edalize_build_rtl.tcl"), "w") + + (src_files, incdirs) = self._get_fileset_files() + vlog_include_dirs = ["+incdir+" + d.replace("\\", "/") for d in incdirs] + + libs = [] + for f in src_files: + if not f.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 = "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: + args += ["-quiet"] + args += ["-work", f.logical_name] + args += [f.name.replace("\\", "/")] + tcl_build_rtl.write("{} {}\n".format(cmd, " ".join(args))) + + def _write_makefile(self): + vpi_make = open(os.path.join(self.work_root, "Makefile"), "w") + _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))] + + _qverify_options = self.tool_options.get("qverify_options", []) + + _modules = [m["name"] for m in self.vpi_modules] + _clean_targets = " ".join(["clean_" + m for m in _modules]) + _s = MAKE_HEADER.format( + toplevel=self.toplevel, + parameters=" ".join(_parameters), + plusargs=" ".join(_plusargs), + qverify_options=" ".join(_qverify_options), + modules=" ".join(_modules), + clean_targets=_clean_targets, + ) + vpi_make.write(_s) + + for vpi_module in self.vpi_modules: + _name = vpi_module["name"] + _objs = [os.path.splitext(s)[0] + ".o" for s in vpi_module["src_files"]] + _libs = ["-l" + l for l in vpi_module["libs"]] + _incs = ["-I" + d for d in vpi_module["include_dirs"]] + _s = VPI_MAKE_SECTION.format( + name=_name, + objs=" ".join(_objs), + libs=" ".join(_libs), + incs=" ".join(_incs), + ) + vpi_make.write(_s) + + vpi_make.close() + + def configure_main(self): + 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") + + tcl_autocheck = open(os.path.join(self.work_root, "edalize_autocheck.tcl"), "w") + _autocheck_options = self.tool_options.get( + "autocheck_options", + ["enable", "compile -d {}".format(self.toplevel), "verify -timeout 10m"], + ) + for ac_option in _autocheck_options: + tcl_autocheck.write("autocheck {}\n".format(ac_option)) + tcl_autocheck.close() + + self._write_build_rtl_tcl_file(tcl_main) + self._write_makefile() + tcl_main.close() + + def run_main(self): + args = ["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)) + + self._run_tool("make", args) diff --git a/edalize/slang.py b/edalize/slang.py new file mode 100644 index 000000000..ec943c490 --- /dev/null +++ b/edalize/slang.py @@ -0,0 +1,145 @@ +# Copyright edalize contributors +# Licensed under the 2-Clause BSD License, see LICENSE for details. +# SPDX-License-Identifier: BSD-2-Clause + +import os +import re +import logging + +from edalize.edatool import Edatool + +logger = logging.getLogger(__name__) + + +class Slang(Edatool): + + _description = """Slang System Verilog Frontend +slang is a software library that provides various +components for lexing, parsing, type checking, and elaborating SystemVerilog code. + +Example snippet of a CAPI2 description file for Slang: + +.. code:: yaml + + slang: + mode: + - lint # this will lint all the files + - preprocess # this will preprocess all the files and output a single file + slang_options: + user defined options + + """ + + tool_options = {"lists": {"mode": "String", "slang_options": "String"}} + + argtypes = ["vlogdefine", "vlogparam"] + + flags = [] + + def __init(self, edam=None, work_root=None, eda_api=None): + # call the super method here + super(Slang, self).__init__(edam, work_root, eda_api) + + self.rtl_paths = None + self.incdirs = None + + # path for final rtl generation + self.gen_rtl_name = None + + # contain the command line arguments + self.flags = [] + + return 0 + + @staticmethod + def get_doc(api_ver): + if api_ver == 0: + return { + "description": "slang is a software library that provides various components for lexing, parsing, type checking, and elaborating SystemVerilog code.", + "lists": [ + { + "name": "mode", + "type": "String", + "desc": ( + "choose slang to run in either lint mode or preprocess mode" + ), + }, + { + "name": "slang_options", + "type": "String", + "desc": ("extra options for slang"), + }, + ], + } + + # we only need to get the list of files + def _get_file_names(self): + """ + get all the file names + """ + src_files, self.incdirs = self._get_fileset_files() + + for dir in self.incdirs: + self.flags.append("-I") + self.flags.append("{}".format(dir)) + + ft_re = re.compile(r"(:?systemV|v)erilogSource") + for file_obj in src_files: + if ft_re.match(file_obj.file_type): + self.flags.append(file_obj.name) + + def _get_run_mode_flags(self): + """ + get the current running mode: whether run to preprocess or lint + """ + run_mode = self.tool_options.get("mode", "") + if run_mode == "lint": + self.flags += ["--lint-only"] + elif run_mode == "preprocess": + self.flags += ["--preprocess"] + + def _get_define_flags(self) -> str: + """ + understand flags necessary for various defines + """ + for key, value in self.vlogdefine.items(): + self.flags.append("-D {}={}".format(key, self._param_value_str(value))) + + def _get_param_flags(self): + """ + get flags for parameters + """ + for key, value in self.vlogparam.items(): + self.flags.append("-G {}={}".format(key, self._param_value_str(value))) + + def _get_slang_options(self): + """ + get extra options from user + """ + slang_options = self.tool_options.get("slang_options", "") + self.flags += " ".join(slang_options).split() + + def _get_top_flags(self): + """ + generate flags for top level module + """ + if self.toplevel != "": + self.flags.append("--top") + self.flags.append("{}".format(self.toplevel)) + + def build_main(self): + self._get_define_flags() + self._get_param_flags() + self._get_file_names() + self._get_slang_options() + self._get_run_mode_flags() + self._get_top_flags() + self._run_tool("slang", self.flags, quiet=True) + return + + def configure_main(self): + self.flags = [] + return + + def run_main(self): + return diff --git a/edalize/templates/vcs/Makefile.j2 b/edalize/templates/vcs/Makefile.j2 index df13ddc33..08a6047b6 100644 --- a/edalize/templates/vcs/Makefile.j2 +++ b/edalize/templates/vcs/Makefile.j2 @@ -1,7 +1,7 @@ all: {{ name }} {{ name }}: {{ name }}.scr - vcs -full64 -top {{ toplevel }} -f {{ name }}.scr -o $@ {% for option in vcs_options %} {{ option }}{% endfor %} + $(EDALIZE_LAUNCHER) vcs -full64 -top {{ toplevel }} -f {{ name }}.scr -o $@ {% for option in vcs_options %} {{ option }}{% endfor %} run: {{ name }} ./{{ name }} -l vcs.log {% for plusarg in plusargs %} {{ plusarg }} {% endfor %}{% for option in run_options %} {{ option }}{% endfor %} diff --git a/edalize/templates/yosys/yosys-script-tcl.j2 b/edalize/templates/yosys/yosys-script-tcl.j2 index d681f90f7..db77f0f16 100644 --- a/edalize/templates/yosys/yosys-script-tcl.j2 +++ b/edalize/templates/yosys/yosys-script-tcl.j2 @@ -13,4 +13,4 @@ verilog_defaults -pop synth $top -{{ write_command }} {{ output_opts -}} $name.{{ output_format }} +{{ write_command }} {{ output_opts -}} {{ output_name }} diff --git a/edalize/tools/icarus.py b/edalize/tools/icarus.py new file mode 100644 index 000000000..bcf79e0f5 --- /dev/null +++ b/edalize/tools/icarus.py @@ -0,0 +1,139 @@ +# Copyright edalize contributors +# Licensed under the 2-Clause BSD License, see LICENSE for details. +# SPDX-License-Identifier: BSD-2-Clause + +import os + +from edalize.tools.edatool import Edatool +from edalize.utils import EdaCommands + +MAKEFILE_TEMPLATE = """ +all: $(VPI_MODULES) $(TARGET) + +run: $(VPI_MODULES) $(TARGET) + vvp -n -M. -l icarus.log $(patsubst %.vpi,-m%,$(VPI_MODULES)) $(TARGET) -fst $(EXTRA_OPTIONS) + +clean: + $(RM) $(VPI_MODULES) $(TARGET) +""" + +VPI_MAKE_SECTION = """ +{name}_LIBS := {libs} +{name}_INCS := {incs} +{name}_SRCS := {srcs} + +{name}.vpi: $({name}_SRCS) + iverilog-vpi --name={name} $({name}_LIBS) $({name}_INCS) $? + +clean_{name}: + $(RM) {name}.vpi +""" + + +class Icarus(Edatool): + + description = "Icarus Verilog is a Verilog simulation and synthesis tool. It operates as a compiler, compiling source code written in Verilog (IEEE-1364) into some target format" + + TOOL_OPTIONS = { + "timescale": { + "type": "str", + "desc": "Default timescale", + }, + "iverilog_options": { + "type": "str", + "desc": "Additional options for iverilog", + }, + } + + def configure(self, edam): + super().configure(edam) + + incdirs = [] + vlog_files = [] + depfiles = [] + unused_files = [] + + with open(os.path.join(self.work_root, self.name + ".scr"), "w") as scr_file: + + for key, value in self.vlogdefine.items(): + scr_file.write( + "+define+{}={}\n".format(key, self._param_value_str(value, "")) + ) + + for key, value in self.vlogparam.items(): + scr_file.write( + "+parameter+{}.{}={}\n".format( + self.toplevel, key, self._param_value_str(value, '"') + ) + ) + + for id in incdirs: + scr_file.write("+incdir+" + id + "\n") + + timescale = self.tool_options.get("timescale") + if timescale: + with open(os.path.join(self.work_root, "timescale.v"), "w") as tsfile: + tsfile.write("`timescale {}\n".format(timescale)) + scr_file.write("timescale.v\n") + + for f in self.files: + file_type = f.get("file_type", "") + depfile = True + if file_type.startswith("systemVerilogSource") or file_type.startswith( + "verilogSource" + ): + if not self._add_include_dir(f, incdirs): + vlog_files.append(f["name"]) + else: + unused_files.append(f) + depfile = False + + if depfile: + depfiles.append(f["name"]) + + for include_dir in incdirs: + scr_file.write(f"+incdir+{include_dir}\n") + + for vlog_file in vlog_files: + scr_file.write(f"{vlog_file}\n") + + self.edam = edam.copy() + self.edam["files"] = unused_files + + commands = EdaCommands() + commands.add( + [ + "iverilog", + f"-s{self.toplevel}", + "-c", + f"{self.name}.scr", + "-o", + self.name, + ] + + self.tool_options.get("iverilog_options", []), + [self.name], + depfiles + [f"{self.name}.scr"], + ) + + # How should the run target be handled? + # Add VPI support + commands.add( + ["vvp", "-n", "-M.", self.name, "-fst", "$(EXTRA_OPTIONS)"], + ["run"], + [self.name], + ) + + self.default_target = self.name + self.commands = commands.commands + + def run(self, args): + args = ["run"] + + # Set plusargs + if self.plusarg: + plusargs = [] + for key, value in self.plusarg.items(): + plusargs += ["+{}={}".format(key, self._param_value_str(value))] + args.append("EXTRA_OPTIONS=" + " ".join(plusargs)) + + return ("make", args, self.work_root) diff --git a/edalize/tools/nextpnr.py b/edalize/tools/nextpnr.py index d1007c324..5f78c76bd 100644 --- a/edalize/tools/nextpnr.py +++ b/edalize/tools/nextpnr.py @@ -27,6 +27,8 @@ def configure(self, edam): lpf_file = "" pcf_file = "" netlist = "" + chipdb_file = "" + placement_constraints = [] unused_files = [] for f in self.files: file_type = f.get("file_type", "") @@ -54,6 +56,16 @@ def configure(self, edam): ) ) pcf_file = f["name"] + if file_type == "chipdb": + if chipdb_file: + raise RuntimeError( + "Nextpnr only supports one ChipDB (bin/bba) file. Found {} and {}".format( + chipdb_file, f["name"] + ) + ) + chipdb_file = f["name"] + if file_type == "xdc": + placement_constraints.append(f["name"]) elif file_type == "jsonNetlist": if netlist: raise RuntimeError( @@ -77,31 +89,54 @@ def configure(self, edam): arch = self.tool_options["arch"] arch_options = [] - if arch == "ecp5": - targets = self.name + ".config" - constraints = ["--lpf", lpf_file] if lpf_file else [] - output = ["--textcfg", targets] - elif arch == "gowin": - device = self.tool_options.get("device") - if not device: - raise RuntimeError("Missing required option 'device' for nextpnr-gowin") - arch_options += ["--device", device] - targets = self.name + ".pack" - constraints = ["--cst", cst_file] if cst_file else [] - output = ["--write", targets] + + # Specific commands for nextpnr-xilinx + if arch == "xilinx": + depends = netlist + if not chipdb_file: + raise RuntimeError("Missing required chipdb (bba/bin) file") + if not placement_constraints: + raise RuntimeError("Missing required XDC file(s)") + targets = self.name + ".fasm" + command = ["nextpnr-" + arch, "--chipdb", chipdb_file] + xdcs = [] + for x in placement_constraints: + xdcs += ["--xdc", x] + command += xdcs + command += ["--json", depends] + command += ["--write", self.name + ".routed.json"] + command += ["--fasm", targets] + command += ["--log", "nextpnr.log"] + command += self.tool_options.get("nextpnr_options", []) + commands.add(command, [targets], [depends]) else: - targets = self.name + ".asc" - constraints = ["--pcf", pcf_file] if pcf_file else [] - output = ["--asc", targets] + if arch == "ecp5": + targets = self.name + ".config" + constraints = ["--lpf", lpf_file] if lpf_file else [] + output = ["--textcfg", targets] + elif arch == "gowin": + device = self.tool_options.get("device") + if not device: + raise RuntimeError( + "Missing required option 'device' for nextpnr-gowin" + ) + arch_options += ["--device", device] + targets = self.name + ".pack" + constraints = ["--cst", cst_file] if cst_file else [] + output = ["--write", targets] + else: + targets = self.name + ".asc" + constraints = ["--pcf", pcf_file] if pcf_file else [] + output = ["--asc", targets] - depends = netlist - command = ["nextpnr-" + arch, "-l", "next.log"] - command += arch_options + self.tool_options.get("nextpnr_options", []) - command += constraints + ["--json", depends] + output + depends = netlist + command = ["nextpnr-" + arch, "-l", "next.log"] + command += arch_options + self.tool_options.get("nextpnr_options", []) + command += constraints + ["--json", depends] + output - # CLI target - commands.add(command, [targets], [depends]) + # CLI target + commands.add(command, [targets], [depends]) - # GUI target - commands.add(command + ["--gui"], ["build-gui"], [depends]) + # GUI target + commands.add(command + ["--gui"], ["build-gui"], [depends]) self.commands = commands.commands diff --git a/edalize/tools/verilator.py b/edalize/tools/verilator.py index ef41b0382..6e3c282d4 100644 --- a/edalize/tools/verilator.py +++ b/edalize/tools/verilator.py @@ -37,8 +37,7 @@ def configure(self, edam): super().configure(edam) # Future improvement: Separate include directories of c and verilog files - incdirs = set() - src_files = [] + incdirs = [] verilator_file = self.name + ".vc" @@ -57,10 +56,6 @@ def configure(self, edam): vc += self.tool_options.get("verilator_options", []) - for include_dir in incdirs: - vc.append(f"+incdir+" + include_dir) - vc.append("-CFLAGS -I" + include_dir) - vlt_files = [] vlog_files = [] opt_c_files = [] @@ -97,6 +92,10 @@ def configure(self, edam): if uhdm_files: vc.append("--uhdm-ast-sv") + for include_dir in incdirs: + vc.append(f"+incdir+" + include_dir) + vc.append("-CFLAGS -I" + include_dir) + vc += vlt_files + uhdm_files + vlog_files vc.append(f"--top-module {self.toplevel}\n") diff --git a/edalize/tools/vivado.py b/edalize/tools/vivado.py index 640013cc1..94576fc38 100644 --- a/edalize/tools/vivado.py +++ b/edalize/tools/vivado.py @@ -29,39 +29,39 @@ class Vivado(Edatool): TOOL_OPTIONS = { "part": { - "type": "String", + "type": "str", "desc": "FPGA part number (e.g. xc7a35tcsg324-1)", }, "synth": { - "type": "String", + "type": "str", "desc": "Synthesis tool. Allowed values are vivado or none.", }, "board_part": { - "type": "String", + "type": "str", "desc": "Board part number (e.g. xilinx.com:kc705:part0:0.9)", }, "board_repo_paths": { - "type": "String", + "type": "str", "desc": "Board repository paths. A list of paths to search for board files.", }, "pnr": { - "type": "String", + "type": "str", "desc": "P&R tool. Allowed values are vivado (default) and none (to just run synthesis)", }, "jobs": { - "type": "Integer", + "type": "int", "desc": "Number of jobs. Useful for parallelizing OOC (Out Of Context) syntheses.", }, "jtag_freq": { - "type": "Integer", + "type": "int", "desc": "The frequency for jtag communication", }, "source_mgmt_mode": { - "type": "String", + "type": "str", "desc": "Source managment mode. Allowed values are None (unmanaged, default), DisplayOnly (automatically update sources) and All (automatically update sources and compile order)", }, "hw_target": { - "type": "Description", + "type": "str", "desc": "A pattern matching a board identifier. Refer to the Vivado documentation for ``get_hw_targets`` for details. Example: ``*/xilinx_tcf/Digilent/123456789123A``", }, } @@ -138,7 +138,7 @@ def configure(self, edam): bd_files.append(f["name"]) if cmd: - if not self._add_include_dir(f, incdirs): + if not self._add_include_dir(f, incdirs, True): src_files.append(cmd + " {" + f["name"] + "}") else: unused_files.append(f) diff --git a/edalize/tools/vpr.py b/edalize/tools/vpr.py index df5e8526f..0c0dc9f58 100644 --- a/edalize/tools/vpr.py +++ b/edalize/tools/vpr.py @@ -29,6 +29,10 @@ class Vpr(Edatool): "type": "str", "desc": "Path to target architecture in XML format", }, + "generate_constraints": { + "type": "list", + "desc": "A list of values used to generate constraints at the place stage (used by F4PGA flow)", + }, "vpr_options": { "type": "str", "desc": "Additional options for VPR.", @@ -67,16 +71,21 @@ def configure(self, edam): """ super().configure(edam) - src_files = [] - incdirs = set() - - file_netlist = [] + netlist_file = "" timing_constraints = [] - for f in src_files: - if f.file_type in ["blif", "eblif"]: - file_netlist.append(f.name) - if f.file_type in ["SDC"]: + for f in self.files: + file_type = f.get("file_type", "") + + if file_type in ["blif", "eblif"]: + if netlist_file: + raise RuntimeError( + "VPR only supports one netlist file. Found {} and {}".format( + netlist_file, f["name"] + ) + ) + netlist_file = f["name"] + if file_type in ["SDC"]: timing_constraints.append(f.name) arch_xml = self.tool_options.get("arch_xml") @@ -90,28 +99,69 @@ def configure(self, edam): commands = EdaCommands() - depends = self.name + ".blif" + # First, check if gen_constraint value list is passed in and is the correct size + gen_constr_list = self.tool_options.get("generate_constraints", []) + + depends = netlist_file targets = self.name + ".net" - command = ["vpr", arch_xml, self.name + ".blif", "--pack"] - command += sdc_opts + vpr_options + command = ["vpr", arch_xml, netlist_file, "--pack"] + command += ( + sdc_opts + vpr_options + [";", "mv", "vpr_stdout.log", "pack.log"] + if gen_constr_list + else [] + ) commands.add(command, [targets], [depends]) - depends = self.name + ".net" + # Run generate constraints script if correct list exists + constraints_file = "constraints.place" + if gen_constr_list: + depends = self.name + ".net" + targets = constraints_file + commands.add( + [ + "python3", + "-m f4pga.wrappers.sh.generate_constraints", + " ".join(gen_constr_list), + ], + [targets], + [depends], + ) + + depends = [self.name + ".net"] targets = self.name + ".place" - command = ["vpr", arch_xml, self.name + ".blif", "--place"] - command += sdc_opts + vpr_options - commands.add(command, [targets], [depends]) + command = ["vpr", arch_xml, netlist_file] + + # Modify place stage if running generate constraints script + if gen_constr_list: + depends += [constraints_file] + command += [f"--fix_clusters {constraints_file}"] + + command += ["--place"] + command += ( + sdc_opts + vpr_options + [";", "mv", "vpr_stdout.log", "place.log"] + if gen_constr_list + else [] + ) + commands.add(command, [targets], depends) depends = self.name + ".place" targets = self.name + ".route" - command = ["vpr", arch_xml, self.name + ".blif", "--route"] - command += sdc_opts + vpr_options + command = ["vpr", arch_xml, netlist_file, "--route"] + command += ( + sdc_opts + vpr_options + [";", "mv", "vpr_stdout.log", "route.log"] + if gen_constr_list + else [] + ) commands.add(command, [targets], [depends]) depends = self.name + ".route" targets = self.name + ".analysis" - command = ["vpr", arch_xml, self.name + ".blif", "--analysis"] - command += sdc_opts + vpr_options + command = ["vpr", arch_xml, netlist_file, "--analysis"] + command += ( + sdc_opts + vpr_options + [";", "mv", "vpr_stdout.log", "analysis.log"] + if gen_constr_list + else [] + ) commands.add(command, [targets], [depends]) for ext in [".net", ".place", ".route", ".analysis"]: diff --git a/edalize/tools/yosys.py b/edalize/tools/yosys.py index 6f709d89b..aa52c8643 100644 --- a/edalize/tools/yosys.py +++ b/edalize/tools/yosys.py @@ -28,6 +28,10 @@ class Yosys(Edatool): "type": "str", "desc": "TCL template file to use instead of default template", }, + "f4pga_synth_part_file": { + "type": "str", + "desc": "The JSON part file used for Yosys synthesis", + }, "yosys_synth_options": { "type": "str", "desc": "Additional options for the synth command", @@ -121,9 +125,10 @@ def write_config_files(self, edam): "name": self.name, } - self.render_template( - "edalize_yosys_procs.tcl.j2", "edalize_yosys_procs.tcl", template_vars - ) + if not yosys_template: + self.render_template( + "edalize_yosys_procs.tcl.j2", "edalize_yosys_procs.tcl", template_vars + ) if not yosys_template: self.render_template( @@ -131,9 +136,53 @@ def write_config_files(self, edam): ) commands = EdaCommands() - commands.add( - ["yosys", "-l", "yosys.log", "-p", f"'tcl {template}'"], - [default_target], - [template] + depfiles, - ) + + # First, check if split_io list is passed in and is the correct size + f4pga_synth_part_file = "" + if "f4pga_synth_part_file" in self.tool_options: + f4pga_synth_part_file = self.tool_options.get("f4pga_synth_part_file") + + # Configure first call to Yosys + targets = [] + depends = depfiles + variables = [] + logfile = "" + + targets = [default_target] + if f4pga_synth_part_file: + in_xdc = "" + for f in self.files: + if f.get("file_type") == "xdc": + in_xdc = f["name"] + if not in_xdc: + print( + "F4PGA flow warning: no Xilinx Design Constraint file (.xdc) specified" + ) + variables = { + "USE_ROI": "FALSE", + "TECHMAP_PATH": "${F4PGA_SHARE_DIR}/techmaps/xc7_vpr/techmap", + "TOP": self.toplevel, + "INPUT_XDC_FILES": in_xdc, + "PART_JSON": f4pga_synth_part_file, + "OUT_FASM_EXTRA": f"{self.name}_fasm_extra.fasm", + "OUT_SDC": f"{self.name}.sdc", + "OUT_SYNTH_V": f"{self.name}_synth.v", + "PYTHON3": "$(shell which python3)", + "UTILS_PATH": "${F4PGA_SHARE_DIR}/scripts", + "OUT_JSON": f"{self.name}.json", + "SYNTH_JSON": f"{self.name}_io.json", + "OUT_EBLIF": default_target, + } + logfile = f"{self.name}_synth.log" + else: + depends = [template] + depends + logfile = "yosys.log" + + command = ["yosys", f"-l {logfile}", f"-p 'tcl {template}'"] + + if f4pga_synth_part_file: + command += depfiles + + commands.add(command, targets, depends, variables=variables) + self.commands = commands.commands diff --git a/edalize/utils.py b/edalize/utils.py index 86f0fb8fd..b8bc69527 100644 --- a/edalize/utils.py +++ b/edalize/utils.py @@ -1,22 +1,39 @@ class EdaCommands(object): class Command(object): - def __init__(self, command, targets, depends, order_only_deps=[]): + def __init__(self, command, targets, depends, order_only_deps=[], variables={}): self.command = command self.targets = targets self.depends = depends self.order_only_deps = order_only_deps + self.variables = variables def __init__(self): self.commands = [] self.vars = [] self.header = "#Auto generated by Edalize\n\n" - def add(self, command, targets, depends, order_only_deps=[]): - self.commands.append(self.Command(command, targets, depends, order_only_deps)) + def add(self, command, targets, depends, order_only_deps=[], variables={}): + self.commands.append( + self.Command(command, targets, depends, order_only_deps, variables) + ) def add_var(self, var): self.vars.append(var) + # Allow for portability between the main platforms + def find_env_var_command(self): + from sys import platform + + if platform == "linux" or platform == "linux2" or platform == "darwin": + return "export" + elif platform == "win32": + return "set" + return "" + + # Simplify the creation of flow environmental variables in the Makefile + def add_env_var(self, key, value): + self.vars.append(f"{self.find_env_var_command()} {key}={value}") + def set_default_target(self, target): self.default_target = target @@ -43,5 +60,13 @@ def write(self, outfile): f.write("\n") + env_prefix = "" + if c.variables: + env_prefix += "env " + for key, value in c.variables.items(): + env_prefix += f"{key}={value} " + if c.command: - f.write(f"\t$(EDALIZE_LAUNCHER) {' '.join(c.command)}\n") + f.write( + f"\t$(EDALIZE_LAUNCHER) {env_prefix}{' '.join(c.command)}\n" + ) diff --git a/edalize/vcs.py b/edalize/vcs.py index e4506a462..427060a4e 100644 --- a/edalize/vcs.py +++ b/edalize/vcs.py @@ -70,7 +70,10 @@ def _vcs_filelist_filter(src_file): plusargs = [] if self.plusarg: for key, value in self.plusarg.items(): - plusargs += ["+{}={}".format(key, self._param_value_str(value))] + plusarg = "+" + key + if value != True: + plusarg += "=" + self._param_value_str(value) + plusargs.append(plusarg) vcs_options = self.tool_options.get("vcs_options", []) diff --git a/edalize/verilator.py b/edalize/verilator.py index 44c8fd10c..e1dafe038 100644 --- a/edalize/verilator.py +++ b/edalize/verilator.py @@ -3,7 +3,6 @@ # SPDX-License-Identifier: BSD-2-Clause import logging -import multiprocessing import os import logging @@ -206,11 +205,7 @@ def build_main(self): logger.info("Building simulation model") if not "mode" in self.tool_options: self.tool_options["mode"] = "cc" - - # Do parallel builds with - make_job_count = multiprocessing.cpu_count() - args = ["-j", str(make_job_count)] - + args = [] if self.tool_options["mode"] == "lint-only": args.append("V" + self.toplevel + ".mk") self._run_tool("make", args, quiet=True) diff --git a/edalize/vivado.py b/edalize/vivado.py index 583d69800..49be7533e 100644 --- a/edalize/vivado.py +++ b/edalize/vivado.py @@ -97,6 +97,32 @@ def __init__(self, edam=None, work_root=None, eda_api=None, verbose=True): def configure_main(self): self.vivado.configure() + def build_main(self): + logger.info("Building") + args = [] + if "pnr" in self.tool_options: + if self.tool_options["pnr"] == "vivado": + pass + elif self.tool_options["pnr"] == "none": + args.append("synth") + self._run_tool("make", args) + + def run_main(self): + """ + Program the FPGA. + + For programming the FPGA a vivado tcl script is written that searches for the + correct FPGA board and then downloads the bitstream. The tcl script is then + executed in Vivado's batch mode. + """ + if "pnr" in self.tool_options: + if self.tool_options["pnr"] == "vivado": + pass + elif self.tool_options["pnr"] == "none": + return + + self._run_tool("make", ["pgm"]) + def build_pre(self): pass diff --git a/edalize/yosys.py b/edalize/yosys.py index 318bfd986..3a02c8e3f 100644 --- a/edalize/yosys.py +++ b/edalize/yosys.py @@ -29,7 +29,12 @@ def get_doc(cls, api_ver): { "name": "output_format", "type": "String", - "desc": "Output file format. Legal values are *json*, *edif*, *blif*", + "desc": "Output file format. Legal values are *json*, *edif*, *blif*, *verilog*", + }, + { + "name": "output_name", + "type": "String", + "desc": "Output file name. [Optional]", }, { "name": "yosys_as_subtool", @@ -83,7 +88,9 @@ def configure_main(self): self.edam["files"] = unused_files output_format = self.tool_options.get("output_format", "blif") - default_target = f"{self.name}.{output_format}" + default_target = self.tool_options.get( + "output_name", f"{self.name}.{output_format}" + ) self.edam["files"].append( { @@ -122,8 +129,10 @@ def configure_main(self): "synth_command": "synth_" + arch, "synth_options": " ".join(self.tool_options.get("yosys_synth_options", "")), "write_command": "write_" + output_format, - "output_format": output_format, - "output_opts": "-pvector bra " if arch == "xilinx" else "", + "output_name": default_target, + "output_opts": "-pvector bra " + if (arch == "xilinx" and output_format == "edif") + else "", "yosys_template": template, "name": self.name, } @@ -146,5 +155,5 @@ def configure_main(self): if self.tool_options.get("yosys_as_subtool"): self.commands = commands.commands else: - commands.set_default_target(f"{self.name}.{output_format}") + commands.set_default_target(default_target) commands.write(os.path.join(self.work_root, "Makefile")) diff --git a/scripts/el_docker b/scripts/el_docker index a4294b453..97c00fba6 100755 --- a/scripts/el_docker +++ b/scripts/el_docker @@ -13,7 +13,7 @@ symbiflow_init = "bash -lec {}" containers = [ {'tool': 'ghdl', 'image': 'ghdl/ghdl:buster-llvm-7', 'init': '', 'vendor': '', 'part': ''}, {'tool': 'verilator', 'image': 'verilator/verilator', 'init': '', 'vendor': '', 'part': ''}, - {'tool': 'flow.tcl', 'image': 'edalize/openlane-sky130:v0.12', 'init': '', 'vendor': '', 'part': ''}, + {'tool': 'flow.tcl', 'image': 'edalize/openlane-sky130:mpw4', 'init': '', 'vendor': '', 'part': ''}, {'tool': 'mill', 'image': 'adoptopenjdk:8u282-b08-jre-hotspot', 'init': '', 'vendor': '', 'part': ''}, {'tool': 'sbt', 'image': 'adoptopenjdk:8u282-b08-jre-hotspot', 'init': '', 'vendor': '', 'part': ''}, {'tool': 'yosys', 'image': 'hdlc/yosys', 'init': '', 'vendor': '', 'part': ''}, @@ -110,17 +110,27 @@ if [c for c in containers if c["tool"] == toolname]: logger.error("ERROR: Tool {} not found in container list.".format(toolname)) exit(1) - prefix = [ - "docker", - "run", - "--rm", - "-v", - build_root + ":/src", - # '-e', dockerEnv, - "-w", - "/src/" + work, - image, - ] + _extra = os.environ.get("EDALIZE_EXTRA_DOCKER_FLAGS") + extra = _extra.split(" ") if _extra else [] + + prefix = ( + [ + "docker", + "run", + "--rm", + "-v", + build_root + ":/src", + # '-e', dockerEnv, + ] + + extra + + [ + "-u", + f"{os.getuid()}:{os.getgid()}", + "-w", + "/src/" + work, + image, + ] + ) if init: c = init.format(shlex.quote(runtool + " " + shlex.join(sys.argv[2:]))) elif tool == "verilator": diff --git a/setup.py b/setup.py index 0648163cd..33edac67e 100755 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ def read(fname): setup( name="edalize", - version="0.3.3", + version="0.4.1", packages=["edalize", "edalize.tools", "edalize.flows"], package_data={ "edalize": [ @@ -52,7 +52,7 @@ def read(fname): author="Olof Kindgren", author_email="olof.kindgren@gmail.com", description=( - "Edalize is a library for interfacing EDA tools, primarily for FPGA development" + "Library for interfacing EDA tools such as simulators, linters or synthesis tools, using a common interface" ), license="BSD-2-Clause", keywords=[ @@ -70,16 +70,13 @@ def read(fname): url="https://github.com/olofk/edalize", long_description=read("README.rst"), classifiers=[ - "Development Status :: 4 - Beta", + "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: BSD License", "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", "Topic :: Utilities", ], install_requires=[ - # 2.11.0 and .1 introduced an incompatible change in template output, - # which was fixed in 2.11.2 and later. - # https://github.com/pallets/jinja/issues/1138 - "Jinja2>=2.11.3", + "Jinja2>=3", ], tests_require=["pytest>=3.3.0", "vunit_hdl>=4.0.8"], # The reporting modules have dependencies that shouldn't be required for diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..a086dcdb0 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +import pytest + +pytest.register_assert_rewrite("tests.edalize_common") diff --git a/tests/mock_commands/p_r b/tests/mock_commands/p_r new file mode 100755 index 000000000..caaf4ede4 --- /dev/null +++ b/tests/mock_commands/p_r @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +import os +import sys + +output_file = sys.argv[sys.argv.index("-o") + 1] +with open(output_file, "a"): + # set the access and modified times to the current time + os.utime(output_file, None) + +with open("p_r.cmd", "w") as f: + f.write(" ".join(sys.argv[1:]) + "\n") diff --git a/tests/mock_commands/qverify b/tests/mock_commands/qverify new file mode 100755 index 000000000..17e63e534 --- /dev/null +++ b/tests/mock_commands/qverify @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +import os +import sys + +with open("qverify.cmd", "w") as f: + f.write(" ".join(sys.argv[1:]) + "\n") diff --git a/tests/mock_commands/slang b/tests/mock_commands/slang new file mode 100755 index 000000000..c31b14341 --- /dev/null +++ b/tests/mock_commands/slang @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +import os +import sys + +with open("slang.cmd", "w") as f: + f.write(" ".join(sys.argv[1:]) + "\n") diff --git a/tests/test_apicula.py b/tests/test_apicula.py index 3fccb49a0..36d00ea36 100644 --- a/tests/test_apicula.py +++ b/tests/test_apicula.py @@ -1,6 +1,6 @@ import os import pytest -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def run_apicula_test(tf): diff --git a/tests/test_apicula/edalize_yosys_template.tcl b/tests/test_apicula/edalize_yosys_template.tcl index c4bfc96ef..3b33afe2f 100644 --- a/tests/test_apicula/edalize_yosys_template.tcl +++ b/tests/test_apicula/edalize_yosys_template.tcl @@ -13,4 +13,4 @@ verilog_defaults -pop synth $top -write_json $name.json +write_json test_apicula_0.json diff --git a/tests/test_apicula/minimal/edalize_yosys_template.tcl b/tests/test_apicula/minimal/edalize_yosys_template.tcl index c4bfc96ef..3b33afe2f 100644 --- a/tests/test_apicula/minimal/edalize_yosys_template.tcl +++ b/tests/test_apicula/minimal/edalize_yosys_template.tcl @@ -13,4 +13,4 @@ verilog_defaults -pop synth $top -write_json $name.json +write_json test_apicula_0.json diff --git a/tests/test_ascentlint.py b/tests/test_ascentlint.py index 075ecadae..1a8d7d4fc 100644 --- a/tests/test_ascentlint.py +++ b/tests/test_ascentlint.py @@ -1,4 +1,4 @@ -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def test_ascentlint_defaults(make_edalize_test): diff --git a/tests/test_diamond.py b/tests/test_diamond.py index 38df84d0d..4b6d45f3e 100644 --- a/tests/test_diamond.py +++ b/tests/test_diamond.py @@ -1,4 +1,4 @@ -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def test_diamond(make_edalize_test): @@ -26,9 +26,8 @@ def test_diamond_minimal(tmpdir): from edalize import get_edatool - from edalize_common import compare_files, tests_dir + from .edalize_common import compare_files, tests_dir - ref_dir = os.path.join(tests_dir, __name__, "minimal") os.environ["PATH"] = ( os.path.join(tests_dir, "mock_commands") + ":" + os.environ["PATH"] ) @@ -44,6 +43,7 @@ def test_diamond_minimal(tmpdir): backend = get_edatool(tool)(edam=edam, work_root=work_root) backend.configure() + ref_dir = os.path.join(tests_dir, "test_" + tool, "minimal") compare_files( ref_dir, work_root, diff --git a/tests/test_edam.py b/tests/test_edam.py index 9f1c3d6be..dd1ab046a 100644 --- a/tests/test_edam.py +++ b/tests/test_edam.py @@ -113,7 +113,7 @@ def test_edam_hook_failing(tmpdir): backend = get_edatool("icarus")(edam=edam, work_root=work_root) exc_str_exp = ( r"pre_build script 'exit_1_script': " - r"\['sh', '.+/exit_1_script'\] exited with error code 1" + r"\['sh', '.+/exit_1_script'\] exited with error code " ) with pytest.raises(RuntimeError, match=exc_str_exp): backend.build_pre() diff --git a/tests/test_gatemate.py b/tests/test_gatemate.py new file mode 100644 index 000000000..052127360 --- /dev/null +++ b/tests/test_gatemate.py @@ -0,0 +1,97 @@ +import os +import pytest +from .edalize_common import make_edalize_test + + +def run_gatemate_test(tf): + tf.backend.configure() + + tf.compare_files( + ["Makefile", "edalize_yosys_procs.tcl", "edalize_yosys_template.tcl"] + ) + + tf.backend.build() + tf.compare_files(["yosys.cmd", "p_r.cmd"]) + + +def test_gatemate(make_edalize_test): + tool_options = { + "device": "CCGM1A1", + "yosys_synth_options": ["some", "yosys_synth_options"], + "p_r_options": ["some", "p_r_synth_options"], + } + tf = make_edalize_test( + "gatemate", param_types=["vlogdefine", "vlogparam"], tool_options=tool_options + ) + + run_gatemate_test(tf) + + +def test_gatemate_minimal(make_edalize_test): + tool_options = { + "device": "CCGM1A1", + } + tf = make_edalize_test( + "gatemate", + param_types=[], + files=[], + tool_options=tool_options, + ref_dir="minimal", + ) + + run_gatemate_test(tf) + + +def test_gatemate_multiple_ccf(make_edalize_test): + files = [ + {"name": "ccf_file.ccf", "file_type": "CCF"}, + {"name": "ccf_file2.ccf", "file_type": "CCF"}, + ] + tool_options = { + "device": "CCGM1A1", + } + tf = make_edalize_test( + "gatemate", + param_types=[], + files=files, + tool_options=tool_options, + ) + + with pytest.raises(RuntimeError) as e: + tf.backend.configure() + assert ( + "p_r only supports one ccf file. Found ccf_file.ccf and ccf_file2.ccf" + in str(e.value) + ) + + +def test_gatemate_no_device(make_edalize_test): + tf = make_edalize_test("gatemate", param_types=[]) + + with pytest.raises(RuntimeError) as e: + tf.backend.configure() + assert "Missing required option 'device' for p_r" in str(e.value) + + +def test_gatemate_wrong_device(make_edalize_test): + tool_options = { + "device": "CCGM2A1", + } + + tf = make_edalize_test("gatemate", param_types=[], tool_options=tool_options) + + with pytest.raises(RuntimeError) as e: + tf.backend.configure() + assert "CCGM2A1 is not known device name" in str(e.value) + + +def test_gatemate_wrong_device_size(make_edalize_test): + tool_options = { + "device": "CCGM1A13", + } + + tf = make_edalize_test("gatemate", param_types=[], tool_options=tool_options) + + with pytest.raises(RuntimeError) as e: + tf.backend.configure() + assert "Rel. size 13 is not unsupported" in str(e.value) diff --git a/tests/test_gatemate/Makefile b/tests/test_gatemate/Makefile new file mode 100644 index 000000000..88879b87d --- /dev/null +++ b/tests/test_gatemate/Makefile @@ -0,0 +1,9 @@ +#Auto generated by Edalize + +all: test_gatemate_0_00.cfg.bit + +test_gatemate_0_synth.v: edalize_yosys_template.tcl + $(EDALIZE_LAUNCHER) yosys -l yosys.log -p 'tcl edalize_yosys_template.tcl' + +test_gatemate_0_00.cfg.bit: test_gatemate_0_synth.v + $(EDALIZE_LAUNCHER) p_r -A 1 -i test_gatemate_0_synth.v -o test_gatemate_0 -lib ccag some p_r_synth_options diff --git a/tests/test_gatemate/edalize_yosys_procs.tcl b/tests/test_gatemate/edalize_yosys_procs.tcl new file mode 100644 index 000000000..d35f81edd --- /dev/null +++ b/tests/test_gatemate/edalize_yosys_procs.tcl @@ -0,0 +1,31 @@ +proc read_files {} { +read_verilog -sv {sv_file.sv} +source {tcl_file.tcl} +read_verilog {vlog_file.v} +read_verilog {vlog05_file.v} +read_verilog -sv {another_sv_file.sv} +} + +proc set_defines {} { +set defines {{vlogdefine_bool True} {vlogdefine_int 42} {vlogdefine_str hello}} + +foreach d ${defines} { + set key [lindex $d 0] + set val [lindex $d 1] + verilog_defines "-D$key=$val" +}} + +proc set_incdirs {} { +verilog_defaults -add -I.} + +proc set_params {} { +chparam -set vlogparam_bool 1 top_module +chparam -set vlogparam_int 42 top_module +chparam -set vlogparam_str {"hello"} top_module} + +proc synth {top} { +synth_gatemate some yosys_synth_options -top $top +} + +set top top_module +set name test_gatemate_0 diff --git a/tests/test_gatemate/edalize_yosys_template.tcl b/tests/test_gatemate/edalize_yosys_template.tcl new file mode 100644 index 000000000..ed1e2490f --- /dev/null +++ b/tests/test_gatemate/edalize_yosys_template.tcl @@ -0,0 +1,16 @@ +yosys -import +source edalize_yosys_procs.tcl + +verilog_defaults -push +verilog_defaults -add -defer + +set_defines +set_incdirs +read_files +set_params + +verilog_defaults -pop + +synth $top + +write_verilog test_gatemate_0_synth.v diff --git a/tests/test_gatemate/minimal/Makefile b/tests/test_gatemate/minimal/Makefile new file mode 100644 index 000000000..5d3a48af9 --- /dev/null +++ b/tests/test_gatemate/minimal/Makefile @@ -0,0 +1,9 @@ +#Auto generated by Edalize + +all: test_gatemate_0_00.cfg.bit + +test_gatemate_0_synth.v: edalize_yosys_template.tcl + $(EDALIZE_LAUNCHER) yosys -l yosys.log -p 'tcl edalize_yosys_template.tcl' + +test_gatemate_0_00.cfg.bit: test_gatemate_0_synth.v + $(EDALIZE_LAUNCHER) p_r -A 1 -i test_gatemate_0_synth.v -o test_gatemate_0 -lib ccag diff --git a/tests/test_gatemate/minimal/edalize_yosys_procs.tcl b/tests/test_gatemate/minimal/edalize_yosys_procs.tcl new file mode 100644 index 000000000..e0f228894 --- /dev/null +++ b/tests/test_gatemate/minimal/edalize_yosys_procs.tcl @@ -0,0 +1,25 @@ +proc read_files {} { + +} + +proc set_defines {} { +set defines {} + +foreach d ${defines} { + set key [lindex $d 0] + set val [lindex $d 1] + verilog_defines "-D$key=$val" +}} + +proc set_incdirs {} { +} + +proc set_params {} { +} + +proc synth {top} { +synth_gatemate -top $top +} + +set top top_module +set name test_gatemate_0 diff --git a/tests/test_gatemate/minimal/edalize_yosys_template.tcl b/tests/test_gatemate/minimal/edalize_yosys_template.tcl new file mode 100644 index 000000000..ed1e2490f --- /dev/null +++ b/tests/test_gatemate/minimal/edalize_yosys_template.tcl @@ -0,0 +1,16 @@ +yosys -import +source edalize_yosys_procs.tcl + +verilog_defaults -push +verilog_defaults -add -defer + +set_defines +set_incdirs +read_files +set_params + +verilog_defaults -pop + +synth $top + +write_verilog test_gatemate_0_synth.v diff --git a/tests/test_gatemate/minimal/p_r.cmd b/tests/test_gatemate/minimal/p_r.cmd new file mode 100644 index 000000000..4cf518337 --- /dev/null +++ b/tests/test_gatemate/minimal/p_r.cmd @@ -0,0 +1 @@ +-A 1 -i test_gatemate_0_synth.v -o test_gatemate_0 -lib ccag diff --git a/tests/test_gatemate/minimal/yosys.cmd b/tests/test_gatemate/minimal/yosys.cmd new file mode 100644 index 000000000..fd0ea46ec --- /dev/null +++ b/tests/test_gatemate/minimal/yosys.cmd @@ -0,0 +1 @@ +-l yosys.log -p tcl edalize_yosys_template.tcl diff --git a/tests/test_gatemate/p_r.cmd b/tests/test_gatemate/p_r.cmd new file mode 100644 index 000000000..82ed9f91b --- /dev/null +++ b/tests/test_gatemate/p_r.cmd @@ -0,0 +1 @@ +-A 1 -i test_gatemate_0_synth.v -o test_gatemate_0 -lib ccag some p_r_synth_options diff --git a/tests/test_gatemate/yosys.cmd b/tests/test_gatemate/yosys.cmd new file mode 100644 index 000000000..fd0ea46ec --- /dev/null +++ b/tests/test_gatemate/yosys.cmd @@ -0,0 +1 @@ +-l yosys.log -p tcl edalize_yosys_template.tcl diff --git a/tests/test_ghdl.py b/tests/test_ghdl.py index 491456b65..aaff4173c 100644 --- a/tests/test_ghdl.py +++ b/tests/test_ghdl.py @@ -1,5 +1,5 @@ import os -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def test_ghdl_01(make_edalize_test): diff --git a/tests/test_icarus.py b/tests/test_icarus.py index f85d068cc..f4184c366 100644 --- a/tests/test_icarus.py +++ b/tests/test_icarus.py @@ -1,4 +1,4 @@ -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def test_icarus(make_edalize_test): @@ -28,9 +28,8 @@ def test_icarus_minimal(tmpdir): from edalize import get_edatool - from edalize_common import compare_files, tests_dir + from .edalize_common import compare_files, tests_dir - ref_dir = os.path.join(tests_dir, __name__, "minimal") os.environ["PATH"] = ( os.path.join(tests_dir, "mock_commands") + ":" + os.environ["PATH"] ) @@ -43,6 +42,7 @@ def test_icarus_minimal(tmpdir): backend = get_edatool(tool)(edam=edam, work_root=work_root) backend.configure() + ref_dir = os.path.join(tests_dir, "test_" + tool, "minimal") compare_files( ref_dir, work_root, diff --git a/tests/test_icarus/Makefile b/tests/test_icarus/Makefile index 5b80cd27d..3e08e8fee 100644 --- a/tests/test_icarus/Makefile +++ b/tests/test_icarus/Makefile @@ -1,16 +1,17 @@ TARGET := test_icarus_0 VPI_MODULES := vpi1.vpi vpi2.vpi -TOPLEVEL := top_module +TOPLEVEL := -stop_module IVERILOG_OPTIONS := some iverilog_options +VVP_OPTIONS := EXTRA_OPTIONS ?= +plusarg_bool=1 +plusarg_int=42 +plusarg_str=hello all: $(VPI_MODULES) $(TARGET) $(TARGET): - iverilog -s$(TOPLEVEL) -c $(TARGET).scr -o $@ $(IVERILOG_OPTIONS) + iverilog $(TOPLEVEL) -c $(TARGET).scr -o $@ $(IVERILOG_OPTIONS) run: $(VPI_MODULES) $(TARGET) - vvp -n -M. -l icarus.log $(patsubst %.vpi,-m%,$(VPI_MODULES)) $(TARGET) -fst $(EXTRA_OPTIONS) + vvp -n -M. -l icarus.log $(patsubst %.vpi,-m%,$(VPI_MODULES)) $(VVP_OPTIONS) $(TARGET) -fst $(EXTRA_OPTIONS) clean: $(RM) $(VPI_MODULES) $(TARGET) diff --git a/tests/test_icarus/minimal/Makefile b/tests/test_icarus/minimal/Makefile index 907ebfd76..af84cad5e 100644 --- a/tests/test_icarus/minimal/Makefile +++ b/tests/test_icarus/minimal/Makefile @@ -1,14 +1,15 @@ TARGET := test_icarus_minimal_0 -TOPLEVEL := top +TOPLEVEL := -stop IVERILOG_OPTIONS := +VVP_OPTIONS := all: $(VPI_MODULES) $(TARGET) $(TARGET): - iverilog -s$(TOPLEVEL) -c $(TARGET).scr -o $@ $(IVERILOG_OPTIONS) + iverilog $(TOPLEVEL) -c $(TARGET).scr -o $@ $(IVERILOG_OPTIONS) run: $(VPI_MODULES) $(TARGET) - vvp -n -M. -l icarus.log $(patsubst %.vpi,-m%,$(VPI_MODULES)) $(TARGET) -fst $(EXTRA_OPTIONS) + vvp -n -M. -l icarus.log $(patsubst %.vpi,-m%,$(VPI_MODULES)) $(VVP_OPTIONS) $(TARGET) -fst $(EXTRA_OPTIONS) clean: $(RM) $(VPI_MODULES) $(TARGET) diff --git a/tests/test_icestorm.py b/tests/test_icestorm.py index 1ef4ee1c9..68beb8543 100644 --- a/tests/test_icestorm.py +++ b/tests/test_icestorm.py @@ -1,6 +1,6 @@ import os import pytest -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def run_icestorm_test(tf, pnr_cmdfile="nextpnr-ice40.cmd"): @@ -87,16 +87,15 @@ def test_icestorm_nextpnr(make_edalize_test): def test_icestorm_invalid_pnr(make_edalize_test): name = "test_icestorm_0" - tf = make_edalize_test( - "icestorm", - test_name=name, - param_types=["vlogdefine", "vlogparam"], - tool_options={"pnr": "invalid"}, - ref_dir="nextpnr", - ) with pytest.raises(RuntimeError) as e: - tf.backend.configure() + tf = make_edalize_test( + "icestorm", + test_name=name, + param_types=["vlogdefine", "vlogparam"], + tool_options={"pnr": "invalid"}, + ref_dir="nextpnr", + ) assert ( "Invalid pnr option 'invalid'. Valid values are 'next' for nextpnr or 'none' to only perform synthesis" in str(e.value) diff --git a/tests/test_icestorm/minimal/edalize_yosys_procs.tcl b/tests/test_icestorm/minimal/edalize_yosys_procs.tcl index 2157a1981..1a296cb7f 100644 --- a/tests/test_icestorm/minimal/edalize_yosys_procs.tcl +++ b/tests/test_icestorm/minimal/edalize_yosys_procs.tcl @@ -21,7 +21,7 @@ proc set_params {} { } proc synth {top} { -synth_ice40 some yosys_synth_options -top $top +synth_ice40 -top $top } set top top_module diff --git a/tests/test_icestorm/nextpnr/edalize_yosys_procs.tcl b/tests/test_icestorm/nextpnr/edalize_yosys_procs.tcl index 44466a90b..867440b57 100644 --- a/tests/test_icestorm/nextpnr/edalize_yosys_procs.tcl +++ b/tests/test_icestorm/nextpnr/edalize_yosys_procs.tcl @@ -27,7 +27,7 @@ chparam -set vlogparam_int 42 top_module chparam -set vlogparam_str {"hello"} top_module} proc synth {top} { -synth_ice40 some yosys_synth_options some yosys_synth_options -top $top +synth_ice40 some yosys_synth_options -top $top } set top top_module diff --git a/tests/test_ise.py b/tests/test_ise.py index 8e25f1600..b673fec4e 100644 --- a/tests/test_ise.py +++ b/tests/test_ise.py @@ -1,4 +1,4 @@ -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test import pytest diff --git a/tests/test_ise/Makefile b/tests/test_ise/Makefile index fd79fd8e4..d06bc3af7 100644 --- a/tests/test_ise/Makefile +++ b/tests/test_ise/Makefile @@ -4,7 +4,7 @@ include config.mk all: $(TOPLEVEL).bit $(TOPLEVEL).bit: $(NAME)_run.tcl $(NAME).xise - xtclsh $^ + $(EDALIZE_LAUNCHER) xtclsh $^ $(NAME).xise: $(NAME).tcl - xtclsh $< + $(EDALIZE_LAUNCHER) xtclsh $< diff --git a/tests/test_ise/test_ise_0.tcl b/tests/test_ise/test_ise_0.tcl index c5038dea1..2566dcb18 100644 --- a/tests/test_ise/test_ise_0.tcl +++ b/tests/test_ise/test_ise_0.tcl @@ -12,7 +12,7 @@ proc xfile_add_exist_ok name { } project_new_exist_ok test_ise_0 -project set family spartan6 +project set family "spartan6" project set device xc6slx45 project set package csg324 project set speed -2 diff --git a/tests/test_isim.py b/tests/test_isim.py index 0889f943c..00c6ed2ff 100644 --- a/tests/test_isim.py +++ b/tests/test_isim.py @@ -1,4 +1,4 @@ -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def test_isim(make_edalize_test): diff --git a/tests/test_libero.py b/tests/test_libero.py index ea593273b..0fc5a871f 100644 --- a/tests/test_libero.py +++ b/tests/test_libero.py @@ -1,4 +1,4 @@ -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def test_libero(make_edalize_test): diff --git a/tests/test_mistral.py b/tests/test_mistral.py index 89f56aefe..081adfdf7 100644 --- a/tests/test_mistral.py +++ b/tests/test_mistral.py @@ -2,7 +2,7 @@ import pytest -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def run_mistral_test(tf): diff --git a/tests/test_mistral/edalize_yosys_template.tcl b/tests/test_mistral/edalize_yosys_template.tcl index c4bfc96ef..9ab150474 100644 --- a/tests/test_mistral/edalize_yosys_template.tcl +++ b/tests/test_mistral/edalize_yosys_template.tcl @@ -13,4 +13,4 @@ verilog_defaults -pop synth $top -write_json $name.json +write_json test_mistral_0.json diff --git a/tests/test_mistral/minimal/edalize_yosys_template.tcl b/tests/test_mistral/minimal/edalize_yosys_template.tcl index c4bfc96ef..9ab150474 100644 --- a/tests/test_mistral/minimal/edalize_yosys_template.tcl +++ b/tests/test_mistral/minimal/edalize_yosys_template.tcl @@ -13,4 +13,4 @@ verilog_defaults -pop synth $top -write_json $name.json +write_json test_mistral_0.json diff --git a/tests/test_modelsim.py b/tests/test_modelsim.py index 63c03f3cd..767106f6a 100644 --- a/tests/test_modelsim.py +++ b/tests/test_modelsim.py @@ -1,6 +1,6 @@ import filecmp import os -from edalize_common import make_edalize_test, tests_dir +from .edalize_common import make_edalize_test, tests_dir def test_modelsim(make_edalize_test): @@ -35,3 +35,40 @@ def test_modelsim(make_edalize_test): ) finally: os.environ = orig_env + + +def test_modelsim_common_compilation(make_edalize_test): + tool_options = { + "compilation_mode": "common", + "vcom_options": ["various", "vcom_options"], + "vlog_options": ["some", "vlog_options"], + "vsim_options": ["a", "few", "vsim_options"], + } + + # FIXME: Add VPI tests + tf = make_edalize_test( + "modelsim", tool_options=tool_options, ref_dir="common_compilation" + ) + + tf.backend.configure() + + tf.compare_files(["Makefile", "edalize_build_rtl.tcl", "edalize_main.tcl"]) + + orig_env = os.environ.copy() + try: + os.environ["MODEL_TECH"] = os.path.join(tests_dir, "mock_commands") + + tf.backend.build() + os.makedirs(os.path.join(tf.work_root, "work")) + + tf.compare_files(["vsim.cmd"]) + + tf.backend.run() + + assert filecmp.cmp( + os.path.join(tf.ref_dir, "vsim2.cmd"), + os.path.join(tf.work_root, "vsim.cmd"), + shallow=False, + ) + finally: + os.environ = orig_env diff --git a/tests/test_modelsim/Makefile b/tests/test_modelsim/Makefile index 02d76e5b2..b5b8bdbde 100644 --- a/tests/test_modelsim/Makefile +++ b/tests/test_modelsim/Makefile @@ -33,7 +33,7 @@ EXTRA_OPTIONS ?= $(VSIM_OPTIONS) $(addprefix -g,$(PARAMETERS)) $(addprefix +,$(P all: work $(VPI_MODULES) run: work $(VPI_MODULES) - $(VSIM) -do "run -all; quit -code [expr [coverage attribute -name TESTSTATUS -concise] >= 2 ? [coverage attribute -name TESTSTATUS -concise] : 0]; exit" -c $(addprefix -pli ,$(VPI_MODULES)) $(EXTRA_OPTIONS) $(TOPLEVEL) + $(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) diff --git a/tests/test_modelsim/common_compilation/Makefile b/tests/test_modelsim/common_compilation/Makefile new file mode 100644 index 000000000..b5b8bdbde --- /dev/null +++ b/tests/test_modelsim/common_compilation/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 ?= a few 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: diff --git a/tests/test_modelsim/common_compilation/edalize_build_rtl.tcl b/tests/test_modelsim/common_compilation/edalize_build_rtl.tcl new file mode 100644 index 000000000..6ac10d4b5 --- /dev/null +++ b/tests/test_modelsim/common_compilation/edalize_build_rtl.tcl @@ -0,0 +1,6 @@ +vlib work +vcom various vcom_options -quiet -work work vhdl_file.vhd +vlib libx +vcom various vcom_options -quiet -work libx vhdl_lfile +vcom -2008 various vcom_options -quiet -work work vhdl2008_file +vlog some vlog_options +define+vlogdefine_bool=1 +define+vlogdefine_int=42 +define+vlogdefine_str=hello -sv +incdir+. -quiet -work work -mfcu sv_file.sv vlog_file.v vlog05_file.v another_sv_file.sv \ No newline at end of file diff --git a/tests/test_modelsim/common_compilation/edalize_main.tcl b/tests/test_modelsim/common_compilation/edalize_main.tcl new file mode 100644 index 000000000..ec4861eb3 --- /dev/null +++ b/tests/test_modelsim/common_compilation/edalize_main.tcl @@ -0,0 +1,3 @@ +onerror { quit -code 1; } +do edalize_build_rtl.tcl +do tcl_file.tcl diff --git a/tests/test_modelsim/common_compilation/vsim.cmd b/tests/test_modelsim/common_compilation/vsim.cmd new file mode 100644 index 000000000..0a1bd305c --- /dev/null +++ b/tests/test_modelsim/common_compilation/vsim.cmd @@ -0,0 +1 @@ +-c -do do edalize_main.tcl; exit diff --git a/tests/test_modelsim/common_compilation/vsim2.cmd b/tests/test_modelsim/common_compilation/vsim2.cmd new file mode 100644 index 000000000..ddb1073c2 --- /dev/null +++ b/tests/test_modelsim/common_compilation/vsim2.cmd @@ -0,0 +1 @@ +-c a few vsim_options -gvlogparam_bool=1 -gvlogparam_int=42 -gvlogparam_str=hello +plusarg_bool=1 +plusarg_int=42 +plusarg_str=hello -do run -all; quit -code [expr [coverage attribute -name TESTSTATUS -concise] >= 2 ? [coverage attribute -name TESTSTATUS -concise] : 0]; exit top_module diff --git a/tests/test_modelsim/vsim2.cmd b/tests/test_modelsim/vsim2.cmd index 4d19a2ca9..ddb1073c2 100644 --- a/tests/test_modelsim/vsim2.cmd +++ b/tests/test_modelsim/vsim2.cmd @@ -1 +1 @@ --do run -all; quit -code [expr [coverage attribute -name TESTSTATUS -concise] >= 2 ? [coverage attribute -name TESTSTATUS -concise] : 0]; exit -c a few vsim_options -gvlogparam_bool=1 -gvlogparam_int=42 -gvlogparam_str=hello +plusarg_bool=1 +plusarg_int=42 +plusarg_str=hello top_module +-c a few vsim_options -gvlogparam_bool=1 -gvlogparam_int=42 -gvlogparam_str=hello +plusarg_bool=1 +plusarg_int=42 +plusarg_str=hello -do run -all; quit -code [expr [coverage attribute -name TESTSTATUS -concise] >= 2 ? [coverage attribute -name TESTSTATUS -concise] : 0]; exit top_module diff --git a/tests/test_morty.py b/tests/test_morty.py index db797982d..1081ef6bc 100644 --- a/tests/test_morty.py +++ b/tests/test_morty.py @@ -1,4 +1,4 @@ -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test import os diff --git a/tests/test_openfpga.py b/tests/test_openfpga.py index 90100c792..f13f37c18 100644 --- a/tests/test_openfpga.py +++ b/tests/test_openfpga.py @@ -1,5 +1,5 @@ import os -from edalize_common import make_edalize_test, tests_dir +from .edalize_common import make_edalize_test, tests_dir def test_openfpga(make_edalize_test): diff --git a/tests/test_openlane.py b/tests/test_openlane.py index 2df39e8d5..bd0bb0f8a 100644 --- a/tests/test_openlane.py +++ b/tests/test_openlane.py @@ -1,4 +1,4 @@ -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test import os diff --git a/tests/test_openlane/config.tcl b/tests/test_openlane/config.tcl index 7892e46db..7208f81dd 100644 --- a/tests/test_openlane/config.tcl +++ b/tests/test_openlane/config.tcl @@ -2,7 +2,7 @@ set ::env(DESIGN_NAME) top_module -set ::env(VERILOG_FILES) [list vlog_file.v vlog05_file.v] +set ::env(VERILOG_FILES) [list sv_file.sv vlog_file.v vlog05_file.v another_sv_file.sv] set ::env(SYNTH_DEFINES) [list vlogdefine_bool=1 vlogdefine_int=42 vlogdefine_str=hello] source tcl_file.tcl diff --git a/tests/test_oxide.py b/tests/test_oxide.py index 21d4a525e..fc789a02f 100644 --- a/tests/test_oxide.py +++ b/tests/test_oxide.py @@ -1,6 +1,6 @@ import os import pytest -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def run_oxide_test(tf): diff --git a/tests/test_oxide/edalize_yosys_template.tcl b/tests/test_oxide/edalize_yosys_template.tcl index c4bfc96ef..f6d03ae2e 100644 --- a/tests/test_oxide/edalize_yosys_template.tcl +++ b/tests/test_oxide/edalize_yosys_template.tcl @@ -13,4 +13,4 @@ verilog_defaults -pop synth $top -write_json $name.json +write_json test_oxide_0.json diff --git a/tests/test_oxide/minimal/edalize_yosys_template.tcl b/tests/test_oxide/minimal/edalize_yosys_template.tcl index c4bfc96ef..f6d03ae2e 100644 --- a/tests/test_oxide/minimal/edalize_yosys_template.tcl +++ b/tests/test_oxide/minimal/edalize_yosys_template.tcl @@ -13,4 +13,4 @@ verilog_defaults -pop synth $top -write_json $name.json +write_json test_oxide_0.json diff --git a/tests/test_quartus.py b/tests/test_quartus.py index 5e5e9245e..6f34c3bff 100644 --- a/tests/test_quartus.py +++ b/tests/test_quartus.py @@ -1,5 +1,5 @@ import os -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test qsys_file = """ diff --git a/tests/test_questa_formal.py b/tests/test_questa_formal.py new file mode 100644 index 000000000..2a93e06f8 --- /dev/null +++ b/tests/test_questa_formal.py @@ -0,0 +1,38 @@ +import filecmp +import os +from .edalize_common import make_edalize_test, tests_dir + + +def test_questa_formal(make_edalize_test): + tool_options = { + "vcom_options": ["various", "vcom_options"], + "vlog_options": ["some", "vlog_options"], + "qverify_options": ["a", "few", "qverify_options"], + "autocheck_options": ["a", "few", "autocheck_options"], + } + + # FIXME: Add VPI tests + tf = make_edalize_test("questaformal", tool_options=tool_options) + + tf.backend.configure() + + tf.compare_files(["Makefile", "edalize_build_rtl.tcl", "edalize_main.tcl"]) + + orig_env = os.environ.copy() + try: + # os.environ["MODEL_TECH"] = os.path.join(tests_dir, "mock_commands") + + tf.backend.build() + os.makedirs(os.path.join(tf.work_root, "work")) + + tf.compare_files(["qverify.cmd"]) + + tf.backend.run() + + assert filecmp.cmp( + os.path.join(tf.ref_dir, "qverify2.cmd"), + os.path.join(tf.work_root, "qverify.cmd"), + shallow=False, + ) + finally: + os.environ = orig_env diff --git a/tests/test_questaformal/Makefile b/tests/test_questaformal/Makefile new file mode 100644 index 000000000..6d9eda7b9 --- /dev/null +++ b/tests/test_questaformal/Makefile @@ -0,0 +1,18 @@ +#Generated by Edalize + +QVERIFY ?= qverify + +QVERIFY_OPTIONS ?= a few qverify_options + +all: work + +run: work + $(QVERIFY) $(QVERIFY_OPTIONS) -do "do edalize_autocheck.tcl; exit" + +run-gui: work + $(QVERIFY) -gui + +work: + $(QVERIFY) -c -do "do edalize_main.tcl; exit" + +clean: diff --git a/tests/test_questaformal/edalize_build_rtl.tcl b/tests/test_questaformal/edalize_build_rtl.tcl new file mode 100644 index 000000000..27ce66967 --- /dev/null +++ b/tests/test_questaformal/edalize_build_rtl.tcl @@ -0,0 +1,9 @@ +vlib work +vlog some vlog_options +define+vlogdefine_bool=1 +define+vlogdefine_int=42 +define+vlogdefine_str=hello -sv +incdir+. -quiet -work work sv_file.sv +vlog some vlog_options +define+vlogdefine_bool=1 +define+vlogdefine_int=42 +define+vlogdefine_str=hello +incdir+. -quiet -work work vlog_file.v +vlog some vlog_options +define+vlogdefine_bool=1 +define+vlogdefine_int=42 +define+vlogdefine_str=hello +incdir+. -quiet -work work vlog05_file.v +vcom various vcom_options -quiet -work work vhdl_file.vhd +vlib libx +vcom various vcom_options -quiet -work libx vhdl_lfile +vcom -2008 various vcom_options -quiet -work work vhdl2008_file +vlog some vlog_options +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/test_questaformal/edalize_main.tcl b/tests/test_questaformal/edalize_main.tcl new file mode 100644 index 000000000..ec4861eb3 --- /dev/null +++ b/tests/test_questaformal/edalize_main.tcl @@ -0,0 +1,3 @@ +onerror { quit -code 1; } +do edalize_build_rtl.tcl +do tcl_file.tcl diff --git a/tests/test_questaformal/qverify.cmd b/tests/test_questaformal/qverify.cmd new file mode 100644 index 000000000..0a1bd305c --- /dev/null +++ b/tests/test_questaformal/qverify.cmd @@ -0,0 +1 @@ +-c -do do edalize_main.tcl; exit diff --git a/tests/test_questaformal/qverify2.cmd b/tests/test_questaformal/qverify2.cmd new file mode 100644 index 000000000..dcee3a1fe --- /dev/null +++ b/tests/test_questaformal/qverify2.cmd @@ -0,0 +1 @@ +a few qverify_options -do do edalize_autocheck.tcl; exit diff --git a/tests/test_radiant.py b/tests/test_radiant.py index 872397ab4..db8766936 100644 --- a/tests/test_radiant.py +++ b/tests/test_radiant.py @@ -1,4 +1,4 @@ -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def test_radiant(make_edalize_test): @@ -26,9 +26,8 @@ def test_radiant_minimal(tmpdir): from edalize import get_edatool - from edalize_common import compare_files, tests_dir + from .edalize_common import compare_files, tests_dir - ref_dir = os.path.join(tests_dir, __name__, "minimal") os.environ["PATH"] = ( os.path.join(tests_dir, "mock_commands") + ":" + os.environ["PATH"] ) @@ -44,6 +43,7 @@ def test_radiant_minimal(tmpdir): backend = get_edatool(tool)(edam=edam, work_root=work_root) backend.configure() + ref_dir = os.path.join(tests_dir, "test_" + tool, "minimal") compare_files( ref_dir, work_root, diff --git a/tests/test_reporting.py b/tests/test_reporting.py index 96cd31b74..7c66aeb45 100644 --- a/tests/test_reporting.py +++ b/tests/test_reporting.py @@ -3,7 +3,7 @@ import pytest import pandas as pd -from edalize_common import tests_dir +from .edalize_common import tests_dir def check_types(s, allowed=[int, float]): diff --git a/tests/test_rivierapro.py b/tests/test_rivierapro.py index 23bf959a7..c51d9d939 100644 --- a/tests/test_rivierapro.py +++ b/tests/test_rivierapro.py @@ -1,6 +1,6 @@ import filecmp import os -from edalize_common import make_edalize_test, tests_dir +from .edalize_common import make_edalize_test, tests_dir def test_rivierapro(make_edalize_test): diff --git a/tests/test_slang.py b/tests/test_slang.py new file mode 100644 index 000000000..28f86f4ff --- /dev/null +++ b/tests/test_slang.py @@ -0,0 +1,47 @@ +from .edalize_common import make_edalize_test + + +def test_slang_lint(make_edalize_test): + tool_options = {"mode": "lint"} + tf = make_edalize_test( + "slang", + test_name="test_slang_lint", + param_types=["vlogdefine", "vlogparam"], + tool_options=tool_options, + ref_dir="lint", + ) + + tf.backend.configure() + tf.backend.build() + tf.backend.run() + tf.compare_files(["slang.cmd"]) + + +def test_slang_preprocess(make_edalize_test): + tool_options = {"mode": "preprocess"} + tf = make_edalize_test( + "slang", + test_name="test_slang_preprocess", + tool_options=tool_options, + param_types=["vlogdefine", "vlogparam"], + ref_dir="preprocess", + ) + tf.backend.configure() + tf.backend.build() + tf.backend.run() + tf.compare_files(["slang.cmd"]) + + +def test_slang_slang_options(make_edalize_test): + tool_options = {"slang_options": ["-v", "-d -c -e"]} + tf = make_edalize_test( + "slang", + test_name="test_slang_slang_options", + tool_options=tool_options, + param_types=["vlogdefine", "vlogparam"], + ref_dir="slang_options", + ) + tf.backend.configure() + tf.backend.build() + tf.backend.run() + tf.compare_files(["slang.cmd"]) diff --git a/tests/test_slang/lint/slang.cmd b/tests/test_slang/lint/slang.cmd new file mode 100644 index 000000000..bd3ed2976 --- /dev/null +++ b/tests/test_slang/lint/slang.cmd @@ -0,0 +1 @@ +-D vlogdefine_bool=1 -D vlogdefine_int=42 -D vlogdefine_str=hello -G vlogparam_bool=1 -G vlogparam_int=42 -G vlogparam_str=hello -I . sv_file.sv vlog_file.v vlog05_file.v another_sv_file.sv --lint-only --top top_module diff --git a/tests/test_slang/preprocess/slang.cmd b/tests/test_slang/preprocess/slang.cmd new file mode 100644 index 000000000..62176f095 --- /dev/null +++ b/tests/test_slang/preprocess/slang.cmd @@ -0,0 +1 @@ +-D vlogdefine_bool=1 -D vlogdefine_int=42 -D vlogdefine_str=hello -G vlogparam_bool=1 -G vlogparam_int=42 -G vlogparam_str=hello -I . sv_file.sv vlog_file.v vlog05_file.v another_sv_file.sv --preprocess --top top_module diff --git a/tests/test_slang/slang_options/slang.cmd b/tests/test_slang/slang_options/slang.cmd new file mode 100644 index 000000000..92a1e2aef --- /dev/null +++ b/tests/test_slang/slang_options/slang.cmd @@ -0,0 +1 @@ +-D vlogdefine_bool=1 -D vlogdefine_int=42 -D vlogdefine_str=hello -G vlogparam_bool=1 -G vlogparam_int=42 -G vlogparam_str=hello -I . sv_file.sv vlog_file.v vlog05_file.v another_sv_file.sv -v -d -c -e --top top_module diff --git a/tests/test_spyglass.py b/tests/test_spyglass.py index 2f685906e..dd4c04fc4 100644 --- a/tests/test_spyglass.py +++ b/tests/test_spyglass.py @@ -1,4 +1,4 @@ -from edalize_common import make_edalize_test, tests_dir +from .edalize_common import make_edalize_test, tests_dir def run_spyglass_test(tf): diff --git a/tests/test_symbiflow.py b/tests/test_symbiflow.py index d1c286349..09b369f20 100644 --- a/tests/test_symbiflow.py +++ b/tests/test_symbiflow.py @@ -1,6 +1,6 @@ import os import pytest -from edalize_common import make_edalize_test, tests_dir +from .edalize_common import make_edalize_test, tests_dir def run_symbiflow_test(tf, config_files=list()): diff --git a/tests/test_symbiflow/nextpnr/fpga_interchange/edalize_yosys_template.tcl b/tests/test_symbiflow/nextpnr/fpga_interchange/edalize_yosys_template.tcl index 41e226e9b..76a3e6e2e 100644 --- a/tests/test_symbiflow/nextpnr/fpga_interchange/edalize_yosys_template.tcl +++ b/tests/test_symbiflow/nextpnr/fpga_interchange/edalize_yosys_template.tcl @@ -13,4 +13,4 @@ verilog_defaults -pop synth $top -write_json -pvector bra $name.json +write_json test_symbiflow_nextpnr_fpga_interchange_0.json diff --git a/tests/test_symbiflow/nextpnr/xilinx/edalize_yosys_template.tcl b/tests/test_symbiflow/nextpnr/xilinx/edalize_yosys_template.tcl index 41e226e9b..860c33189 100644 --- a/tests/test_symbiflow/nextpnr/xilinx/edalize_yosys_template.tcl +++ b/tests/test_symbiflow/nextpnr/xilinx/edalize_yosys_template.tcl @@ -13,4 +13,4 @@ verilog_defaults -pop synth $top -write_json -pvector bra $name.json +write_json test_symbiflow_nextpnr_xilinx_0.json diff --git a/tests/test_symbiyosys.py b/tests/test_symbiyosys.py index b02a04b0c..3275a2d13 100644 --- a/tests/test_symbiyosys.py +++ b/tests/test_symbiyosys.py @@ -1,4 +1,4 @@ -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def test_symbiyosys(make_edalize_test): diff --git a/tests/test_trellis.py b/tests/test_trellis.py index ba67591cc..e898e166e 100644 --- a/tests/test_trellis.py +++ b/tests/test_trellis.py @@ -1,6 +1,6 @@ import os import pytest -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def run_trellis_test(tf, pnr_cmdfile="nextpnr-ice40.cmd"): diff --git a/tests/test_trellis/edalize_yosys_template.tcl b/tests/test_trellis/edalize_yosys_template.tcl index c4bfc96ef..c749b7028 100644 --- a/tests/test_trellis/edalize_yosys_template.tcl +++ b/tests/test_trellis/edalize_yosys_template.tcl @@ -13,4 +13,4 @@ verilog_defaults -pop synth $top -write_json $name.json +write_json test_trellis_0.json diff --git a/tests/test_trellis/minimal/edalize_yosys_template.tcl b/tests/test_trellis/minimal/edalize_yosys_template.tcl index c4bfc96ef..c749b7028 100644 --- a/tests/test_trellis/minimal/edalize_yosys_template.tcl +++ b/tests/test_trellis/minimal/edalize_yosys_template.tcl @@ -13,4 +13,4 @@ verilog_defaults -pop synth $top -write_json $name.json +write_json test_trellis_0.json diff --git a/tests/test_vcs.py b/tests/test_vcs.py index c065899d4..59bbe4de7 100644 --- a/tests/test_vcs.py +++ b/tests/test_vcs.py @@ -1,4 +1,4 @@ -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def run_vcs_test(tf): @@ -37,9 +37,8 @@ def test_vcs_minimal(tmpdir): from edalize import get_edatool - from edalize_common import compare_files, tests_dir + from .edalize_common import compare_files, tests_dir - ref_dir = os.path.join(tests_dir, __name__, "minimal") os.environ["PATH"] = ( os.path.join(tests_dir, "mock_commands") + ":" + os.environ["PATH"] ) @@ -52,6 +51,7 @@ def test_vcs_minimal(tmpdir): backend = get_edatool(tool)(edam=edam, work_root=work_root) backend.configure() + ref_dir = os.path.join(tests_dir, "test_" + tool, "minimal") compare_files(ref_dir, work_root, ["Makefile", name + ".scr"]) backend.build() diff --git a/tests/test_vcs/minimal/Makefile b/tests/test_vcs/minimal/Makefile index dc0575cd5..71ad38157 100644 --- a/tests/test_vcs/minimal/Makefile +++ b/tests/test_vcs/minimal/Makefile @@ -1,7 +1,7 @@ all: test_vcs_minimal_0 test_vcs_minimal_0: test_vcs_minimal_0.scr - vcs -full64 -top top -f test_vcs_minimal_0.scr -o $@ + $(EDALIZE_LAUNCHER) vcs -full64 -top top -f test_vcs_minimal_0.scr -o $@ run: test_vcs_minimal_0 ./test_vcs_minimal_0 -l vcs.log clean: diff --git a/tests/test_vcs/no_tool_options/Makefile b/tests/test_vcs/no_tool_options/Makefile index 579f9fd34..600f14fc2 100644 --- a/tests/test_vcs/no_tool_options/Makefile +++ b/tests/test_vcs/no_tool_options/Makefile @@ -1,8 +1,8 @@ all: test_vcs_0 test_vcs_0: test_vcs_0.scr - vcs -full64 -top top_module -f test_vcs_0.scr -o $@ -sverilog + $(EDALIZE_LAUNCHER) vcs -full64 -top top_module -f test_vcs_0.scr -o $@ -sverilog run: test_vcs_0 - ./test_vcs_0 -l vcs.log +plusarg_bool=1 +plusarg_int=42 +plusarg_str=hello + ./test_vcs_0 -l vcs.log +plusarg_bool +plusarg_int=42 +plusarg_str=hello clean: $(RM) test_vcs_0 diff --git a/tests/test_vcs/no_tool_options/run.cmd b/tests/test_vcs/no_tool_options/run.cmd index d0407b0be..43b092926 100644 --- a/tests/test_vcs/no_tool_options/run.cmd +++ b/tests/test_vcs/no_tool_options/run.cmd @@ -1 +1 @@ --l vcs.log +plusarg_bool=1 +plusarg_int=42 +plusarg_str=hello +-l vcs.log +plusarg_bool +plusarg_int=42 +plusarg_str=hello diff --git a/tests/test_vcs/tool_options/Makefile b/tests/test_vcs/tool_options/Makefile index 1676d1c6f..4a7ffdba8 100644 --- a/tests/test_vcs/tool_options/Makefile +++ b/tests/test_vcs/tool_options/Makefile @@ -1,8 +1,8 @@ all: test_vcs_tool_options_0 test_vcs_tool_options_0: test_vcs_tool_options_0.scr - vcs -full64 -top top_module -f test_vcs_tool_options_0.scr -o $@ -debug_access+pp -debug_access+all -sverilog + $(EDALIZE_LAUNCHER) vcs -full64 -top top_module -f test_vcs_tool_options_0.scr -o $@ -debug_access+pp -debug_access+all -sverilog run: test_vcs_tool_options_0 - ./test_vcs_tool_options_0 -l vcs.log +plusarg_bool=1 +plusarg_int=42 +plusarg_str=hello -licqueue + ./test_vcs_tool_options_0 -l vcs.log +plusarg_bool +plusarg_int=42 +plusarg_str=hello -licqueue clean: $(RM) test_vcs_tool_options_0 diff --git a/tests/test_vcs/tool_options/run.cmd b/tests/test_vcs/tool_options/run.cmd index 7b6cf7751..bfdd8017a 100644 --- a/tests/test_vcs/tool_options/run.cmd +++ b/tests/test_vcs/tool_options/run.cmd @@ -1 +1 @@ --l vcs.log +plusarg_bool=1 +plusarg_int=42 +plusarg_str=hello -licqueue +-l vcs.log +plusarg_bool +plusarg_int=42 +plusarg_str=hello -licqueue diff --git a/tests/test_veribleformat.py b/tests/test_veribleformat.py index 1daf3249b..0a7ff2765 100644 --- a/tests/test_veribleformat.py +++ b/tests/test_veribleformat.py @@ -1,4 +1,4 @@ -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def test_veribleformat_default(make_edalize_test): diff --git a/tests/test_veriblelint.py b/tests/test_veriblelint.py index 58402ba66..1487dddfd 100644 --- a/tests/test_veriblelint.py +++ b/tests/test_veriblelint.py @@ -1,4 +1,4 @@ -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def test_veriblelint_default(make_edalize_test): diff --git a/tests/test_verilator.py b/tests/test_verilator.py index 3fdf66564..f982c9526 100644 --- a/tests/test_verilator.py +++ b/tests/test_verilator.py @@ -1,4 +1,4 @@ -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def test_verilator_cc(make_edalize_test): diff --git a/tests/test_vivado.py b/tests/test_vivado.py index ec638c3c7..b9ab226d7 100644 --- a/tests/test_vivado.py +++ b/tests/test_vivado.py @@ -1,5 +1,5 @@ import pytest -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test def test_vivado(make_edalize_test): @@ -28,11 +28,10 @@ def test_vivado_minimal(params, tmpdir, make_edalize_test): import os import edalize - from edalize_common import compare_files, tests_dir + from .edalize_common import compare_files, tests_dir test_name, synth_tool = params - ref_dir = os.path.join(tests_dir, __name__, test_name) os.environ["PATH"] = ( os.path.join(tests_dir, "mock_commands") + ":" + os.environ["PATH"] ) @@ -63,6 +62,7 @@ def test_vivado_minimal(params, tmpdir, make_edalize_test): else: config_file_list.append(name + "_synth.tcl") + ref_dir = os.path.join(tests_dir, "test_" + tool, test_name) compare_files(ref_dir, work_root, config_file_list) build_file_list = ["vivado.cmd"] diff --git a/tests/test_vpr.py b/tests/test_vpr.py index b377cbc40..04222d11a 100644 --- a/tests/test_vpr.py +++ b/tests/test_vpr.py @@ -1,16 +1,15 @@ import os import pytest -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test @pytest.mark.parametrize("params", [("minimal", "vpr")]) def test_vpr(params, tmpdir): import os import edalize - from edalize_common import compare_files, tests_dir + from .edalize_common import compare_files, tests_dir test_name = "vpr" - ref_dir = os.path.join(tests_dir, __name__, test_name) os.environ["PATH"] = ( os.path.join(tests_dir, "mock_commands") + ":" + os.environ["PATH"] ) @@ -33,4 +32,5 @@ def test_vpr(params, tmpdir): config_file_list = [ "Makefile", ] + ref_dir = os.path.join(tests_dir, "test_" + tool, test_name) compare_files(ref_dir, work_root, config_file_list) diff --git a/tests/test_vunit.py b/tests/test_vunit.py index b3386c360..e5dc10ebb 100644 --- a/tests/test_vunit.py +++ b/tests/test_vunit.py @@ -1,7 +1,7 @@ import pytest import os.path from unittest.mock import patch, MagicMock -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test import edalize.edatool @@ -12,7 +12,7 @@ def test_vunit_codegen(make_edalize_test): def test_vunit_hooks(tmpdir): - from edalize_common import tests_dir + from .edalize_common import tests_dir import subprocess import sys @@ -20,9 +20,9 @@ def test_vunit_hooks(tmpdir): from unittest import mock from edalize import get_edatool - sys.path = [os.path.join(tests_dir, __name__, "vunit_mock")] + sys.path + sys.path = [os.path.join(tests_dir, "test_vunit", "vunit_mock")] + sys.path - ref_dir = os.path.join(tests_dir, __name__, "minimal") + ref_dir = os.path.join(tests_dir, "test_vunit", "minimal") tool = "vunit" name = "test_" + tool + "_minimal_0" work_root = str(tmpdir) diff --git a/tests/test_xcelium.py b/tests/test_xcelium.py index 41e5c69f3..4e28fc014 100644 --- a/tests/test_xcelium.py +++ b/tests/test_xcelium.py @@ -1,5 +1,5 @@ import os -from edalize_common import make_edalize_test, tests_dir +from .edalize_common import make_edalize_test, tests_dir def test_xcelium(make_edalize_test): diff --git a/tests/test_xsim.py b/tests/test_xsim.py index 83bc991a2..2dc3f95dd 100644 --- a/tests/test_xsim.py +++ b/tests/test_xsim.py @@ -1,4 +1,4 @@ -from edalize_common import make_edalize_test +from .edalize_common import make_edalize_test import os diff --git a/tests/test_yosys.py b/tests/test_yosys.py new file mode 100644 index 000000000..d2f70636b --- /dev/null +++ b/tests/test_yosys.py @@ -0,0 +1,21 @@ +from .edalize_common import make_edalize_test + + +def test_symbiyosys(make_edalize_test): + output_names = { + "default_output_name": { + "arch": "ice40", + }, + "custom_output_name": {"arch": "ice40", "output_name": "test.json"}, + } + + for test_name, tool_options in output_names.items(): + tf = make_edalize_test( + "yosys", + param_types=["vlogdefine", "vlogparam"], + tool_options=tool_options, + ref_dir=test_name, + ) + + tf.backend.configure() + tf.compare_files(["Makefile"]) diff --git a/tests/test_yosys/custom_output_name/Makefile b/tests/test_yosys/custom_output_name/Makefile new file mode 100644 index 000000000..285ac2535 --- /dev/null +++ b/tests/test_yosys/custom_output_name/Makefile @@ -0,0 +1,6 @@ +#Auto generated by Edalize + +all: test.json + +test.json: edalize_yosys_template.tcl + $(EDALIZE_LAUNCHER) yosys -l yosys.log -p 'tcl edalize_yosys_template.tcl' diff --git a/tests/test_yosys/default_output_name/Makefile b/tests/test_yosys/default_output_name/Makefile new file mode 100644 index 000000000..e6080e4ee --- /dev/null +++ b/tests/test_yosys/default_output_name/Makefile @@ -0,0 +1,6 @@ +#Auto generated by Edalize + +all: test_yosys_0.blif + +test_yosys_0.blif: edalize_yosys_template.tcl + $(EDALIZE_LAUNCHER) yosys -l yosys.log -p 'tcl edalize_yosys_template.tcl'