blob: 9d2945aaa46594b85ebcfa8a71435bf0991c667d [file]
# 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 .operand import Operand
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,
name_to_operand: Dict[str, Operand],
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_width = None
value = '' # type: Union[BoolLiteral, str]
if re.match(r'b[01x_]+$', as_str):
value = BoolLiteral.from_string(as_str, what)
value_width = value.width
value_type = 'a literal value'
else:
operand = name_to_operand.get(as_str)
if operand is None:
raise ValueError('Unknown operand, {!r}, as {}'
.format(as_str, what))
value_width = operand.op_type.width
value = as_str
value_type = 'an operand'
# Unless we had an operand of type 'imm' (unknown width), we now have
# an expected width. Check it matches the width of the schema field.
if value_width is not None:
if scheme_field.bits.width != value_width:
raise ValueError('{} is mapped to {} with width {}, but the '
'encoding schema field has width {}.'
.format(what, value_type, value_width,
scheme_field.bits.width))
# 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,
name_to_operand: Dict[str, Operand],
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), [])
# Track the set of operand names that were used in some field
operands_used = set()
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))
field = EncodingField.from_yaml(check_str(ydm[field_name], field_what),
scheme_fields.fields[field_name],
name_to_operand,
field_what)
# If the field's value is an operand rather than a literal, it
# will have type str. Track the operands that we've used.
if isinstance(field.value, str):
operands_used.add(field.value)
self.fields[field_name] = field
# We know that every field in the encoding scheme has a value. But we
# still need to check that every operand ended up in some field.
assert operands_used <= set(name_to_operand.keys())
unused_ops = set(name_to_operand.keys()) - operands_used
if unused_ops:
raise ValueError('Not all operands used in {} (missing: {}).'
.format(what, ', '.join(list(unused_ops))))
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)
# Are there any low bits that shouldn't be there?
shift_mask = (1 << field.scheme_field.shift) - 1
if field_val & shift_mask:
raise ValueError("operand field {} has a shift of {}, "
"so can't represent the value {:#x}."
.format(field.value,
field.scheme_field.shift,
field_val))
shifted = field_val >> field.scheme_field.shift
# 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 shifted >> field.scheme_field.bits.width:
shift_msg = ((' (shifted right by {} bits from {:#x})'
.format(field.scheme_field.shift, field_val))
if field.scheme_field.shift
else '')
raise ValueError("operand field {} has a width of {}, "
"so can't represent the value {:#x}{}."
.format(field.value,
field.scheme_field.bits.width,
shifted, shift_msg))
val |= field.scheme_field.bits.encode(shifted)
return val