text: #!/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 <addr>         Set the PC to <addr> and start OTBN
  
      step                 Run one instruction. Print trace information to
                           stdout.
  
      run                  Run instructions until ecall or error. No trace
                           information.
  
      load_elf <path>      Load the ELF file at <path>, replacing current
                           contents of DMEM and IMEM.
  
      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)
  
  '''
  
  import sys
  from typing import List
  
  from sim.decode import decode_file
  from sim.elf import load_elf
  from sim.sim import OTBNSim
  
  
  def read_word(arg_name: str, word_data: str) -> int:
      '''Try to read a 32-bit unsigned word'''
      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 >> 32:
          raise ValueError('<{}> argument is {!r}: not representable as a u32.'
                           .format(arg_name, word_data))
  
      return value
  
  
  def end_command() -> None:
      '''Print a single '.' to stdout and flush, ending the output for command'''
      print('.')
      sys.stdout.flush()
  
  
  def on_start(sim: OTBNSim, args: List[str]) -> None:
      '''Jump to an address given as the (only) argument and start running'''
      if len(args) != 1:
          raise ValueError('start expects exactly 1 argument. Got {}.'
                           .format(args))
  
      addr = read_word('addr', args[0])
      if addr & 3:
          raise ValueError('start address must be word-aligned. Got {:#08x}.'
                           .format(addr))
  
      print('START {:#08x}'.format(addr))
      sim.state.start(addr)
  
  
  def on_step(sim: OTBNSim, args: List[str]) -> None:
      '''Step one instruction'''
      if len(args):
          raise ValueError('step expects zero arguments. Got {}.'
                           .format(args))
  
      pc = sim.state.pc
      assert 0 == pc & 3
  
      insn, changes = sim.step(verbose=False)
  
      if insn is None:
          hdr = 'STALL'
      else:
          hdr = 'E PC: {:#010x}, insn: {:#010x}'.format(pc, insn.raw)
      print(hdr)
      for change in changes:
          entry = change.rtl_trace()
          if entry is not None:
              print(entry)
  
  
  def on_run(sim: OTBNSim, args: List[str]) -> None:
      '''Run until ecall or error'''
      if len(args):
          raise ValueError('run expects zero arguments. Got {}.'
                           .format(args))
  
      num_cycles = sim.run(verbose=False)
      print(' ran for {} cycles'.format(num_cycles))
  
  
  def on_load_elf(sim: OTBNSim, args: List[str]) -> None:
      '''Load contents of ELF at path given by only argument'''
      if len(args) != 1:
          raise ValueError('load_elf expects exactly 1 argument. Got {}.'
                           .format(args))
      path = args[0]
  
      print('LOAD_ELF {!r}'.format(path))
      load_elf(sim, path)
  
  
  def on_load_d(sim: OTBNSim, args: List[str]) -> None:
      '''Load contents of data memory from file at path given by only argument'''
      if len(args) != 1:
          raise ValueError('load_d expects exactly 1 argument. Got {}.'
                           .format(args))
      path = args[0]
  
      print('LOAD_D {!r}'.format(path))
      with open(path, 'rb') as handle:
          sim.load_data(handle.read())
  
  
  def on_load_i(sim: OTBNSim, args: List[str]) -> None:
      '''Load contents of insn memory from file at path given by only argument'''
      if len(args) != 1:
          raise ValueError('load_i expects exactly 1 argument. Got {}.'
                           .format(args))
      path = args[0]
  
      print('LOAD_I {!r}'.format(path))
      sim.load_program(decode_file(0, path))
  
  
  def on_dump_d(sim: OTBNSim, args: List[str]) -> None:
      '''Dump contents of data memory to file at path given by only argument'''
      if len(args) != 1:
          raise ValueError('dump_d expects exactly 1 argument. Got {}.'
                           .format(args))
      path = args[0]
  
      print('DUMP_D {!r}'.format(path))
  
      with open(path, 'wb') as handle:
          handle.write(sim.state.dmem.dump_le_words())
  
  
  def on_print_regs(sim: OTBNSim, args: List[str]) -> None:
      '''Print registers to stdout'''
      if len(args):
          raise ValueError('print_regs expects zero arguments. Got {}.'
                           .format(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))
  
  
  def on_print_call_stack(sim: OTBNSim, args: List[str]) -> None:
      '''Print call stack to stdout. First element is the bottom of the stack'''
      if len(args):
          raise ValueError('print_call_stack expects zero arguments. Got {}.'
                           .format(args))
  
      print('PRINT_CALL_STACK')
      for value in sim.state.peek_call_stack():
          print('0x{:08x}'.format(value))
  
  
  _HANDLERS = {
      'start': on_start,
      'step': on_step,
      'run': on_run,
      'load_elf': on_load_elf,
      '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
  }
  
  
  def on_input(sim: OTBNSim, line: str) -> None:
      '''Process an input command'''
      words = line.split()
  
      # Just ignore empty lines
      if not words:
          return
  
      verb = words[0]
      handler = _HANDLERS.get(verb)
      if handler is None:
          raise RuntimeError('Unknown command: {!r}'.format(verb))
  
      handler(sim, words[1:])
      print('.')
      sys.stdout.flush()
  
  
  def main() -> int:
      sim = OTBNSim()
      try:
          for line in sys.stdin:
              on_input(sim, line)
      except KeyboardInterrupt:
          print("Received shutdown request, ending OTBN simulation.")
          return 0
      return 0
  
  
  if __name__ == '__main__':
      sys.exit(main())
  