# 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
"""Viewer tool to visualize an M2-ISA-R model hierarchy."""
import argparse
import logging
import pathlib
import pickle
import tkinter as tk
from collections import defaultdict
from tkinter import ttk
from m2isar.backends.viewer.utils import TreeGenContext
from ...metamodel import M2_METAMODEL_VERSION, M2Model, arch, patch_model
from ...metamodel.utils.expr_preprocessor import (process_attributes,
process_functions,
process_instructions)
from . import treegen
[docs]
logger = logging.getLogger("viewer")
[docs]
def sort_instruction(entry: "tuple[tuple[int, int], arch.Instruction]"):
"""Instruction sort key function. Sorts most restrictive encoding first."""
(code, mask), _ = entry
return bin(mask).count("1"), code
#return code, bin(mask).count("1")
[docs]
def main():
"""Main app entrypoint."""
# read command line args
parser = argparse.ArgumentParser()
parser.add_argument('top_level', help="A .m2isarmodel file containing the models to generate.")
parser.add_argument('-s', '--separate', action='store_true', help="Generate separate .cpp files for each instruction set.")
parser.add_argument("--log", default="info", choices=["critical", "error", "warning", "info", "debug"])
args = parser.parse_args()
# initialize logging
logging.basicConfig(level=getattr(logging, args.log.upper()))
# resolve model paths
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")
# load 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
# preprocess model
for core_name, core in models.items():
logger.info("preprocessing model %s", core_name)
process_functions(core)
process_instructions(core)
process_attributes(core)
# load Ttk TreeView transformer functions
patch_model(treegen)
# create main Tk window
root = tk.Tk()
root.title("M2-ISA-R Viewer")
tree = ttk.Treeview(root, columns=(1,))
tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar = ttk.Scrollbar(root, orient=tk.VERTICAL, command=tree.yview)
tree.configure(yscroll=scrollbar.set)
scrollbar.pack(side=tk.LEFT, fill=tk.Y)
#tree.heading(0, text="Item")
tree.heading("#0", text="Item")
tree.heading(1, text="Value")
# add each core to the treeview
for core_name, core_def in sorted(models.items()):
core_id = tree.insert("", tk.END, text=core_name)
# add constants to tree
consts_id = tree.insert(core_id, tk.END, text="Constants")
for const_name, const_def in sorted(core_def.constants.items()):
tree.insert(consts_id, tk.END, text=const_name, values=(const_def.value,))
# add memories to tree
mems_id = tree.insert(core_id, tk.END, text="Memories")
for mem_name, mem_def in sorted(core_def.memories.items()):
tree.insert(mems_id, tk.END, text=mem_name, values=(f"{mem_def.range.upper}:{mem_def.range.lower} ({mem_def.range.length}), {mem_def.size}",))
# add memory aliases to tree
alias_id = tree.insert(core_id, tk.END, text="Memory Aliases")
for mem_name, mem_def in sorted(core_def.memory_aliases.items()):
tree.insert(alias_id, tk.END, text=f"{mem_name} ({mem_def.parent.name})", values=(f"{mem_def.range.upper}:{mem_def.range.lower} ({mem_def.range.length}), {mem_def.size}",))
# add auxillary attributes
tree.insert(core_id, tk.END, text="Main Memory Object", values=(core_def.main_memory,))
tree.insert(core_id, tk.END, text="Main Register File Object", values=(core_def.main_reg_file,))
tree.insert(core_id, tk.END, text="PC Memory Object", values=(core_def.pc_memory,))
# add functions to tree
fns_id = tree.insert(core_id, tk.END, text="Functions")
for fn_name, fn_def in core_def.functions.items():
fn_id = tree.insert(fns_id, tk.END, text=fn_name, values=("extern" if fn_def.extern else ""))
# add returns and throws information
return_str = "None" if fn_def.size is None else f"{fn_def.data_type} {fn_def.size}"
tree.insert(fn_id, tk.END, text="Return", values=(return_str,))
tree.insert(fn_id, tk.END, text="Throws", values=(fn_def.throws))
# generate and add attributes
attrs_id = tree.insert(fn_id, tk.END, text="Attributes")
for attr, ops in fn_def.attributes.items():
attr_id = tree.insert(attrs_id, tk.END, text=attr)
for op in ops:
context = TreeGenContext(tree, attr_id)
op.generate(context)
# generate and add parameters
params_id = tree.insert(fn_id, tk.END, text="Parameters")
for param_name, param_def in fn_def.args.items():
tree.insert(params_id, tk.END, text=param_name, values=(f"{param_def.data_type} {param_def.size}",))
# generate and add function behavior
context = TreeGenContext(tree, fn_id)
fn_def.operation.generate(context)
# group instructions by size
instrs_by_size = defaultdict(dict)
for k, v in core_def.instructions.items():
instrs_by_size[v.size][k] = v
# sort instructions by encoding
for k, v in instrs_by_size.items():
instrs_by_size[k] = dict(sorted(v.items(), key=sort_instruction, reverse=True))
instrs_top_id = tree.insert(core_id, tk.END, text="Instructions")
# generate instruction size groups
for size, instrs in sorted(instrs_by_size.items()):
instrs_id = tree.insert(instrs_top_id, tk.END, text=f"Width {size}")
# generate instructions
for (code, mask), instr_def in instrs.items():
opcode_str = "{code:0{width}x}:{mask:0{width}x}".format(code=code, mask=mask, width=int(instr_def.size/4))
instr_id = tree.insert(instrs_id, tk.END, text=f"{instr_def.ext_name} : {instr_def.name}", values=(opcode_str,), tags=("mono",))
# generate encoding
enc_str = []
for enc in instr_def.encoding:
if isinstance(enc, arch.BitVal):
enc_str.append(f"{enc.value:0{enc.length}b}")
elif isinstance(enc, arch.BitField):
enc_str.append(f"{enc.name}[{enc.range.upper}:{enc.range.lower}]")
tree.insert(instr_id, tk.END, text="Encoding", values=(" ".join(enc_str),))
tree.insert(instr_id, tk.END, text="Assembly", values=(instr_def.disass,))
tree.insert(instr_id, tk.END, text="Throws", values=(instr_def.throws))
attrs_id = tree.insert(instr_id, tk.END, text="Attributes")
# generate attributes
for attr, ops in instr_def.attributes.items():
attr_id = tree.insert(attrs_id, tk.END, text=attr.name)
for op in ops:
context = TreeGenContext(tree, attr_id)
op.generate(context)
# generate behavior
context = TreeGenContext(tree, instr_id)
instr_def.operation.generate(context)
#tree.tag_configure("mono", font=font.nametofont("TkFixedFont"))
root.mainloop()
if __name__ == "__main__":
main()