[otbn,dv] Properly handle operands that are both src and dst in RIG

The existing code was only correct if no operand is both a source and
a destination... which isn't true any more! In particular, we could
generate BN.MULQACC instructions that wrote to constant registers.

Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/hw/ip/otbn/dv/rig/rig/model.py b/hw/ip/otbn/dv/rig/rig/model.py
index 9834666..ea8b9cd 100644
--- a/hw/ip/otbn/dv/rig/rig/model.py
+++ b/hw/ip/otbn/dv/rig/rig/model.py
@@ -341,35 +341,54 @@
     def pick_reg_operand_value(self, op_type: RegOperandType) -> Optional[int]:
         '''Pick a random value for a register operand
 
-        Returns None if there's no valid value possible.'''
-        if op_type.is_src():
-            # This operand needs an architectural value. Pick a register
-            # from the indices in _known_regs[op_type.reg_type].
-            known_regs = self._known_regs.get(op_type.reg_type)
-            if not known_regs:
-                return None
+        Returns None if there's no valid value possible.
 
-            known_list = list(known_regs)
-            if op_type.reg_type == 'gpr':
-                # Add x1 if to the list of known registers if it has an
-                # architectural value and isn't marked constant. This won't
-                # appear in known_regs, because we don't track x1 there.
-                assert 1 not in known_regs
-                if not (self._call_stack.empty() or self.is_const('gpr', 1)):
-                    known_list.append(1)
+        '''
 
-            return random.choice(known_list)
+        # A register can be used if all of the following hold:
+        #
+        #   - If the register is used as a source, it has an architecturally
+        #     defined value (maybe not known by us, but at least defined).
+        #
+        #   - If the register is used as a destination, it must not be const.
+        #
+        #   - For x1 used as a source, the call stack must not be empty and x1
+        #     must not be marked constant (since the read will pop from the
+        #     call stack).
+        #
+        #   - For x1 used as a destination, the call stack must not be full and
+        #     x1 must not be marked constant.
 
-        # This operand isn't treated as a source. Generate a list of allowed
-        # registers (everything but constant registers, plus x1 if the call
-        # stack is full) and then pick from it.
+        is_src = op_type.is_src()
+        is_dst = op_type.is_dest()
+
         assert op_type.width is not None
-        const_regs = self._const_regs.get(op_type.reg_type, set())
-        all_regs = set(range(1 << op_type.width))
-        good_regs = all_regs - const_regs
-        if op_type.reg_type == 'gpr' and self._call_stack.full():
-            good_regs.discard(1)
-        return random.choice(list(good_regs))
+
+        reg_set = set(self._known_regs.get(op_type.reg_type, {}).keys()
+                      if is_src else range(1 << op_type.width))
+        if is_dst:
+            reg_set -= self._const_regs.get(op_type.reg_type, set())
+
+        # Special handling for x1
+        #
+        # Note that this won't allow us to generate things like add x1, x1, x1
+        # when the stack is full, because we only do one operand at a time.
+        if op_type.reg_type == 'gpr':
+            can_use_x1 = not self.is_const('gpr', 1)
+            if is_src and self._call_stack.empty():
+                can_use_x1 = False
+            if is_dst and self._call_stack.full():
+                can_use_x1 = False
+
+            # Since x1 isn't tracked in known_regs, we add it here if wanted
+            # (to handle the src case) and remove it here if not wanted (to
+            # handle the non-src case).
+            if can_use_x1:
+                reg_set.add(1)
+            else:
+                reg_set.discard(1)
+
+        return None if not reg_set else random.choice(list(reg_set))
 
     def all_regs_with_known_vals(self) -> Dict[str, List[Tuple[int, int]]]:
         '''Like regs_with_known_vals, but returns all reg types'''