[otbn, dv] Generate instructions with bad CSR / WSR addresses.

Following additions/ modifications are done in this commit:
1.bad_ispr.py: A new snippet generator that picks up instructions only
related to CSR/ WSR registers and replaces the good register address
with bad address.
2.known_mem.py: A method called pick_bad_addr is added to pick an
address from the gaps in known memory.

Signed-off-by: Prajwala Puttappa <prajwalaputtappa@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 3fece62..3ffe4f2 100644
--- a/hw/ip/otbn/dv/rig/rig/configs/base.yml
+++ b/hw/ip/otbn/dv/rig/rig/configs/base.yml
@@ -27,3 +27,4 @@
   BadGiantLoop: 1
   BadZeroLoop: 1
   MisalignedLoadStore: 1
+  BadIspr: 1
diff --git a/hw/ip/otbn/dv/rig/rig/gens/bad_ispr.py b/hw/ip/otbn/dv/rig/rig/gens/bad_ispr.py
new file mode 100644
index 0000000..d22dc10
--- /dev/null
+++ b/hw/ip/otbn/dv/rig/rig/gens/bad_ispr.py
@@ -0,0 +1,97 @@
+# 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, Tuple
+
+from shared.insn_yaml import InsnsFile
+
+from ..config import Config
+from ..program import ProgInsn, Program
+from ..model import Model
+from ..snippet import ProgSnippet
+from ..snippet_gen import GenCont, GenRet, SnippetGen
+from .straight_line_insn import StraightLineInsn
+
+
+class BadIspr(SnippetGen):
+    ''' A snippet to generate load and store instructions to bad CSR/ WSR adresses '''
+
+    ends_program = True
+
+    def __init__(self, cfg: Config, insns_file: InsnsFile) -> None:
+        super().__init__()
+
+        self.insns = []
+        self.weights = []
+        self.straightgen = StraightLineInsn(cfg, insns_file)
+
+        for insn in insns_file.insns:
+            # Pick only the instructions pertaining to CSR and WSR.
+            if insn.mnemonic not in ['csrrw', 'csrrs', 'wsrs', 'wsrw']:
+                continue
+
+            assert not (insn.python_pseudo_op or insn.literal_pseudo_op)
+
+            # The instructions we're interested in are all straight-line
+            assert insn.straight_line
+
+            weight = cfg.insn_weights.get(insn.mnemonic)
+            if weight > 0:
+                self.insns.append(insn)
+                self.weights.append(weight)
+
+        # Disable the generator if no instruction has a positive weight
+        assert len(self.insns) == len(self.weights)
+        if not self.insns:
+            self.disabled = True
+
+    def gen(self,
+            cont: GenCont,
+            model: Model,
+            program: Program) -> Optional[GenRet]:
+        pc = model.pc
+        ret = self._gen(model, program)
+        if ret is None:
+            return None
+
+        prog_insn, model = ret
+        snippet = ProgSnippet(pc, [prog_insn])
+        snippet.insert_into_program(program)
+
+        return (snippet, True, model)
+
+    def _gen(self,
+             model: Model,
+             program: Program) -> Optional[Tuple[ProgInsn, Model]]:
+
+        weights = self.weights
+        prog_insn = None
+        while prog_insn is None:
+            idx = random.choices(range(len(self.insns)), weights=weights)[0]
+
+            # weights[idx] will be positive so long as at least one instruction has positive weight.
+            # If it's zero, it means that every weight is zero and we should give up.
+            if weights[idx] < 0:
+                return None
+
+            # Try to fill out the instruction. On failure, clear the weight for
+            # this index and go around again. We take the copy here, rather
+            # than outside the loop, because we don't expect this to happen
+            # very often.
+            prog_insn = self.straightgen.fill_insn(self.insns[idx], model)
+
+            if prog_insn is None:
+                weights = self.weights.copy()
+                weights[idx] = 0
+                continue
+
+            assert prog_insn.lsu_info is not None
+            mem_type, tgt_addr = prog_insn.lsu_info
+            # Replace good CSR / WSR address with bad address.
+            bad_addr = model.pick_bad_addr(mem_type)
+            assert bad_addr is not None
+            prog_insn.lsu_info = mem_type, bad_addr
+
+        return (prog_insn, model)
diff --git a/hw/ip/otbn/dv/rig/rig/known_mem.py b/hw/ip/otbn/dv/rig/rig/known_mem.py
index e370c4f..c0338c3 100644
--- a/hw/ip/otbn/dv/rig/rig/known_mem.py
+++ b/hw/ip/otbn/dv/rig/rig/known_mem.py
@@ -360,3 +360,25 @@
 
         assert addr == ibase_addr + offset
         return addr, offset
+
+    def pick_bad_addr(self) -> Optional[int]:
+        '''Pick bad addresses from gaps present in known addresses.'''
+
+        gap_list = []
+        gap_vma = 0
+        for low, high in self.known_ranges:
+            assert gap_vma <= low
+            if gap_vma < low:
+                gap_list.append((gap_vma, low - 1))
+            gap_vma = high + 1
+
+        if gap_vma <= self.top_addr:
+            gap_list.append((gap_vma, self.top_addr))
+
+        if not gap_list:
+            return None
+
+        gap_len = [1 + hi - lo for lo, hi in gap_list]
+        bad_addr_lo, bad_addr_hi = random.choices(gap_list, weights=gap_len)[0]
+
+        return random.randint(bad_addr_lo, bad_addr_hi)
diff --git a/hw/ip/otbn/dv/rig/rig/model.py b/hw/ip/otbn/dv/rig/rig/model.py
index 33644d1..ccda388 100644
--- a/hw/ip/otbn/dv/rig/rig/model.py
+++ b/hw/ip/otbn/dv/rig/rig/model.py
@@ -1055,3 +1055,6 @@
             self._generic_update_for_insn(prog_insn)
 
         self.consume_fuel()
+
+    def pick_bad_addr(self, mem_type: str) -> Optional[int]:
+        return self._known_mem[mem_type].pick_bad_addr()
diff --git a/hw/ip/otbn/dv/rig/rig/snippet_gens.py b/hw/ip/otbn/dv/rig/rig/snippet_gens.py
index 755065e..21da372 100644
--- a/hw/ip/otbn/dv/rig/rig/snippet_gens.py
+++ b/hw/ip/otbn/dv/rig/rig/snippet_gens.py
@@ -32,6 +32,7 @@
 from .gens.bad_giant_loop import BadGiantLoop
 from .gens.bad_load_store import BadLoadStore
 from .gens.bad_zero_loop import BadZeroLoop
+from .gens.bad_ispr import BadIspr
 from .gens.misaligned_load_store import MisalignedLoadStore
 from .gens.untaken_branch import UntakenBranch
 
@@ -60,6 +61,7 @@
         BadGiantLoop,
         BadLoadStore,
         BadZeroLoop,
+        BadIspr,
         MisalignedLoadStore
     ]