[otbn,dv] Bad BNMOVR Instruction Snippet

This commit includes a bad bn.movr instruction that has out
of bounds register values or both increments at the same time

Signed-off-by: Canberk Topal <ctopal@lowrisc.org>
diff --git a/hw/ip/otbn/dv/rig/rig/configs/base.yml b/hw/ip/otbn/dv/rig/rig/configs/base.yml
index 51c9fba..a4c45a7 100644
--- a/hw/ip/otbn/dv/rig/rig/configs/base.yml
+++ b/hw/ip/otbn/dv/rig/rig/configs/base.yml
@@ -9,13 +9,14 @@
   Jump: 0.1
   Loop: 0.1
   LoopDupEnd: 0.01
-  SmallVal: 0.05
+  SmallVal: 0.2
   StraightLineInsn: 1.0
   KnownWDR: 0.05
 
   # Generators that end the program
   ECall: 1
   BadAtEnd: 1
+  BadBNMovr: 1
   BadDeepLoop: 1
   BadInsn: 1
   BadGiantLoop: 1
diff --git a/hw/ip/otbn/dv/rig/rig/gens/bad_bnmovr.py b/hw/ip/otbn/dv/rig/rig/gens/bad_bnmovr.py
new file mode 100644
index 0000000..db5737c
--- /dev/null
+++ b/hw/ip/otbn/dv/rig/rig/gens/bad_bnmovr.py
@@ -0,0 +1,111 @@
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+import random
+from typing import Optional
+
+from shared.operand import RegOperandType, OptionOperandType
+from shared.insn_yaml import InsnsFile
+
+from ..config import Config
+from ..model import Model
+from ..program import ProgInsn, Program
+
+from ..snippet import ProgSnippet
+from ..snippet_gen import GenCont, GenRet, SnippetGen
+
+
+class BadBNMovr(SnippetGen):
+    '''A snippet generator that generates program ending BN.MOVR instructions.
+
+    This includes incrementing both GPRs or having *grd or *grs > 31
+
+    '''
+
+    ends_program = True
+
+    def __init__(self, cfg: Config, insns_file: InsnsFile) -> None:
+        super().__init__()
+
+        self.insn = self._get_named_insn(insns_file, 'bn.movr')
+
+        # bn.movr expects the operands: grd, grs, grd_inc, grs_inc
+        if len(self.insn.operands) != 4:
+            raise RuntimeError('Unexpected number of operands for bn.movr')
+
+        grd, grs, grd_inc, grs_inc = self.insn.operands
+        exp_shape = (
+            # grd
+            isinstance(grd.op_type, RegOperandType) and
+            grd.op_type.reg_type == 'gpr' and
+            not grd.op_type.is_dest() and
+            # grs
+            isinstance(grs.op_type, RegOperandType) and
+            grs.op_type.reg_type == 'gpr' and
+            not grs.op_type.is_dest() and
+            # grd_inc
+            isinstance(grd_inc.op_type, OptionOperandType) and
+            # grs_inc
+            isinstance(grs_inc.op_type, OptionOperandType)
+        )
+        if not exp_shape:
+            raise RuntimeError('Unexpected shape for bn.movr')
+
+        self.weight = cfg.insn_weights.get(self.insn.mnemonic)
+
+        # Check that the instruction has a positive weight
+        if not self.weight:
+            self.disabled = True
+
+    def gen(self,
+            cont: GenCont,
+            model: Model,
+            program: Program) -> Optional[GenRet]:
+
+        # Get known registers
+        known_regs = model.regs_with_known_vals('gpr')
+        bad_regs = []
+        good_regs = []
+
+        # Make sure there is at least one out of bounds value
+        for reg_idx, reg_val in known_regs:
+            if 31 < reg_val:
+                bad_regs.append(reg_idx)
+            else:
+                good_regs.append(reg_idx)
+
+        # We have 4 different options for increments * 4 different options for
+        # out of bound register values.
+        # reg_val_choice[1:0] ->   grd_val, grs_val
+        # inc_choice[1:0]     ->   grd_inc, grs_inc
+        # bad = 1, good = 0
+        # choices[3:0] = {reg_val_choice, inc_choice}
+
+        # We always have an element (x0) in good_regs but if bad_regs is empty
+        # hard-code grd_val/grs_val = Good/Good and we have to use two incr.
+        # for producing an error.
+        choices = 3 if not bad_regs else random.randint(3, 15)
+
+        reg_val_choice = choices // 4
+        inc_choice = choices % 4
+
+        bad_grd = reg_val_choice in [2, 3]  # grd_val/grs_val = Bad/Good or Bad/Bad
+        bad_grs = reg_val_choice in [1, 3]  # grd_val/grs_val = Good/Bad or Bad/Bad
+
+        # If grs_val/grd_val = good/good, we have to force +/+ for a fault
+        grd_inc = int(inc_choice in [2, 3])
+        grs_inc = int(inc_choice in [1, 3])
+
+        op_val_grs = random.choice(bad_regs if bad_grs else good_regs)
+
+        op_val_grd = random.choice(bad_regs if bad_grd else good_regs)
+
+        op_val = [op_val_grd, op_val_grs, grd_inc, grs_inc]
+
+        prog_insn = ProgInsn(self.insn, op_val, None)
+
+        snippet = ProgSnippet(model.pc, [prog_insn])
+        snippet.insert_into_program(program)
+
+        return (snippet, True, model)
diff --git a/hw/ip/otbn/dv/rig/rig/snippet_gens.py b/hw/ip/otbn/dv/rig/rig/snippet_gens.py
index 67709c4..ba40914 100644
--- a/hw/ip/otbn/dv/rig/rig/snippet_gens.py
+++ b/hw/ip/otbn/dv/rig/rig/snippet_gens.py
@@ -24,6 +24,7 @@
 from .gens.straight_line_insn import StraightLineInsn
 
 from .gens.bad_at_end import BadAtEnd
+from .gens.bad_bnmovr import BadBNMovr
 from .gens.bad_deep_loop import BadDeepLoop
 from .gens.bad_insn import BadInsn
 from .gens.bad_giant_loop import BadGiantLoop
@@ -44,6 +45,7 @@
 
         ECall,
         BadAtEnd,
+        BadBNMovr,
         BadDeepLoop,
         BadInsn,
         BadGiantLoop,