# SPDX-License-Identifier: Apache-2.0
#
# This file is part of the M2-ISA-R project: https://github.com/tum-ei-eda/M2-ISA-R
#
# Copyright (C) 2022
# Chair of Electrical Design Automation
# Technical University of Munich
"""Simple disassembler backend for M2-ISA-R ISA metamodels. Not very
actively maintained, might break or otherwise not work as expected.
"""
import argparse
import logging
import pathlib
import pickle
from collections import defaultdict
from io import SEEK_CUR
from ...metamodel import M2_METAMODEL_VERSION, M2Model, arch
[docs]
logger = logging.getLogger("viewer")
[docs]
def sort_instruction(entry):
"""Key function for sorting instructions:
Sorts by most restrictive mask first, to accurately distinguish
overlapping opcodes
"""
(code, mask), _ = entry
return bin(mask).count("1"), code
[docs]
def find_instr(iw: int, instructions: "dict[tuple[int, int], arch.Instruction]"):
"""Linear search for an instruction by its codeword."""
for (code, mask), instr_def in instructions.items():
if (iw & mask) == code:
return instr_def
return None
[docs]
def slice_int(v: int, upper: int, lower: int):
return (v & ((1 << upper + 1) - 1)) >> lower
[docs]
def decode(iw: int, instr: arch.Instruction):
"""Separate out operands of an instruction from its codeword."""
enc_idx = 0
operands = defaultdict(int)
for enc in reversed(instr.encoding):
if isinstance(enc, arch.BitField):
lower = enc.range.lower
length = enc.range.length
operands[enc.name] += slice_int(iw, enc_idx+length-1, enc_idx) << lower
enc_idx += length
else:
enc_idx += enc.length
return operands
[docs]
def main():
parser = argparse.ArgumentParser()
parser.add_argument('top_level', help="A .m2isarmodel file containing the models to generate.")
parser.add_argument("core_name")
parser.add_argument('bin')
parser.add_argument("--log", default="info", choices=["critical", "error", "warning", "info", "debug"])
args = parser.parse_args()
logging.basicConfig(level=getattr(logging, args.log.upper()))
top_level = pathlib.Path(args.top_level)
abs_top_level = top_level.resolve()
search_path = abs_top_level.parent.parent
model_fname = abs_top_level
if abs_top_level.suffix == ".core_desc":
logger.warning(".core_desc file passed as input. This is deprecated behavior, please change your scripts!")
search_path = abs_top_level.parent
model_path = search_path.joinpath('gen_model')
if not model_path.exists():
raise FileNotFoundError('Models not generated!')
model_fname = model_path / (abs_top_level.stem + '.m2isarmodel')
output_base_path = search_path.joinpath('gen_output')
output_base_path.mkdir(exist_ok=True)
logger.info("loading models")
with open(model_fname, 'rb') as f:
model_obj: "M2Model" = pickle.load(f)
if model_obj.model_version != M2_METAMODEL_VERSION:
logger.warning("Loaded model version mismatch")
models = model_obj.models
core = models[args.core_name]
readlen = max(core.instr_classes) // 8
steplen = min(core.instr_classes) // 8
instrs_by_size = defaultdict(dict)
# group instructions by their codeword width
for k, v in core.instructions.items():
instrs_by_size[v.size][k] = v
# sort instructions by opcode
for k, v in instrs_by_size.items():
instrs_by_size[k] = dict(sorted(v.items(), key=sort_instruction, reverse=True))
instrs_by_size = dict(sorted(instrs_by_size.items()))
with open(args.bin, "rb") as f:
# read at most XLEN bytes at a time
while iw_read := f.peek(readlen):
# truncate read data as peek is not guaranteed to return exactly XLEN bytes
iw = iw_read[:readlen]
# look for instruction
found_ins = None
for cls in sorted(core.instr_classes):
ii = int.from_bytes(iw[:cls // 8], "little")
i = find_instr(ii, instrs_by_size[cls])
if i is not None:
found_ins = i
if found_ins is None:
ins_str = "unknown"
step = steplen
# decode instruction operands
else:
operands = decode(ii, found_ins)
op_str = " | ".join([f"{k}={v}" for k, v in operands.items()])
ins_str = f"{found_ins.name} [{op_str}]"
step = found_ins.size // 8
# print decoded instruction mnemonic
iword = int.from_bytes(iw[:step], "little")
iword = "{iword:0{step}x}".format(iword=iword, step=step*2)
print(f"{f.tell():08x}: {iword:<16} {ins_str}")
f.seek(step, SEEK_CUR)
if __name__ == "__main__":
main()