blob: 61023f7422a95d3df7f6bd73ef3f2b84c550257e [file] [log] [blame]
#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
'''Run a test on the OTBN simulator.'''
import argparse
import subprocess
import sys
from enum import IntEnum
from typing import List
from shared.check import CheckResult
from shared.reg_dump import parse_reg_dump
# Names of special registers
ERR_BITS = 'ERR_BITS'
INSN_CNT = 'INSN_CNT'
STOP_PC = 'STOP_PC'
# copied from hw/ip/otbn/dv/otbnsim/sim/constants.py
class ErrBits(IntEnum):
'''A copy of the list of bits in the ERR_BITS register.'''
BAD_DATA_ADDR = 1 << 0
BAD_INSN_ADDR = 1 << 1
CALL_STACK = 1 << 2
ILLEGAL_INSN = 1 << 3
LOOP = 1 << 4
KEY_INVALID = 1 << 5
RND_REP_CHK_FAIL = 1 << 6
RND_FIPS_CHK_FAIL = 1 << 7
IMEM_INTG_VIOLATION = 1 << 16
DMEM_INTG_VIOLATION = 1 << 17
REG_INTG_VIOLATION = 1 << 18
BUS_INTG_VIOLATION = 1 << 19
BAD_INTERNAL_STATE = 1 << 20
ILLEGAL_BUS_ACCESS = 1 << 21
LIFECYCLE_ESCALATION = 1 << 22
FATAL_SOFTWARE = 1 << 23
def get_err_names(err: int) -> List[str]:
'''Get the names of all error bits that are set.'''
out = []
for err_bit in ErrBits:
if err & err_bit != 0:
out.append(err_bit.name)
return out
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument('simulator',
help='Path to the standalone OTBN simulator.')
parser.add_argument('expected',
metavar='FILE',
type=argparse.FileType('r'),
help=(f'File containing expected register values. '
f'Registers that are not listed are allowed to '
f'have any value, except for {ERR_BITS}. If '
f'{ERR_BITS} is not listed, the test will assume '
f'there are no errors expected (i.e. {ERR_BITS}'
f'= 0).'))
parser.add_argument('elf',
help='Path to the .elf file for the OTBN program.')
parser.add_argument('-v', '--verbose', action='store_true')
args = parser.parse_args()
# Parse expected values.
result = CheckResult()
expected_regs = parse_reg_dump(args.expected.read())
# Run the simulation and produce a register dump.
cmd = [args.simulator, '--dump-regs', '-', args.elf]
sim_proc = subprocess.run(cmd, check=True,
stdout=subprocess.PIPE, universal_newlines=True)
actual_regs = parse_reg_dump(sim_proc.stdout)
# Special handling for the ERR_BITS register.
expected_err = expected_regs.get(ERR_BITS, 0)
actual_err = actual_regs[ERR_BITS]
insn_cnt = actual_regs[INSN_CNT]
stop_pc = actual_regs[STOP_PC]
if expected_err == 0 and actual_err != 0:
# Test is expected to have no errors, but an error occurred. In this
# case, give a special error message and exit rather than print all the
# mismatched registers.
if actual_err != 0:
err_names = ', '.join(get_err_names(actual_err))
result.err(f'OTBN encountered an unexpected error: {err_names}.\n'
f' {ERR_BITS}\t= {actual_err:#010x}\n'
f' {INSN_CNT}\t= {insn_cnt:#010x}\n'
f' {STOP_PC}\t= {stop_pc:#010x}')
else:
for reg, expected_value in expected_regs.items():
actual_value = actual_regs.get(reg, None)
if actual_value != expected_value:
if reg.startswith('w'):
expected_str = f'{expected_value:#066x}'
actual_str = f'{actual_value:#066x}'
else:
expected_str = f'{expected_value:#010x}'
actual_str = f'{actual_value:#010x}'
result.err(f'Mismatch for register {reg}:\n'
f' Expected: {expected_str}\n'
f' Actual: {actual_str}')
if result.has_errors() or result.has_warnings() or args.verbose:
print(result.report())
return 1 if result.has_errors() else 0
if __name__ == "__main__":
sys.exit(main())