[otbn] Allow multiple errors in a single cycle in ISS

This means we can't just raise an exception any more, which makes
things a little more fiddly. Instead, any component of the state that
might raise an error has an "errs" list, to which it appends an error
if it sees one. These lists get concatenated in a tree, in much the
same way as we gather up changes.

After executing an instruction, the code checks whether there were any
errors. If not, it commits pending changes. If there is an error, it
figures out the correct value of ERR_BITS and stops.

Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/hw/ip/otbn/dv/otbnsim/sim/insn.py b/hw/ip/otbn/dv/otbnsim/sim/insn.py
index 594abee..2f2cc61 100644
--- a/hw/ip/otbn/dv/otbnsim/sim/insn.py
+++ b/hw/ip/otbn/dv/otbnsim/sim/insn.py
@@ -4,7 +4,7 @@
 
 from typing import Dict
 
-from .alert import LoopError
+from .alert import ERR_CODE_NO_ERROR, LoopError
 from .flags import FlagReg
 from .isa import (DecodeError, OTBNInsn, RV32RegReg, RV32RegImm, RV32ImmShift,
                   insn_for_mnemonic, logical_byte_shift)
@@ -315,7 +315,7 @@
 
     def execute(self, state: OTBNState) -> None:
         # Set INTR_STATE.done and STATUS, reflecting the fact we've stopped.
-        state.stop(None)
+        state._stop(ERR_CODE_NO_ERROR)
 
 
 class LOOP(OTBNInsn):
@@ -330,8 +330,9 @@
     def execute(self, state: OTBNState) -> None:
         num_iters = state.gprs.get_reg(self.grs).read_unsigned()
         if num_iters == 0:
-            raise LoopError('loop count in x{} was zero'
-                            .format(self.grs))
+            state.on_error(LoopError('loop count in x{} was zero'
+                                     .format(self.grs)))
+            return
 
         state.loop_start(num_iters, self.bodysize)