blob: 0f3bd0540c6a900e32b3f4d10cc50ab4dd5bec38 [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
'''A simulator that runs one instruction at a time, reading from a REPL
The input language is simple (and intended to be generated by another program).
Input should appear with one command per line.
The valid commands are as follows. All arguments are shown here as <argname>.
The integer arguments are read with Python's int() function, so should be
prefixed with "0x" if they are hexadecimal.
start Set the PC to zero and start OTBN
configure Enable or disable the secure wipe machinery (this is a
temporary feature until everything works together)
step Run one instruction. Print trace information to
stdout.
load_elf <path> Load the ELF file at <path>, replacing current
contents of DMEM and IMEM.
add_loop_warp <addr> <from> <to>
Add a loop warp to the simulation. This will trigger
at address <addr> and will jump from iteration <from>
to iteration <to>.
clear_loop_warps Clear any loop warp rules
load_d <path> Replace the current contents of DMEM with <path>
(read as an array of 32-bit little-endian words)
load_i <path> Replace the current contents of IMEM with <path>
(read as an array of 32-bit little-endian words)
dump_d <path> Write the current contents of DMEM to <path> (same
format as for load).
print_regs Write the contents of all registers to stdout (in hex)
edn_rnd_step Send 32b RND Data to the model.
edn_rnd_cdc_done Finish the RND data write process by signalling RTL
is also finished processing 32b packages from EDN.
edn_urnd_step Send 32b URND seed data to the model.
edn_urnd_cdc_done Finish the URND resseding process by signalling RTL
is also finished processing 32b packages from EDN and
set the seed.
edn_flush Flush EDN data from model because of reset signal in
EDN clock domain
invalidate_imem Mark all of IMEM as having invalid ECC checksums
invalidate_dmem Mark all of DMEM as having invalid ECC checksums
set_keymgr_value Send keymgr data to the model.
step_crc Step CRC function with 48 bits of data. No actual
change of state (this is pure, but handled in Python
to simplify verification).
send_lc_escalation React to lc_escalate_en_i going high
'''
import binascii
import sys
from typing import List, Optional
from sim.decode import decode_file
from sim.load_elf import load_elf
from sim.sim import OTBNSim
def read_word(arg_name: str, word_data: str, bits: int) -> int:
'''Try to read an unsigned word of the specified bit length'''
try:
value = int(word_data, 0)
except ValueError:
raise ValueError('Failed to read {!r} as an integer for <{}> argument.'
.format(word_data, arg_name)) from None
if value < 0 or value >> bits:
raise ValueError('<{}> argument is {!r}: '
'not representable in {!r} bits.'
.format(arg_name, word_data, bits))
return value
def end_command() -> None:
'''Print a single '.' to stdout and flush, ending the output for command'''
print('.')
sys.stdout.flush()
def check_arg_count(cmd: str, cnt: int, args: List[str]) -> None:
if len(args) != cnt:
if cnt == 0:
txt_cnt = 'no arguments'
elif cnt == 1:
txt_cnt = 'exactly one argument'
else:
txt_cnt = f'exactly {cnt} arguments'
raise ValueError(f'{cmd} expects {txt_cnt} arguments. Got {args}.')
def on_start(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
'''Jump to an address given as the (only) argument and start running'''
check_arg_count('start', 0, args)
print('START')
sim.state.ext_regs.commit()
sim.start(collect_stats=False)
return None
def on_configure(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
check_arg_count('configure', 1, args)
enable = read_word('secure_wipe_en', args[0], 1) != 0
sim.configure(enable)
return None
def on_step(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
'''Step one instruction'''
check_arg_count('step', 0, args)
pc = sim.state.pc
assert 0 == pc & 3
was_wiping = sim.state.wiping() and sim.state.secure_wipe_enabled
insn, changes = sim.step(verbose=False)
if insn is not None:
hdr = insn.rtl_trace(pc) # type: Optional[str]
elif was_wiping:
# The trailing space is a bit naff but matches the behaviour in the RTL
# tracer, where it's rather difficult to change.
hdr = 'U ' if sim.state.wiping() else 'V '
elif (sim.state.running() or
(changes and not sim.state.secure_wipe_enabled)):
hdr = 'STALL'
else:
hdr = None
rtl_changes = []
for c in changes:
rt = c.rtl_trace()
if rt is not None:
rtl_changes.append(rt)
if hdr is None:
assert not rtl_changes
else:
print(hdr)
for rt in rtl_changes:
print(rt)
return None
def on_load_elf(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
'''Load contents of ELF at path given by only argument'''
check_arg_count('load_elf', 1, args)
path = args[0]
print('LOAD_ELF {!r}'.format(path))
load_elf(sim, path)
return None
def on_add_loop_warp(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
'''Add a loop warp to the simulation'''
check_arg_count('add_loop_warp', 3, args)
try:
addr = int(args[0], 0)
if addr < 0:
raise ValueError('addr is negative')
from_cnt = int(args[1], 0)
if from_cnt < 0:
raise ValueError('from_cnt is negative')
to_cnt = int(args[2], 0)
if to_cnt < 0:
raise ValueError('to_cnt is negative')
except ValueError as err:
raise ValueError('Bad argument to add_loop_warp: {}'
.format(err)) from None
print('ADD_LOOP_WARP {:#x} {} {}'.format(addr, from_cnt, to_cnt))
sim.add_loop_warp(addr, from_cnt, to_cnt)
return None
def on_clear_loop_warps(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
'''Run until ecall or error'''
check_arg_count('clear_loop_warps', 0, args)
sim.loop_warps = {}
return None
def on_load_d(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
'''Load contents of data memory from file at path given by only argument'''
check_arg_count('load_d', 1, args)
path = args[0]
print('LOAD_D {!r}'.format(path))
with open(path, 'rb') as handle:
sim.load_data(handle.read(), has_validity=True)
return None
def on_load_i(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
'''Load contents of insn memory from file at path given by only argument'''
check_arg_count('load_i', 1, args)
path = args[0]
print('LOAD_I {!r}'.format(path))
sim.load_program(decode_file(0, path))
return None
def on_dump_d(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
'''Dump contents of data memory to file at path given by only argument'''
check_arg_count('dump_d', 1, args)
path = args[0]
print('DUMP_D {!r}'.format(path))
with open(path, 'wb') as handle:
handle.write(sim.state.dmem.dump_le_words())
return None
def on_print_regs(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
'''Print registers to stdout'''
check_arg_count('print_regs', 0, args)
print('PRINT_REGS')
for idx, value in enumerate(sim.state.gprs.peek_unsigned_values()):
print(' x{:<2} = 0x{:08x}'.format(idx, value))
for idx, value in enumerate(sim.state.wdrs.peek_unsigned_values()):
print(' w{:<2} = 0x{:064x}'.format(idx, value))
return None
def on_print_call_stack(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
'''Print call stack to stdout. First element is the bottom of the stack'''
check_arg_count('print_call_stack', 0, args)
print('PRINT_CALL_STACK')
for value in sim.state.peek_call_stack():
print('0x{:08x}'.format(value))
return None
def on_reset(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
if args:
raise ValueError('reset expects zero arguments. Got {}.'
.format(args))
return OTBNSim()
def on_edn_rnd_step(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
if len(args) != 1:
raise ValueError('edn_rnd_step expects exactly 1 argument. Got {}.'
.format(args))
edn_rnd_data = read_word('edn_rnd_step', args[0], 32)
sim.state.edn_rnd_step(edn_rnd_data)
return None
def on_edn_urnd_step(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
if len(args) != 1:
raise ValueError('edn_urnd_step expects exactly 1 argument. Got {}.'
.format(args))
edn_urnd_data = read_word('edn_urnd_step', args[0], 32)
sim.state.edn_urnd_step(edn_urnd_data)
return None
def on_edn_flush(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
if len(args) != 0:
raise ValueError('edn_flush expects zero arguments. Got {}.'
.format(args))
sim.state.edn_flush()
return None
def on_edn_urnd_cdc_done(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
if len(args) != 0:
raise ValueError('edn_urnd_cdc_done expects zero arguments. Got {}.'
.format(args))
sim.state.urnd_completed()
return None
def on_edn_rnd_cdc_done(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
check_arg_count('edn_rnd_cdc_done', 0, args)
sim.state.rnd_completed()
return None
def on_invalidate_imem(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
check_arg_count('invalidate_imem', 0, args)
sim.state.invalidate_imem()
return None
def on_invalidate_dmem(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
check_arg_count('invalidate_dmem', 0, args)
sim.state.dmem.empty_dmem()
return None
def on_set_keymgr_value(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
if len(args) != 3:
raise ValueError('set_keymgr_value expects exactly 1 argument. Got {}.'
.format(args))
key0 = read_word('key0', args[0], 384)
key1 = read_word('key1', args[1], 384)
valid = read_word('valid', args[2], 1) == 1
sim.state.wsrs.set_sideload_keys(key0 if valid else None,
key1 if valid else None)
return None
def on_step_crc(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
check_arg_count('step_crc', 2, args)
item = read_word('item', args[0], 48)
state = read_word('state', args[1], 32)
new_state = binascii.crc32(item.to_bytes(6, 'little'), state)
print(f'! otbn.LOAD_CHECKSUM: 0x{new_state:08x}')
return None
def on_send_lc_escalation(sim: OTBNSim, args: List[str]) -> Optional[OTBNSim]:
check_arg_count('send_lc_escalation', 0, args)
sim.on_lc_escalation()
return None
_HANDLERS = {
'start': on_start,
'configure': on_configure,
'step': on_step,
'load_elf': on_load_elf,
'add_loop_warp': on_add_loop_warp,
'clear_loop_warps': on_clear_loop_warps,
'load_d': on_load_d,
'load_i': on_load_i,
'dump_d': on_dump_d,
'print_regs': on_print_regs,
'print_call_stack': on_print_call_stack,
'reset': on_reset,
'edn_rnd_step': on_edn_rnd_step,
'edn_urnd_step': on_edn_urnd_step,
'edn_rnd_cdc_done': on_edn_rnd_cdc_done,
'edn_urnd_cdc_done': on_edn_urnd_cdc_done,
'edn_flush': on_edn_flush,
'invalidate_imem': on_invalidate_imem,
'invalidate_dmem': on_invalidate_dmem,
'set_keymgr_value': on_set_keymgr_value,
'step_crc': on_step_crc,
'send_lc_escalation': on_send_lc_escalation
}
def on_input(sim: OTBNSim, line: str) -> Optional[OTBNSim]:
'''Process an input command'''
words = line.split()
# Just ignore empty lines
if not words:
return None
verb = words[0]
handler = _HANDLERS.get(verb)
if handler is None:
raise RuntimeError('Unknown command: {!r}'.format(verb))
ret = handler(sim, words[1:])
print('.')
sys.stdout.flush()
return ret
def main() -> int:
sim = OTBNSim()
try:
for line in sys.stdin:
ret = on_input(sim, line)
if ret is not None:
sim = ret
except KeyboardInterrupt:
print("Received shutdown request, ending OTBN simulation.")
return 0
return 0
if __name__ == '__main__':
sys.exit(main())