[otbn] Add some convenience commands to stepped.py

These allow you to run a simulation and print out the resulting
registers with something like:

  echo -e "load_elf bug\nstart 0\nrun\nprint_regs\n" | \
    hw/ip/otbn/dv/otbnsim/stepped.py

which should make it easy to write some simple "golden model" tests.

Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/hw/ip/otbn/dv/otbnsim/sim/elf.py b/hw/ip/otbn/dv/otbnsim/sim/elf.py
index 226908e..f293f73 100644
--- a/hw/ip/otbn/dv/otbnsim/sim/elf.py
+++ b/hw/ip/otbn/dv/otbnsim/sim/elf.py
@@ -8,11 +8,10 @@
 
 from elftools.elf.elffile import ELFFile  # type: ignore
 
-from riscvmodel.sim import Simulator  # type: ignore
-
 from shared.mem_layout import get_memory_layout
 
 from .decode import decode_bytes
+from .sim import OTBNSim
 
 _SegList = List[Tuple[int, bytes]]
 
@@ -117,7 +116,7 @@
     return (imem_segments, dmem_segments)
 
 
-def load_elf(sim: Simulator, path: str) -> None:
+def load_elf(sim: OTBNSim, path: str) -> None:
     '''Load contents of ELF file at path'''
     mems = get_memory_layout()
     imem_desc = mems['IMEM']
diff --git a/hw/ip/otbn/dv/otbnsim/sim/reg.py b/hw/ip/otbn/dv/otbnsim/sim/reg.py
index 4b6ed47..d8b8ecb 100644
--- a/hw/ip/otbn/dv/otbnsim/sim/reg.py
+++ b/hw/ip/otbn/dv/otbnsim/sim/reg.py
@@ -34,15 +34,15 @@
         self._uval = uval
         self._next_uval = None  # type: Optional[int]
 
-    def read_unsigned(self) -> int:
-        if self._parent is not None:
+    def read_unsigned(self, backdoor: bool = False) -> int:
+        if not backdoor and self._parent is not None:
             self._parent.mark_read(self._idx)
         return self._uval
 
-    def write_unsigned(self, uval: int) -> None:
+    def write_unsigned(self, uval: int, backdoor: bool = False) -> None:
         assert 0 <= uval < (1 << self._width)
         self._next_uval = uval
-        if self._parent is not None:
+        if not backdoor and self._parent is not None:
             self._parent.mark_written(self._idx)
 
     def read_next(self) -> Optional[int]:
@@ -117,3 +117,7 @@
             assert 0 <= idx < len(self._registers)
             self._registers[idx].commit()
         self._pending_writes.clear()
+
+    def peek_unsigned_values(self) -> List[int]:
+        '''Get a list of the (unsigned) values of the registers'''
+        return [reg.read_unsigned(backdoor=True) for reg in self._registers]
diff --git a/hw/ip/otbn/dv/otbnsim/sim/sim.py b/hw/ip/otbn/dv/otbnsim/sim/sim.py
index 3a2dcf5..baa7d3f 100644
--- a/hw/ip/otbn/dv/otbnsim/sim/sim.py
+++ b/hw/ip/otbn/dv/otbnsim/sim/sim.py
@@ -19,14 +19,12 @@
     def load_data(self, data: bytes) -> None:
         self.state.dmem.load_le_words(data)
 
-    def run(self, start_addr: int, verbose: bool) -> int:
-        '''Start a simulation at start_addr and run until ECALL.
+    def run(self, verbose: bool) -> int:
+        '''Run until ECALL.
 
-        Return the number of instructions executed.
+        Return the number of cycles taken.
 
         '''
-        self.state.pc = start_addr
-        self.state.start()
         insn_count = 0
         while self.state.running:
             self.step(verbose)
diff --git a/hw/ip/otbn/dv/otbnsim/standalone.py b/hw/ip/otbn/dv/otbnsim/standalone.py
index 32f9619..bcfc8aa 100755
--- a/hw/ip/otbn/dv/otbnsim/standalone.py
+++ b/hw/ip/otbn/dv/otbnsim/standalone.py
@@ -21,7 +21,9 @@
     sim = OTBNSim()
     load_elf(sim, args.elf)
 
-    sim.run(start_addr=0, verbose=args.verbose)
+    sim.state.pc = 0
+    sim.state.start()
+    sim.run(verbose=args.verbose)
 
     if args.dmem_dump is not None:
         try:
diff --git a/hw/ip/otbn/dv/otbnsim/stepped.py b/hw/ip/otbn/dv/otbnsim/stepped.py
index 4d3dec2..36f9ccd 100755
--- a/hw/ip/otbn/dv/otbnsim/stepped.py
+++ b/hw/ip/otbn/dv/otbnsim/stepped.py
@@ -17,6 +17,12 @@
     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)
 
@@ -26,12 +32,15 @@
     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
 
 
@@ -88,6 +97,27 @@
         print('  {}'.format(trace))
 
 
+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:
@@ -124,12 +154,28 @@
         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))
+
+
 _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
+    'dump_d': on_dump_d,
+    'print_regs': on_print_regs
 }