[otbn] Add support for bn.sid to RIG
This isn't massively different from bn.lid, but we need to check that
we pick a source WDR that actually has an architectural value. We also
slightly weaken the checks for bn.lid, which can use any GRD with an
architectural value: we don't actually need to know it.
Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/hw/ip/otbn/util/rig/gens/straight_line_insn.py b/hw/ip/otbn/util/rig/gens/straight_line_insn.py
index 65bfd82..af07ea0 100644
--- a/hw/ip/otbn/util/rig/gens/straight_line_insn.py
+++ b/hw/ip/otbn/util/rig/gens/straight_line_insn.py
@@ -29,9 +29,9 @@
if not insn.straight_line:
continue
- # Skip bn.sid and bn.movr (these have special "LSU behaviour" and
- # aren't yet implemented)
- if insn.mnemonic in ['bn.sid', 'bn.movr']:
+ # Skip bn.movr (this has special "LSU behaviour" and isn't yet
+ # implemented)
+ if insn.mnemonic in ['bn.movr']:
continue
self.insns.append(insn)
@@ -102,8 +102,8 @@
# Special-case BN load/store instructions by mnemonic. These use
# complicated indirect addressing, so it's probably more sensible to
# give them special code.
- if insn.mnemonic == 'bn.lid':
- return self._fill_bn_lid(insn, model)
+ if insn.mnemonic in ['bn.lid', 'bn.sid']:
+ return self._fill_bn_xid(insn, model)
return self._fill_lsu_insn(insn, model)
@@ -125,42 +125,91 @@
assert len(op_vals) == len(insn.operands)
return ProgInsn(insn, op_vals, None)
- def _fill_bn_lid(self, insn: Insn, model: Model) -> Optional[ProgInsn]:
- '''Fill out a BN.LID instruction'''
- assert insn.mnemonic == 'bn.lid'
- # bn.lid expects the operands: grd, grs1, offset, grs1_inc, grd_inc
- if len(insn.operands) != 5:
- raise RuntimeError('Unexpected number of operands for bn.lid')
+ def _fill_bn_xid(self, insn: Insn, model: Model) -> Optional[ProgInsn]:
+ '''Fill out a BN.LID or BN.SID instruction'''
+ if insn.mnemonic == 'bn.lid':
+ is_load = True
+ # bn.lid expects the operands: grd, grs1, offset, grs1_inc, grd_inc
+ if len(insn.operands) != 5:
+ raise RuntimeError('Unexpected number of operands for bn.lid')
- grd, grs1, offset, grs1_inc, grd_inc = 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
- # grs1
- isinstance(grs1.op_type, RegOperandType) and
- grs1.op_type.reg_type == 'gpr' and
- not grs1.op_type.is_dest() and
- # offset
- isinstance(offset.op_type, ImmOperandType) and
- offset.op_type.signed and
- # grs1_inc
- isinstance(grs1_inc.op_type, OptionOperandType) and
- # grd_inc
- isinstance(grd_inc.op_type, OptionOperandType)
- )
+ grd, grs1, offset, grs1_inc, grd_inc = 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
+ # grs1
+ isinstance(grs1.op_type, RegOperandType) and
+ grs1.op_type.reg_type == 'gpr' and
+ not grs1.op_type.is_dest() and
+ # offset
+ isinstance(offset.op_type, ImmOperandType) and
+ offset.op_type.signed and
+ # grs1_inc
+ isinstance(grs1_inc.op_type, OptionOperandType) and
+ # grd_inc
+ isinstance(grd_inc.op_type, OptionOperandType)
+ )
+ else:
+ assert insn.mnemonic == 'bn.sid'
+ is_load = False
+ # bn.sid expects the operands: grs1, grs2, offset, grs1_inc,
+ # grs2_inc
+ if len(insn.operands) != 5:
+ raise RuntimeError('Unexpected number of operands for bn.sid')
+
+ grs1, grs2, offset, grs1_inc, grs2_inc = insn.operands
+ exp_shape = (
+ # grs1
+ isinstance(grs1.op_type, RegOperandType) and
+ grs1.op_type.reg_type == 'gpr' and
+ not grs1.op_type.is_dest() and
+ # grs2
+ isinstance(grs2.op_type, RegOperandType) and
+ grs2.op_type.reg_type == 'gpr' and
+ not grs2.op_type.is_dest() and
+ # offset
+ isinstance(offset.op_type, ImmOperandType) and
+ offset.op_type.signed and
+ # grs1_inc
+ isinstance(grs1_inc.op_type, OptionOperandType) and
+ # grs2_inc
+ isinstance(grs2_inc.op_type, OptionOperandType)
+ )
+
if not exp_shape:
- raise RuntimeError('Unexpected shape for bn.lid')
+ raise RuntimeError('Unexpected shape for {}'.format(insn.mnemonic))
# Assertions to guide mypy
assert isinstance(offset.op_type, ImmOperandType)
- # bn.lid reads the bottom 5 bits of grd to get the index of the
- # destination WDR. So look at the registers with architectural values.
- # Since this is a destination register, we can safely pick anything.
known_regs = model.regs_with_known_vals('gpr')
- grd_idx, grd_val = random.choices(known_regs)[0]
+ if is_load:
+ # bn.lid reads the bottom 5 bits of grd to get the index of the
+ # destination WDR. We can pick any GPR that has an architectural
+ # value.
+ arch_gprs = model.regs_with_architectural_vals('gpr')
+ assert arch_gprs
+ wdr_gpr_idx = random.choices(arch_gprs)[0]
+ else:
+ # bn.sid looks at the bottom 5 bits of grs2 to get a source WDR.
+ # Unlike bn.lid above, we need an architectural value in the
+ # corresponding WDR. Thus we have to pick a GPR with a known value
+ # which, in turn, points at a WDR with an architectural value.
+ arch_wdrs = set(model.regs_with_architectural_vals('wdr'))
+ valid_grs2_indices = []
+ for grs2_idx, grs2_val in known_regs:
+ if grs2_val is None:
+ continue
+ wdr_idx = grs2_val & 31
+ if wdr_idx in arch_wdrs:
+ valid_grs2_indices.append(grs2_idx)
+
+ if not valid_grs2_indices:
+ return None
+
+ wdr_gpr_idx = random.choices(valid_grs2_indices)[0]
# Now pick the source register and offset. The range for offset
# shouldn't be none (because we know the width of the underlying bit
@@ -170,7 +219,7 @@
op_to_known_regs = {'grs1': known_regs}
tgt = model.pick_lsu_target('dmem',
- True,
+ is_load,
op_to_known_regs,
offset_rng,
offset.op_type.shift,
@@ -187,19 +236,29 @@
offset_val = offset.op_type.op_val_to_enc_val(imm_val, model.pc)
assert offset_val is not None
- # Do we increment grs1 / grd? We can increment up to one of them.
+ # Do we increment the GPRs? We can increment up to one of them.
inc_idx = random.randint(0, 2)
if inc_idx == 0:
grs1_inc_val = 0
- grd_inc_val = 0
+ wdr_gpr_inc_val = 0
elif inc_idx == 1:
grs1_inc_val = 1
- grd_inc_val = 0
+ wdr_gpr_inc_val = 0
else:
grs1_inc_val = 0
- grd_inc_val = 1
+ wdr_gpr_inc_val = 1
- enc_vals = [grd_idx, grs1_val, offset_val, grs1_inc_val, grd_inc_val]
+ # Finally, package up the operands properly for the instruction we're
+ # building.
+ if is_load:
+ # bn.lid: grd, grs1, offset, grs1_inc, grd_inc
+ enc_vals = [wdr_gpr_idx, grs1_val, offset_val,
+ grs1_inc_val, wdr_gpr_inc_val]
+ else:
+ # bn.sid: grs1, grs2, offset, grs1_inc, grs2_inc
+ enc_vals = [grs1_val, wdr_gpr_idx, offset_val,
+ grs1_inc_val, wdr_gpr_inc_val]
+
return ProgInsn(insn, enc_vals, ('dmem', addr))
def _fill_lsu_insn(self, insn: Insn, model: Model) -> Optional[ProgInsn]:
diff --git a/hw/ip/otbn/util/rig/model.py b/hw/ip/otbn/util/rig/model.py
index 08943dd..44060ab 100644
--- a/hw/ip/otbn/util/rig/model.py
+++ b/hw/ip/otbn/util/rig/model.py
@@ -503,6 +503,20 @@
return ret
+ def regs_with_architectural_vals(self, reg_type: str) -> List[int]:
+ '''List registers that have an architectural value'''
+ known_regs = self._known_regs.setdefault(reg_type, {})
+ arch_regs = list(known_regs.keys())
+
+ # Handle x1, which has an architectural (and known) value iff the call
+ # stack is not empty.
+ if reg_type == 'gpr':
+ assert 1 not in arch_regs
+ if self._call_stack:
+ arch_regs.append(1)
+
+ return arch_regs
+
def pick_lsu_target(self,
mem_type: str,
loads_value: bool,
@@ -702,6 +716,43 @@
elif grd_inc:
self._inc_gpr(grd, 1, 31)
+ def update_for_bnsid(self, prog_insn: ProgInsn) -> None:
+ '''Update model state after an BN.SID'''
+ insn = prog_insn.insn
+ op_vals = prog_insn.operands
+ assert insn.mnemonic == 'bn.sid'
+ assert len(insn.operands) == len(op_vals)
+
+ grs1_op, grs2_op, offset_op, grs1_inc_op, grs2_inc_op = insn.operands
+ exp_shape = (
+ # grs1
+ isinstance(grs1_op.op_type, RegOperandType) and
+ grs1_op.op_type.reg_type == 'gpr' and
+ not grs1_op.op_type.is_dest() and
+ # grs2
+ isinstance(grs2_op.op_type, RegOperandType) and
+ grs2_op.op_type.reg_type == 'gpr' and
+ not grs2_op.op_type.is_dest() and
+ # offset
+ isinstance(offset_op.op_type, ImmOperandType) and
+ offset_op.op_type.signed and
+ # grs1_inc
+ isinstance(grs1_inc_op.op_type, OptionOperandType) and
+ # grs2_inc
+ isinstance(grs2_inc_op.op_type, OptionOperandType)
+ )
+ if not exp_shape:
+ raise RuntimeError('Unexpected shape for bn.sid')
+
+ grs1, grs2, offset, grs1_inc, grs2_inc = op_vals
+
+ self._generic_update_for_insn(prog_insn)
+
+ if grs1_inc:
+ self._inc_gpr(grs1, 32, (1 << 32) - 1)
+ elif grs2_inc:
+ self._inc_gpr(grs2, 1, 31)
+
def _generic_update_for_insn(self, prog_insn: ProgInsn) -> None:
'''Update registers and memory for prog_insn
@@ -744,7 +795,8 @@
updaters = {
'lui': self.update_for_lui,
'addi': self.update_for_addi,
- 'bn.lid': self.update_for_bnlid
+ 'bn.lid': self.update_for_bnlid,
+ 'bn.sid': self.update_for_bnsid
}
updater = updaters.get(prog_insn.insn.mnemonic)
if updater is not None: