cocotb.runner 和from cocotb_test.simulator import run 两个方法区别

在这里插入图片描述

在这里插入图片描述

不要使用 from cocotb_test.simulator import run

# test_runner.py

import os
from pathlib import Path

from cocotb.runner import get_runner


def test_my_design_runner():
    sim = os.getenv("SIM", "icarus")

    proj_path = Path(__file__).resolve().parent

    sources = [proj_path / "my_design.sv"]

    runner = get_runner(sim)
    runner.build(
        sources=sources,
        hdl_toplevel="my_design",
    )

    runner.test(hdl_toplevel="my_design", test_module="test_my_design,")


if __name__ == "__main__":
    test_my_design_runner()

https://docs.cocotb.org/en/latest/_modules/cocotb_tools/runner.html#Runner


class Runner(ABC):
    supported_gpi_interfaces: Dict[str, List[str]] = {}

    def __init__(self) -> None:
        self._simulator_in_path()

        self.env: Dict[str, str] = {}

        # for running test() independently of build()
        self.build_dir: Path = get_abs_path("sim_build")
        self.parameters: Mapping[str, object] = {}

        self.log = logging.getLogger(type(self).__qualname__)

    @abstractmethod
    def _simulator_in_path(self) -> None:
        """Raise exception if the simulator executable does not exist in :envvar:`PATH`.

        Raises:
            SystemExit: Simulator executable does not exist in :envvar:`PATH`.
        """

    def _check_hdl_toplevel_lang(self, hdl_toplevel_lang: Optional[str]) -> str:
        """Return *hdl_toplevel_lang* if supported by simulator, raise exception otherwise.

        Returns:
            *hdl_toplevel_lang* if supported by the simulator.

        Raises:
            ValueError: *hdl_toplevel_lang* is not supported by the simulator.
        """
        if hdl_toplevel_lang is None:
            if self.vhdl_sources and not self.verilog_sources and not self.sources:
                lang = "vhdl"
            elif self.verilog_sources and not self.vhdl_sources and not self.sources:
                lang = "verilog"
            elif self.sources and not self.vhdl_sources and not self.verilog_sources:
                if is_vhdl_source(self.sources[-1]):
                    lang = "vhdl"
                elif is_verilog_source(self.sources[-1]):
                    lang = "verilog"
                else:
                    raise UnknownFileExtension(self.sources[-1])
            else:
                raise ValueError(
                    f"{type(self).__qualname__}: Must specify a hdl_toplevel_lang in a mixed-language design"
                )
        else:
            lang = hdl_toplevel_lang

        if lang in self.supported_gpi_interfaces.keys():
            return lang
        else:
            raise ValueError(
                f"{type(self).__qualname__}: hdl_toplevel_lang {hdl_toplevel_lang!r} is not "
                f"in supported list: {', '.join(self.supported_gpi_interfaces.keys())}"
            )

    def _set_env(self) -> None:
        """Set environment variables for sub-processes."""

        for e in os.environ:
            self.env[e] = os.environ[e]

        if "LIBPYTHON_LOC" not in self.env:
            libpython_path = find_libpython.find_libpython()
            if not libpython_path:
                raise ValueError(
                    "Unable to find libpython, please make sure the appropriate libpython is installed"
                )
            self.env["LIBPYTHON_LOC"] = libpython_path

        self.env["PATH"] += os.pathsep + str(cocotb_tools.config.libs_dir)
        self.env["PYTHONPATH"] = os.pathsep.join(sys.path)
        self.env["PYTHONHOME"] = sys.prefix
        self.env["COCOTB_TOPLEVEL"] = self.sim_hdl_toplevel
        self.env["COCOTB_TEST_MODULES"] = self.test_module
        self.env["TOPLEVEL_LANG"] = self.hdl_toplevel_lang

    @abstractmethod
    def _build_command(self) -> Sequence[_Command]:
        """Return command to build the HDL sources."""

    @abstractmethod
    def _test_command(self) -> Sequence[_Command]:
        """Return command to run a test."""


[docs]

    def build(
        self,
        hdl_library: str = "top",
        verilog_sources: Sequence[PathLike] = [],
        vhdl_sources: Sequence[PathLike] = [],
        sources: Sequence[Union[PathLike, VHDL, Verilog]] = [],
        includes: Sequence[PathLike] = [],
        defines: Mapping[str, object] = {},
        parameters: Mapping[str, object] = {},
        build_args: Sequence[Union[str, VHDL, Verilog]] = [],
        hdl_toplevel: Optional[str] = None,
        always: bool = False,
        build_dir: PathLike = "sim_build",
        clean: bool = False,
        verbose: bool = False,
        timescale: Optional[Tuple[str, str]] = None,
        waves: bool = False,
        log_file: Optional[PathLike] = None,
    ) -> None:
        """Build the HDL sources.

        With mixed language simulators, *sources* will be built,
        followed by *vhdl_sources*, then *verilog_sources*.
        With simulators that only support either VHDL or Verilog, *sources* will be built,
        followed by *vhdl_sources* and *verilog_sources*, respectively.

        If your source files use an atypical file extension,
        use :class:`VHDL` and :class:`Verilog` to tag the path as a VHDL or Verilog source file, respectively.
        If the filepaths aren't tagged, the extension is used to determine if they are VHDL or Verilog files.

        +----------+------------------------------------+
        | Language | File Extensions                    |
        +==========+====================================+
        | VHDL     | ``.vhd``, ``.vhdl``                |
        +----------+------------------------------------+
        | Verilog  | ``.v``, ``.sv``, ``.vh``, ``.svh`` |
        +----------+------------------------------------+


        .. code-block:: python3

            runner.build(
                sources=[
                    VHDL("/my/file.is_actually_vhdl"),
                    Verilog("/other/file.verilog"),
                ],
            )

        The same tagging works for *build_args*.
        Tagged *build_args* only supply that option to the compiler when building the source file for the tagged language.
        Non-tagged *build_args* are supplied when compiling any language.

        Args:
            hdl_library: The library name to compile into.
            verilog_sources: Verilog source files to build.
            vhdl_sources: VHDL source files to build.
            sources: Language-agnostic list of source files to build.
            includes: Verilog include directories.
            defines: Defines to set.
            parameters: Verilog parameters or VHDL generics.
            build_args: Extra build arguments for the simulator.
            hdl_toplevel: The name of the HDL toplevel module.
            always: Always run the build step.
            build_dir: Directory to run the build step in.
            clean: Delete *build_dir* before building.
            verbose: Enable verbose messages.
            timescale: Tuple containing time unit and time precision for simulation.
            waves: Record signal traces.
            log_file: File to write the build log to.

        .. deprecated:: 2.0

            Uses of the *verilog_sources* and *vhdl_sources* parameters should be replaced with the language-agnostic *sources* argument.
        """

        self.clean: bool = clean
        self.build_dir = get_abs_path(build_dir)
        if self.clean:
            self.rm_build_folder(self.build_dir)
        os.makedirs(self.build_dir, exist_ok=True)

        # note: to avoid mutating argument defaults, we ensure that no value
        # is written without a copy. This is much more concise and leads to
        # a better docstring than using `None` as a default in the parameters
        # list.
        self.hdl_library: str = hdl_library
        if verilog_sources:
            warnings.warn(
                "Simulator.build *verilog_sources* parameter is deprecated. Use the language-agnostic *sources* parameter instead.",
                DeprecationWarning,
                stacklevel=2,
            )
        self.verilog_sources: List[Path] = get_abs_paths(verilog_sources)
        if vhdl_sources:
            warnings.warn(
                "Simulator.build *vhdl_sources* parameter is deprecated. Use the language-agnostic *sources* parameter instead.",
                DeprecationWarning,
                stacklevel=2,
            )
        self.vhdl_sources: List[Path] = get_abs_paths(vhdl_sources)
        self.sources: List[Path] = get_abs_paths(sources)
        self.includes: List[Path] = get_abs_paths(includes)
        self.defines = dict(defines)
        self.parameters = dict(parameters)
        self.build_args = list(build_args)
        self.always: bool = always
        self.hdl_toplevel: Optional[str] = hdl_toplevel
        self.verbose: bool = verbose
        self.timescale: Optional[Tuple[str, str]] = timescale
        self.log_file: Optional[PathLike] = log_file

        self.waves = waves

        self.env.update(os.environ)

        cmds: Sequence[_Command] = self._build_command()
        self._execute(cmds, cwd=self.build_dir)




[docs]

    def test(
        self,
        test_module: Union[str, Sequence[str]],
        hdl_toplevel: str,
        hdl_toplevel_library: str = "top",
        hdl_toplevel_lang: Optional[str] = None,
        gpi_interfaces: Optional[List[str]] = None,
        testcase: Optional[Union[str, Sequence[str]]] = None,
        seed: Optional[Union[str, int]] = None,
        elab_args: Sequence[str] = [],
        test_args: Sequence[str] = [],
        plusargs: Sequence[str] = [],
        extra_env: Mapping[str, str] = {},
        waves: bool = False,
        gui: bool = False,
        parameters: Optional[Mapping[str, object]] = None,
        build_dir: Optional[PathLike] = None,
        test_dir: Optional[PathLike] = None,
        results_xml: Optional[str] = None,
        pre_cmd: Optional[List[str]] = None,
        verbose: bool = False,
        timescale: Optional[Tuple[str, str]] = None,
        log_file: Optional[PathLike] = None,
        test_filter: Optional[str] = None,
    ) -> Path:
        """Run the tests.

        Args:
            test_module: Name(s) of the Python module(s) containing the tests to run.
                Can be a comma-separated list.
            hdl_toplevel: Name of the HDL toplevel module.
            hdl_toplevel_library: The library name for HDL toplevel module.
            hdl_toplevel_lang: Language of the HDL toplevel module.
            gpi_interfaces: List of GPI interfaces to use, with the first one being the entry point.
            testcase: Name(s) of a specific testcase(s) to run.
                If not set, run all testcases found in *test_module*.
                Can be a comma-separated list.
            seed: A specific random seed to use.
            elab_args: A list of elaboration arguments for the simulator.
            test_args: A list of extra arguments for the simulator.
            plusargs: 'plusargs' to set for the simulator.
            extra_env: Extra environment variables to set.
            waves: Record signal traces.
            gui: Run with simulator GUI.
            parameters: Verilog parameters or VHDL generics.
            build_dir: Directory the build step has been run in.
            test_dir: Directory to run the tests in.
            results_xml: Name of xUnit XML file to store test results in.
                If an absolute path is provided it will be used as-is,
                ``{build_dir}/results.xml`` otherwise.
                This argument should not be set when run with ``pytest``.
            verbose: Enable verbose messages.
            pre_cmd: Commands to run before simulation begins.
                Typically Tcl commands for simulators that support them.
            timescale: Tuple containing time unit and time precision for simulation.
            log_file: File to write the test log to.
            test_filter: Regular expression which matches test names.
                Only matched tests are run if this argument if given.

        Returns:
            The absolute location of the results XML file which can be
            defined by the *results_xml* argument.
        """

        __tracebackhide__ = True  # Hide the traceback when using pytest

        if build_dir is not None:
            self.build_dir = get_abs_path(build_dir)

        if parameters is not None:
            self.parameters = dict(parameters)

        if test_dir is None:
            self.test_dir = self.build_dir
        else:
            self.test_dir = get_abs_path(test_dir)
        os.makedirs(self.test_dir, exist_ok=True)

        if isinstance(test_module, str):
            self.test_module = test_module
        else:
            self.test_module = ",".join(test_module)

        # note: to avoid mutating argument defaults, we ensure that no value
        # is written without a copy. This is much more concise and leads to
        # a better docstring than using `None` as a default in the parameters
        # list.
        self.sim_hdl_toplevel = hdl_toplevel
        self.hdl_toplevel_library: str = hdl_toplevel_library
        self.hdl_toplevel_lang = self._check_hdl_toplevel_lang(hdl_toplevel_lang)
        if gpi_interfaces:
            self.gpi_interfaces = gpi_interfaces
        else:
            self.gpi_interfaces = []
            for gpi_if in self.supported_gpi_interfaces.values():
                self.gpi_interfaces.append(gpi_if[0])

        self.pre_cmd = pre_cmd

        self.elab_args = list(elab_args)
        self.test_args = list(test_args)
        self.plusargs = list(plusargs)
        self.env = dict(extra_env)

        if testcase is not None:
            if isinstance(testcase, str):
                self.env["COCOTB_TESTCASE"] = testcase
            else:
                self.env["COCOTB_TESTCASE"] = ",".join(testcase)

        if test_filter is not None:
            self.env["COCOTB_TEST_FILTER"] = test_filter

        if seed is not None:
            self.env["COCOTB_RANDOM_SEED"] = str(seed)

        self.log_file = log_file
        self.waves = waves
        self.gui = gui
        self.timescale = timescale

        if verbose is not None:
            self.verbose = verbose

        # When using pytest, use test name as result file name
        pytest_current_test = os.getenv("PYTEST_CURRENT_TEST", None)
        test_dir_path = Path(self.test_dir)
        self.current_test_name = "test"
        if results_xml is not None:
            # PYTEST_CURRENT_TEST only allowed when results_xml is not set
            assert not pytest_current_test
            results_xml_path = Path(results_xml)
            if results_xml_path.is_absolute():
                results_xml_file = results_xml_path
            else:
                results_xml_file = test_dir_path / results_xml_path
        elif pytest_current_test is not None:
            self.current_test_name = pytest_current_test.split(":")[-1].split(" ")[0]
            results_xml_file = test_dir_path / f"{self.current_test_name}.{results_xml}"
        else:
            results_xml_file = test_dir_path / "results.xml"

        with suppress(OSError):
            os.remove(results_xml_file)

        # transport the settings to cocotb via environment variables
        self._set_env()
        self.env["COCOTB_RESULTS_FILE"] = str(results_xml_file)

        cmds: Sequence[_Command] = self._test_command()
        simulator_exit_code: int = 0
        try:
            self._execute(cmds, cwd=self.test_dir)
        except subprocess.CalledProcessError as e:
            # It is possible for the simulator to fail but still leave results.
            self.log.error("Simulation failed: %d", e.returncode)
            simulator_exit_code = e.returncode

        # Only when running under pytest, check the results file here,
        # potentially raising an exception with failing testcases,
        # otherwise return the results file for later analysis.
        if pytest_current_test:
            try:
                (num_tests, num_failed) = get_results(results_xml_file)
            except RuntimeError as e:
                self.log.error("%s", e.args[0])
                sys.exit(simulator_exit_code)
            else:
                if num_failed:
                    self.log.error(
                        "ERROR: Failed %d of %d tests.", num_failed, num_tests
                    )
                    sys.exit(1 if simulator_exit_code == 0 else simulator_exit_code)

        if simulator_exit_code != 0:
            sys.exit(simulator_exit_code)

        self.log.info("Results file: %s", results_xml_file)
        return results_xml_file



    @abstractmethod
    def _get_include_options(self, includes: Sequence[PathLike]) -> _Command:
        """Return simulator-specific formatted option strings with *includes* directories."""

    @abstractmethod
    def _get_define_options(self, defines: Mapping[str, object]) -> _Command:
        """Return simulator-specific formatted option strings with *defines* macros."""

    @abstractmethod
    def _get_parameter_options(self, parameters: Mapping[str, object]) -> _Command:
        """Return simulator-specific formatted option strings with *parameters*/generics."""

    def _execute(self, cmds: Sequence[_Command], cwd: PathLike) -> None:
        __tracebackhide__ = True  # Hide the traceback when using PyTest.

        if self.log_file is None:
            self._execute_cmds(cmds, cwd)
        else:
            with open(self.log_file, "w") as f:
                self._execute_cmds(cmds, cwd, f)

    def _execute_cmds(
        self, cmds: Sequence[_Command], cwd: PathLike, stdout: Optional[TextIO] = None
    ) -> None:
        __tracebackhide__ = True  # Hide the traceback when using PyTest.

        for cmd in cmds:
            self.log.info("Running command %s in directory %s", _shlex_join(cmd), cwd)

            # TODO: create a thread to handle stderr and log as error?
            # TODO: log forwarding

            stderr = None if stdout is None else subprocess.STDOUT
            subprocess.run(
                cmd, cwd=cwd, env=self.env, check=True, stdout=stdout, stderr=stderr
            )

    def rm_build_folder(self, build_dir: Path) -> None:
        if os.path.isdir(build_dir):
            self.log.info("Removing: %s", build_dir)
            shutil.rmtree(build_dir, ignore_errors=True)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值