| # 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 |
| |
| from . import utils |
| |
| log = logging.getLogger(__name__) |
| |
| |
| class VerilatorSimOpenTitan: |
| UART0_SPEED = 7200 # see device/lib/arch/device_sim_verilator.c |
| |
| def __init__(self, |
| sim_path: Path, |
| rom_vmem_path: Path, |
| otp_img_path: Path, |
| work_dir: Path, |
| boot_timeout: int = 120, |
| test_timeout: int = 600): |
| """ A verilator simulation of an OpenTitan toplevel """ |
| assert sim_path.is_file() |
| self._sim_path = sim_path |
| |
| self.p_sim = None |
| |
| assert rom_vmem_path.is_file() |
| self._rom_vmem_path = rom_vmem_path |
| assert otp_img_path is None or otp_img_path.is_file() |
| self._otp_img_path = otp_img_path |
| |
| assert work_dir.is_dir() |
| self._work_dir = work_dir |
| |
| self.boot_timeout = boot_timeout |
| self.test_timeout = test_timeout |
| |
| 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' |
| |
| if self._otp_img_path is None: |
| cmd_sim = [ |
| self._sim_path, '--meminit=rom,' + str(self._rom_vmem_path), |
| '+UARTDPI_LOG_uart0=' + str(self.uart0_log_path) |
| ] |
| else: |
| cmd_sim = [ |
| self._sim_path, '--meminit=rom,' + str(self._rom_vmem_path), |
| '--meminit=otp,' + str(self._otp_img_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 (through the simulated terminal) |
| 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))) |
| |
| # UART0 as logged directly to file by the uartdpi module. |
| 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_output(self, |
| pattern, |
| timeout, |
| filter_func=None, |
| from_start=False): |
| """ Find a pattern in STDOUT or STDERR of the Verilator simulation """ |
| assert self.p_sim |
| |
| return self.p_sim.find_in_output(pattern, |
| timeout, |
| from_start=from_start, |
| filter_func=filter_func) |
| |
| def find_in_uart0(self, |
| pattern, |
| timeout, |
| filter_func=utils.filter_remove_device_sw_log_prefix, |
| from_start=False): |
| assert self._uart0_log |
| |
| try: |
| return utils.find_in_files([self._uart0_log], |
| pattern, |
| timeout, |
| filter_func=filter_func, |
| from_start=from_start) |
| except subprocess.TimeoutExpired: |
| return None |
| |
| |
| # 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 assert_selfchecking_test_passes(sim): |
| assert sim.find_in_output( |
| re.compile(r"SW test transitioned to SwTestStatusInTest.$"), |
| timeout=sim.boot_timeout, |
| filter_func=utils.filter_remove_sw_test_status_log_prefix |
| ) is not None, "Start of test indication not found." |
| |
| log.debug("Waiting for pass string from device test") |
| |
| result_match = sim.find_in_output( |
| re.compile(r'^==== SW TEST (PASSED|FAILED) ====$'), |
| timeout=sim.test_timeout, |
| filter_func=utils.filter_remove_sw_test_status_log_prefix) |
| assert result_match is not None, "PASSED/FAILED indication not found in test output." |
| |
| result_msg = result_match.group(1) |
| log.info("Test ended with {}".format(result_msg)) |
| assert result_msg == 'PASSED' |