[otbn,dv] Add a "small value" snippet generator

This generates small values in the range [-5, 5] and writes them to
GPRs. The idea is that it should make it much easier to generate
things like LOOP instructions because now we have more values
available to be used as iteration counts.

Signed-off-by: Rupert Swarbrick <rswarbrick@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 17ddbea..a42c545 100644
--- a/hw/ip/otbn/dv/rig/rig/configs/base.yml
+++ b/hw/ip/otbn/dv/rig/rig/configs/base.yml
@@ -7,6 +7,7 @@
   Branch: 0.1
   Jump: 0.1
   Loop: 0.1
+  SmallVal: 0.05
   StraightLineInsn: 1.0
 
   # Generators that end the program
diff --git a/hw/ip/otbn/dv/rig/rig/gens/small_val.py b/hw/ip/otbn/dv/rig/rig/gens/small_val.py
new file mode 100644
index 0000000..ea7c3c4
--- /dev/null
+++ b/hw/ip/otbn/dv/rig/rig/gens/small_val.py
@@ -0,0 +1,82 @@
+# 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 ImmOperandType, RegOperandType
+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 SmallVal(SnippetGen):
+    '''A snippet generator that generates writes small values to registers
+
+    We do so using addi (which is modelled fully in model.py), which means we
+    can then use those small values for later instructions: useful for things
+    like loop counters.
+
+    '''
+
+    def __init__(self, cfg: Config, insns_file: InsnsFile) -> None:
+        super().__init__()
+
+        if 'addi' not in insns_file.mnemonic_to_insn:
+            raise RuntimeError('ADDI instruction not in instructions file')
+        self.insn = insns_file.mnemonic_to_insn['addi']
+
+        # ADDI has three operands: grd, grs1 and imm.
+        if not (len(self.insn.operands) == 3 and
+                isinstance(self.insn.operands[0].op_type, RegOperandType) and
+                self.insn.operands[0].op_type.reg_type == 'gpr' and
+                isinstance(self.insn.operands[1].op_type, RegOperandType) and
+                self.insn.operands[1].op_type.reg_type == 'gpr' and
+                isinstance(self.insn.operands[2].op_type, ImmOperandType) and
+                self.insn.operands[2].op_type.signed):
+            raise RuntimeError('ADDI instruction from instructions file is not '
+                               'the shape expected by the SmallVal generator.')
+
+        self.grd_op_type = self.insn.operands[0].op_type
+        self.grs1_op_type = self.insn.operands[1].op_type
+        self.imm_op_type = self.insn.operands[2].op_type
+
+    def gen(self,
+            cont: GenCont,
+            model: Model,
+            program: Program) -> Optional[GenRet]:
+        # Return None if this is the last instruction in the current gap
+        # because we need to either jump or do an ECALL to avoid getting stuck.
+        if program.get_insn_space_at(model.pc) <= 1:
+            return None
+
+        # Pick grd any old way: we can write to any register. This should
+        # always succeed.
+        grd_val = model.pick_operand_value(self.grd_op_type)
+        assert grd_val is not None
+
+        # Pick a target value. For now, we take arbitrary values in the range
+        # [-5, 5].
+        tgt_val = random.randint(-5, 5)
+
+        # We'll use x0 as the register source. Since the register source has
+        # value zero, we need -tgt_val as our immediate. The small range of
+        # possible target values should mean this is always representable.
+        imm_encoded = self.imm_op_type.op_val_to_enc_val(-tgt_val, model.pc)
+        assert imm_encoded is not None
+
+        op_vals = [grd_val, 0, imm_encoded]
+
+        prog_insn = ProgInsn(self.insn, op_vals, None)
+        snippet = ProgSnippet(model.pc, [prog_insn])
+        snippet.insert_into_program(program)
+
+        model.update_for_insn(prog_insn)
+        model.pc += 4
+
+        return (snippet, False, model)
diff --git a/hw/ip/otbn/dv/rig/rig/snippet_gens.py b/hw/ip/otbn/dv/rig/rig/snippet_gens.py
index 6237dbe..7f84c88 100644
--- a/hw/ip/otbn/dv/rig/rig/snippet_gens.py
+++ b/hw/ip/otbn/dv/rig/rig/snippet_gens.py
@@ -17,6 +17,7 @@
 from .gens.ecall import ECall
 from .gens.jump import Jump
 from .gens.loop import Loop
+from .gens.small_val import SmallVal
 from .gens.straight_line_insn import StraightLineInsn
 
 from .gens.bad_insn import BadInsn
@@ -29,6 +30,7 @@
         Branch,
         Jump,
         Loop,
+        SmallVal,
         StraightLineInsn,
 
         ECall,