[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
}