[otbn] Add fixed_offset to immediate operands This allows instruction encodings where the immediate given in assembly is offset by some fixed value before being encoded. Signed-off-by: Greg Chadwick <gac@lowrisc.org>
diff --git a/hw/ip/otbn/util/shared/operand.py b/hw/ip/otbn/util/shared/operand.py index ed75617..18461b1 100644 --- a/hw/ip/otbn/util/shared/operand.py +++ b/hw/ip/otbn/util/shared/operand.py
@@ -246,18 +246,21 @@ '''A class representing an immediate operand type''' def __init__(self, width: Optional[int], + enc_offset: int, shift: int, signed: bool, pc_rel: bool) -> None: assert shift >= 0 super().__init__(width) + self.enc_offset = enc_offset self.shift = shift self.signed = signed self.pc_rel = pc_rel @staticmethod def make(width: Optional[int], + enc_offset: int, shift: int, signed: bool, pc_rel: bool, @@ -275,11 +278,11 @@ 'but the operand claims to have width {}.' .format(what, scheme_field.bits.width, width)) - return ImmOperandType(width, shift, signed, pc_rel) + return ImmOperandType(width, enc_offset, shift, signed, pc_rel) def markdown_doc(self) -> Optional[str]: # Override from OperandType base class - rel_rng = self.get_rel_range() + rel_rng = self.get_doc_range() if rel_rng is None: return None @@ -315,41 +318,49 @@ return None # Otherwise, we need to encode the offset. - offset_val = op_val - cur_pc + pc_relative_val = op_val - cur_pc else: - offset_val = op_val + pc_relative_val = op_val - # First, try to shift right. Check that we won't clobber any low bits. + # Try to shift right. Check that we won't clobber any low bits. shift_mask = (1 << self.shift) - 1 - if offset_val & shift_mask: + if pc_relative_val & shift_mask: raise ValueError('Cannot encode the value {}: the operand has a ' 'shift of {}, but that would clobber some bits ' '(because {} & {} = {}, not zero).' - .format(offset_val, self.shift, - offset_val, shift_mask, - offset_val & shift_mask)) + .format(pc_relative_val, self.shift, + pc_relative_val, shift_mask, + pc_relative_val & shift_mask)) - shifted = offset_val >> self.shift + # Compute offset encoded value by applying shift and enc_offset + shifted = pc_relative_val >> self.shift + offset_val = shifted - self.enc_offset - rng = self.get_rel_range() - assert rng is not None - lo, hi = rng + # Check offset encoded value sits in the allowable range for encoded + # values + enc_rng = self.get_enc_range() + assert enc_rng is not None + enc_lo, enc_hi = enc_rng - if not (lo <= offset_val <= hi): - shifted_msg = (', which shifts down to {}'.format(shifted) - if self.shift != 0 else '') + if not (enc_lo <= offset_val <= enc_hi): + doc_rng = self.get_doc_range() + assert doc_rng is not None + doc_lo, doc_hi = doc_rng + + encoded_msg = (', which encodes to {},'.format(offset_val) + if self.shift != 0 or self.enc_offset != 0 else '') raise ValueError('Cannot encode the value {}{} as a {}-bit ' '{}signed value. Possible range: {}..{}.' - .format(offset_val, shifted_msg, + .format(pc_relative_val, encoded_msg, self.width, '' if self.signed else 'un', - lo, hi)) + doc_lo, doc_hi)) if self.signed: - encoded = (1 << self.width) + shifted if shifted < 0 else shifted + encoded = (1 << self.width) + offset_val if offset_val < 0 else offset_val else: - assert shifted >= 0 - encoded = shifted + assert offset_val >= 0 + encoded = offset_val assert (encoded >> self.width) == 0 return encoded @@ -369,6 +380,8 @@ signed_val -= 1 << self.width assert signed_val < 0 + signed_val += self.enc_offset + shifted = signed_val << self.shift # If this value is PC-relative, add the current PC. The point is that @@ -379,8 +392,8 @@ return rel_val - def get_rel_range(self) -> Optional[Tuple[int, int]]: - '''Like get_op_val_range, but doesn't include any PC offset''' + def get_enc_range(self) -> Optional[Tuple[int, int]]: + '''Gets range of allowable encoded values''' if self.width is None: return None @@ -391,10 +404,26 @@ sgn_lo = 0 sgn_hi = (1 << self.width) - 1 + return (sgn_lo, sgn_hi) + + def get_doc_range(self) -> Optional[Tuple[int, int]]: + '''Gets range of allowable values as they will appear in + documentation + ''' + + enc_range = self.get_enc_range() + if enc_range is None: + return None + + sgn_lo, sgn_hi = enc_range + + sgn_lo += self.enc_offset + sgn_hi += self.enc_offset + return (sgn_lo << self.shift, sgn_hi << self.shift) def get_op_val_range(self, cur_pc: int) -> Optional[Tuple[int, int]]: - rel_rng = self.get_rel_range() + rel_rng = self.get_doc_range() if rel_rng is None: return None @@ -546,16 +575,17 @@ for base, signed in [('simm', True), ('uimm', False)]: # The type of an immediate operand is encoded as # - # BASE WIDTH? (<<SHIFT)? + # BASE WIDTH? (+ENC_OFFSET)? (<<SHIFT)? # - # where BASE is 'simm' or 'uimm', WIDTH is a positive integer and SHIFT - # is a non-negative integer. The regex below captures WIDTH as group 1 - # and SHIFT as group 2. - m = re.match(base + r'([1-9][0-9]*)?(?:<<([0-9]+))?$', fmt) + # where BASE is 'simm' or 'uimm', WIDTH is a positive integer and + # ENC_OFFSET and SHIFT are non-negative integers. The regex below + # captures WIDTH as group 1, OFFSET as group 2 and SHIFT as group 3. + m = re.match(base + r'([1-9][0-9]*)?(?:\+([0-9]+))?(?:<<([0-9]+))?$', fmt) if m is not None: width = int(m.group(1)) if m.group(1) is not None else None - shift = int(m.group(2)) if m.group(2) is not None else 0 - return ImmOperandType.make(width, shift, signed, pc_rel, + enc_offset = int(m.group(2)) if m.group(2) is not None else 0 + shift = int(m.group(3)) if m.group(3) is not None else 0 + return ImmOperandType.make(width, enc_offset, shift, signed, pc_rel, what, scheme_field) m = re.match(r'enum\(([^\)]+)\)$', fmt)