blob: 0c2daa00e6c216fc27ba8939f86cae4403126b8e [file] [log] [blame]
# 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'