blob: 7f52284136c4a0235066535f251164d71a9a4101 [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 sys
from typing import Dict, Optional, Tuple
from shared.insn_yaml import Insn, load_insns_yaml
from .state import OTBNState
# Load the insns.yml file at module load time: we'll use its data while
# declaring the classes. The point is that an OTBNInsn below is an instance of
# a particular Insn object from shared.insn_yaml, so we want a class variable
# on the OTBNInsn that points at the corresponding Insn.
try:
_INSNS_FILE = load_insns_yaml()
except RuntimeError as err:
sys.stderr.write('{}\n'.format(err))
sys.exit(1)
class DummyInsn(Insn):
'''A dummy instruction that will never be decoded. Used for the insn class
variable in the OTBNInsn base class.
'''
def __init__(self) -> None:
fake_yml = {
'mnemonic': 'dummy-insn',
'operands': []
}
super().__init__(fake_yml, None)
def insn_for_mnemonic(mnemonic: str, num_operands: int) -> Insn:
'''Look up the named instruction in the loaded YAML data.
As a sanity check, make sure it has the expected number of operands. If we
fail to find the right instruction, print a message to stderr and exit
(rather than raising a RuntimeError: this happens on module load time, so
it's a lot clearer to the user what's going on this way).
'''
insn = _INSNS_FILE.mnemonic_to_insn.get(mnemonic)
if insn is None:
sys.stderr.write('Failed to find an instruction for mnemonic {!r} in '
'insns.yml.\n'
.format(mnemonic))
sys.exit(1)
if len(insn.operands) != num_operands:
sys.stderr.write('The instruction for mnemonic {!r} in insns.yml has '
'{} operands, but we expected {}.\n'
.format(mnemonic, len(insn.operands), num_operands))
sys.exit(1)
return insn
class OTBNInsn:
'''A decoded OTBN instruction.
'''
# A class variable that holds the Insn subclass corresponding to this
# instruction.
insn = DummyInsn() # type: Insn
def __init__(self, op_vals: Dict[str, int]):
self.op_vals = op_vals
# Memoized disassembly for this instruction. We store the PC at which
# we disassembled too (which should be the same next time around, but
# it can't hurt to check).
self._disasm = None # type: Optional[Tuple[int, str]]
def execute(self, state: OTBNState) -> None:
raise NotImplementedError('OTBNInsn.execute')
def disassemble(self, pc: int) -> str:
'''Generate an assembly listing for this instruction'''
if self._disasm is not None:
old_pc, old_disasm = self._disasm
assert pc == old_pc
return old_disasm
disasm = self.insn.disassemble(self.op_vals, 12)
self._disasm = (pc, disasm)
return disasm
class RV32RegReg(OTBNInsn):
'''A general class for register-register insns from the RV32I ISA'''
def __init__(self, op_vals: Dict[str, int]):
super().__init__(op_vals)
self.grd = op_vals['grd']
self.grs1 = op_vals['grs1']
self.grs2 = op_vals['grs2']
class RV32RegImm(OTBNInsn):
'''A general class for register-immediate insns from the RV32I ISA'''
def __init__(self, op_vals: Dict[str, int]):
super().__init__(op_vals)
self.grd = op_vals['grd']
self.grs1 = op_vals['grs1']
self.imm = op_vals['imm']
class RV32ImmShift(OTBNInsn):
'''A general class for immediate shift insns from the RV32I ISA'''
def __init__(self, op_vals: Dict[str, int]):
super().__init__(op_vals)
self.grd = op_vals['grd']
self.grs1 = op_vals['grs1']
self.shamt = op_vals['shamt']
def logical_byte_shift(value: int, shift_type: int, shift_bytes: int) -> int:
'''Logical shift value by shift_bytes to the left or right.
value should be an unsigned 256-bit value. shift_type should be 0 (shift left)
or 1 (shift right): matching the encoding of the big number instructions.
shift_bytes should be a non-negative number of bytes to shift by.
Returns an unsigned 256-bit value, truncating on an overflowing left shift.
'''
mask256 = (1 << 256) - 1
assert 0 <= value <= mask256
assert 0 <= shift_type <= 1
assert 0 <= shift_bytes
shift_bits = 8 * shift_bytes
shifted = value << shift_bits if shift_type == 0 else value >> shift_bits
return shifted & mask256