| # Copyright lowRISC contributors. |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| # SPDX-License-Identifier: Apache-2.0 |
| |
| import re |
| from typing import Dict, Tuple, Union |
| |
| from .bool_literal import BoolLiteral |
| from .encoding_scheme import EncSchemeField, EncSchemes |
| from .yaml_parse_helpers import check_keys, check_str |
| |
| |
| class EncodingField: |
| '''A single element of an encoding's mapping''' |
| def __init__(self, |
| value: Union[BoolLiteral, str], |
| scheme_field: EncSchemeField) -> None: |
| self.value = value |
| self.scheme_field = scheme_field |
| |
| @staticmethod |
| def from_yaml(as_str: str, |
| scheme_field: EncSchemeField, |
| what: str) -> 'EncodingField': |
| # The value should either be a boolean literal ("000xx11" or similar) |
| # or should be a name, which is taken as the name of an operand. |
| if not as_str: |
| raise ValueError('Empty string as {}.'.format(what)) |
| |
| # Set self.value to be either the bool literal or the name of the |
| # operand. |
| value = '' # type: Union[BoolLiteral, str] |
| if re.match(r'b[01x_]+$', as_str): |
| value = BoolLiteral.from_string(as_str, what) |
| |
| # Check that the literal operand value matches the width of the |
| # schema field. |
| if scheme_field.bits.width != value.width: |
| raise ValueError('{} is mapped to a literal value with width ' |
| '{}, but the encoding schema field has ' |
| 'width {}.' |
| .format(what, value.width, |
| scheme_field.bits.width)) |
| else: |
| value = as_str |
| |
| # Track the scheme field as well (so we don't have to keep track of a |
| # scheme once we've made an encoding object) |
| return EncodingField(value, scheme_field) |
| |
| |
| class Encoding: |
| '''The encoding for an instruction''' |
| def __init__(self, |
| yml: object, |
| schemes: EncSchemes, |
| mnemonic: str): |
| what = 'encoding for instruction {!r}'.format(mnemonic) |
| yd = check_keys(yml, what, ['scheme', 'mapping'], []) |
| |
| scheme_what = 'encoding scheme for instruction {!r}'.format(mnemonic) |
| scheme_name = check_str(yd['scheme'], scheme_what) |
| scheme_fields = schemes.resolve(scheme_name, mnemonic) |
| |
| what = 'encoding mapping for instruction {!r}'.format(mnemonic) |
| |
| # Check we've got exactly the right fields for the scheme |
| ydm = check_keys(yd['mapping'], what, list(scheme_fields.op_fields), []) |
| |
| # Build a map from operand name to the name of a field that uses it. |
| self.op_to_field_name = {} # type: Dict[str, str] |
| |
| self.fields = {} |
| for field_name, scheme_field in scheme_fields.fields.items(): |
| if scheme_field.value is not None: |
| field = EncodingField(scheme_field.value, scheme_field) |
| else: |
| field_what = ('value for {} field in ' |
| 'encoding for instruction {!r}' |
| .format(field_name, mnemonic)) |
| ef_val = check_str(ydm[field_name], field_what) |
| field = EncodingField.from_yaml(ef_val, |
| scheme_fields.fields[field_name], |
| field_what) |
| |
| # If the field's value has type str, the field uses an operand |
| # rather than a literal. Check for linearity and store the |
| # mapping. |
| if isinstance(field.value, str): |
| other_field_name = self.op_to_field_name.get(field.value) |
| if other_field_name is not None: |
| raise ValueError('Non-linear use of operand with name ' |
| '{!r} in encoding for instruction ' |
| '{!r}: used in fields {!r} and {!r}.' |
| .format(field.value, mnemonic, |
| other_field_name, |
| field_name)) |
| self.op_to_field_name[field.value] = field_name |
| |
| self.fields[field_name] = field |
| |
| def get_masks(self) -> Tuple[int, int]: |
| '''Return zeros/ones masks for encoding |
| |
| Returns a pair (m0, m1) where m0 is the "zeros mask": a mask where a |
| bit is set if there is an bit pattern matching this encoding with that |
| bit zero. m1 is the ones mask: equivalent, but for that bit one. |
| |
| ''' |
| m0 = 0 |
| m1 = 0 |
| for field_name, field in self.fields.items(): |
| if isinstance(field.value, str): |
| m0 |= field.scheme_field.bits.mask |
| m1 |= field.scheme_field.bits.mask |
| else: |
| # Match up the bits in the value with the ranges in the scheme. |
| assert field.value.width > 0 |
| assert field.value.width == field.scheme_field.bits.width |
| bits_seen = 0 |
| for msb, lsb in field.scheme_field.bits.ranges: |
| val_msb = field.scheme_field.bits.width - 1 - bits_seen |
| val_lsb = val_msb - msb + lsb |
| bits_seen += msb - lsb + 1 |
| |
| for idx in range(0, msb - lsb + 1): |
| desc = field.value.char_for_bit(val_lsb + idx) |
| if desc in ['0', 'x']: |
| m0 |= 1 << (idx + lsb) |
| if desc in ['1', 'x']: |
| m1 |= 1 << (idx + lsb) |
| |
| all_bits = (1 << 32) - 1 |
| assert (m0 | m1) == all_bits |
| return (m0, m1) |
| |
| def get_ones_mask(self) -> int: |
| '''Return the mask of fixed bits that are set |
| |
| For literal values of x (unused bits in the encoding), we'll prefer |
| '0'. |
| |
| ''' |
| m0, m1 = self.get_masks() |
| return m1 & ~m0 |
| |
| def assemble(self, op_to_idx: Dict[str, int]) -> int: |
| '''Assemble an instruction |
| |
| op_to_idx should map each operand in the encoding to some integer |
| index, which should be small enough to fit in the width of the |
| operand's type and should be representable after any shift. Will raise |
| a ValueError if not. |
| |
| ''' |
| val = self.get_ones_mask() |
| for field_name, field in self.fields.items(): |
| if not isinstance(field.value, str): |
| # We've done this field already (in get_ones_mask) |
| continue |
| |
| # Try to get the operand value for the field. If this is an |
| # optional operand, we might not have one, and just encode zero. |
| field_val = op_to_idx.get(field.value, 0) |
| |
| # The encoding process should already have converted anything |
| # negative to a 2's complement representation. |
| assert field_val >= 0 |
| |
| # Is the number too big? At the moment, we are assuming immediates |
| # are unsigned (because the OTBN big number instructions all have |
| # unsigned immediates). |
| if field_val >> field.scheme_field.bits.width: |
| raise ValueError("operand field {} has a width of {}, " |
| "so can't represent the value {:#x}." |
| .format(field.value, |
| field.scheme_field.bits.width, |
| field_val)) |
| |
| val |= field.scheme_field.bits.encode(field_val) |
| |
| return val |
| |
| def extract_operands(self, word: int) -> Dict[str, int]: |
| '''Extract the encoded operand values from an encoded instruction''' |
| ret = {} |
| for field in self.fields.values(): |
| # The operand fields (rather than fixed ones) have the operand name as |
| # their value. |
| if not isinstance(field.value, str): |
| continue |
| |
| ret[field.value] = field.scheme_field.bits.decode(word) |
| |
| return ret |