blob: e697d7a054f15d5cc13ff7238787d38bbb87d90c [file] [log] [blame]
# 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