| # Copyright lowRISC contributors. | |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| import logging | |
| import re | |
| import subprocess | |
| from pathlib import Path | |
| import pytest | |
| from .. import config, utils | |
| log = logging.getLogger(__name__) | |
| class VerilatorSimEarlgrey: | |
| UART0_SPEED = 9600 # see device/lib/arch/device_sim_verilator.c | |
| def __init__(self, sim_path: Path, rom_elf_path: Path, work_dir: Path): | |
| """ A verilator simulation of the Earl Grey toplevel """ | |
| assert sim_path.is_file() | |
| self._sim_path = sim_path | |
| assert rom_elf_path.is_file() | |
| self._rom_elf_path = rom_elf_path | |
| assert work_dir.is_dir() | |
| self._work_dir = work_dir | |
| self._log = logging.getLogger(__name__) | |
| self._uart0_log = None | |
| self.uart0_log_path = None | |
| self.spi0_log_path = None | |
| def run(self, flash_elf=None, extra_sim_args=[]): | |
| """ | |
| Run the simulation | |
| """ | |
| self.uart0_log_path = self._work_dir / 'uart0.log' | |
| cmd_sim = [ | |
| self._sim_path, '--meminit=rom,' + str(self._rom_elf_path), | |
| '+UARTDPI_LOG_uart0=' + str(self.uart0_log_path) | |
| ] | |
| cmd_sim += extra_sim_args | |
| if flash_elf is not None: | |
| assert flash_elf.is_file() | |
| cmd_sim.append('--meminit=flash,' + str(flash_elf)) | |
| self.p_sim = utils.Process(cmd_sim, | |
| logdir=self._work_dir, | |
| cwd=self._work_dir, | |
| startup_done_expect='Simulation running', | |
| startup_timeout=10) | |
| self.p_sim.run() | |
| self._log.info("Simulation running") | |
| # Find paths to simulated I/O devices | |
| # UART | |
| self._uart0 = None | |
| uart0_match = self.p_sim.find_in_output( | |
| re.compile(r'UART: Created (/dev/pts/\d+) for uart0\.'), | |
| timeout=1, | |
| from_start=True) | |
| assert uart0_match is not None | |
| self.uart0_device_path = Path(uart0_match.group(1)) | |
| assert self.uart0_device_path.is_char_device() | |
| self._log.debug("Found uart0 device file at {}".format( | |
| str(self.uart0_device_path))) | |
| assert self.uart0_log_path.is_file() | |
| self._uart0_log = open(str(self.uart0_log_path), 'rb') | |
| # SPI | |
| spi0_match = self.p_sim.find_in_output( | |
| re.compile(r'SPI: Created (/dev/pts/\d+) for spi0\.'), | |
| timeout=1, | |
| from_start=True) | |
| assert spi0_match is not None | |
| self.spi0_device_path = Path(spi0_match.group(1)) | |
| assert self.spi0_device_path.is_char_device() | |
| self._log.debug("Found spi0 device file at {}".format( | |
| str(self.spi0_device_path))) | |
| self.spi0_log_path = self._work_dir / 'spi0.log' | |
| assert self.spi0_log_path.is_file() | |
| # GPIO | |
| self.gpio0_fifo_write_path = self._work_dir / 'gpio0-write' | |
| assert self.gpio0_fifo_write_path.is_fifo() | |
| self.gpio0_fifo_read_path = self._work_dir / 'gpio0-read' | |
| assert self.gpio0_fifo_read_path.is_fifo() | |
| self._log.info("Simulation startup completed.") | |
| def uart0(self): | |
| if self._uart0 is None: | |
| log_dir_path = self._work_dir / 'uart0' | |
| log_dir_path.mkdir() | |
| log.info("Opening UART on device {} ({} baud)".format( | |
| str(self.uart0_device_path), self.UART0_SPEED)) | |
| self._uart0 = utils.LoggingSerial( | |
| str(self.uart0_device_path), | |
| self.UART0_SPEED, | |
| timeout=1, | |
| log_dir_path=log_dir_path, | |
| default_filter_func=utils.filter_remove_device_sw_log_prefix) | |
| return self._uart0 | |
| def terminate(self): | |
| """ Gracefully terminate the simulation """ | |
| if self._uart0 is not None: | |
| self._uart0.close() | |
| if self._uart0_log is not None: | |
| self._uart0_log.close() | |
| self.p_sim.send_ctrl_c() | |
| # Give the process some time to clean up | |
| self.p_sim.proc.wait(timeout=5) | |
| assert self.p_sim.proc.returncode == 0 | |
| try: | |
| self.p_sim.terminate() | |
| except ProcessLookupError: | |
| # process is already dead | |
| pass | |
| def find_in_uart0(self, | |
| pattern, | |
| timeout, | |
| filter_func=None, | |
| from_start=False): | |
| assert self._uart0_log | |
| if filter_func is None: | |
| # The default filter function for all device software in OpenTitan. | |
| filter_func = utils.filter_remove_device_sw_log_prefix | |
| try: | |
| return utils.find_in_files([self._uart0_log], | |
| pattern, | |
| timeout, | |
| filter_func=filter_func, | |
| from_start=from_start) | |
| except subprocess.TimeoutExpired: | |
| return None | |
| @pytest.fixture(params=config.TEST_APPS_SELFCHECKING, | |
| ids=lambda param: param['name']) | |
| def app_selfchecking(request, bin_dir): | |
| """ A self-checking device application for Verilator simulation | |
| Returns: | |
| A set (elf_path, verilator_extra_args) | |
| """ | |
| app_config = request.param | |
| if 'name' not in app_config: | |
| raise RuntimeError("Key 'name' not found in TEST_APPS_SELFCHECKING") | |
| if 'targets' in app_config and 'sim_verilator' not in app_config['targets']: | |
| pytest.skip("Test %s skipped on Verilator." % app_config['name']) | |
| if 'binary_name' in app_config: | |
| binary_name = app_config['binary_name'] | |
| else: | |
| binary_name = app_config['name'] | |
| if 'verilator_extra_args' in app_config: | |
| verilator_extra_args = app_config['verilator_extra_args'] | |
| else: | |
| verilator_extra_args = [] | |
| test_filename = binary_name + '_sim_verilator.elf' | |
| bin_path = bin_dir / 'sw/device/tests' / test_filename | |
| assert bin_path.is_file() | |
| return (bin_path, verilator_extra_args) | |
| # The following tests use the UART output from the log file written by the | |
| # UARTDPI module, and not the simulated UART device (/dev/pty/N) to ensure | |
| # reliable testing: As soon as the device application finishes, the simulation | |
| # ends and the UART device becomes unavailable to readers. | |
| # Therefore, if we do not read quickly enough, we miss the PASS/FAIL indication | |
| # and the test never finishes. | |
| def test_apps_selfchecking(tmp_path, bin_dir, app_selfchecking): | |
| """ | |
| Run a self-checking application on a Earl Grey Verilator simulation | |
| The ROM is initialized with the default boot ROM, the flash is initialized | |
| with |app_selfchecking|. | |
| Self-checking applications are expected to return PASS or FAIL in the end. | |
| """ | |
| sim_path = bin_dir / "hw/top_earlgrey/Vtop_earlgrey_verilator" | |
| rom_elf_path = bin_dir / "sw/device/boot_rom/boot_rom_sim_verilator.elf" | |
| sim = VerilatorSimEarlgrey(sim_path, rom_elf_path, tmp_path) | |
| sim.run(app_selfchecking[0], extra_sim_args=app_selfchecking[1]) | |
| bootmsg_exp = b'Boot ROM initialisation has completed, jump into flash!' | |
| assert sim.find_in_uart0( | |
| bootmsg_exp, | |
| timeout=120) is not None, "End-of-bootrom string not found." | |
| log.debug("Waiting for pass string from device test") | |
| result_match = sim.find_in_uart0(re.compile(rb'^(PASS|FAIL)!$'), | |
| timeout=600) | |
| assert result_match is not None, "PASS/FAIL indication not found in test output." | |
| result_msg = result_match.group(1) | |
| log.info("Test ended with {}".format(result_msg)) | |
| assert result_msg == b'PASS' | |
| sim.terminate() | |
| @pytest.mark.skip( | |
| reason="Spiflash on Verilator isn't reliable currently. See issue #3708.") | |
| def test_spiflash(tmp_path, bin_dir): | |
| """ Load a single application to the Verilator simulation using spiflash """ | |
| sim_path = bin_dir / "hw/top_earlgrey/Vtop_earlgrey_verilator" | |
| rom_elf_path = bin_dir / "sw/device/boot_rom/boot_rom_sim_verilator.elf" | |
| sim = VerilatorSimEarlgrey(sim_path, rom_elf_path, tmp_path) | |
| sim.run() | |
| log.debug("Waiting for simulation to be ready for SPI input") | |
| spiwait_msg = b'HW initialisation completed, waiting for SPI input...' | |
| assert sim.find_in_uart0(spiwait_msg, timeout=120) | |
| log.debug("SPI is ready, continuing with spiload") | |
| app_bin = bin_dir / 'sw/device/tests/dif_uart_smoketest_sim_verilator.bin' | |
| spiflash = bin_dir / 'sw/host/spiflash/spiflash' | |
| utils.load_sw_over_spi(tmp_path, spiflash, app_bin, | |
| ['--verilator', sim.spi0_device_path]) | |
| bootmsg_exp = b'Boot ROM initialisation has completed, jump into flash!' | |
| assert sim.find_in_uart0( | |
| bootmsg_exp, | |
| timeout=600) is not None, "End-of-bootrom string not found." | |
| log.debug("Waiting for pass string from device test") | |
| result_match = sim.find_in_uart0(re.compile(rb'^(PASS|FAIL)!$'), | |
| timeout=240) | |
| assert result_match is not None, "PASS/FAIL indication not found in test output." | |
| result_msg = result_match.group(1) | |
| log.info("Test ended with {}".format(result_msg)) | |
| assert result_msg == b'PASS' | |
| sim.terminate() |