# 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
"""Main entrypoint for the etiss_writer program."""
import argparse
import logging
import pathlib
import pickle
import shutil
import time
from ...metamodel import M2_METAMODEL_VERSION, M2Model
from ...metamodel.utils.expr_preprocessor import (process_attributes,
process_functions,
process_instructions)
from . import BlockEndType, CodeInfoTracker
from .architecture_writer import (write_arch_cmake, write_arch_cpp,
write_arch_gdbcore, write_arch_header,
write_arch_lib, write_arch_specific_cpp,
write_arch_specific_header,
write_arch_struct)
from .instruction_writer import write_functions, write_instructions
[docs]
class BooleanOptionalAction(argparse.Action):
"""A boolean optional action for argparse, supports automatic generation of --no-x flags."""
def __init__(self,
option_strings,
dest,
default=None,
type_=None,
choices=None,
required=False,
help=None,
metavar=None):
for option_string in option_strings:
_option_strings.append(option_string)
if option_string.startswith('--'):
option_string = '--no-' + option_string[2:]
_option_strings.append(option_string)
if help is not None and default is not None and default is not argparse.SUPPRESS:
help += " (default: %(default)s)"
super().__init__(
option_strings=_option_strings,
dest=dest,
nargs=0,
default=default,
type=type_,
choices=choices,
required=required,
help=help,
metavar=metavar)
[docs]
def __call__(self, parser, namespace, values, option_string=None):
if option_string in self.option_strings:
setattr(namespace, self.dest, not option_string.startswith('--no-'))
[docs]
def setup():
"""Setup a M2-ISA-R metamodel consumer. Create an argument parser, unpickle the model
and generate output file structure.
"""
# read command line arguments
parser = argparse.ArgumentParser()
parser.add_argument('top_level', help="A .m2isarmodel file containing the models to generate.")
parser.add_argument('--separate', action=BooleanOptionalAction, default=True, help="Generate separate .cpp files for each instruction set.")
parser.add_argument("--static-scalars", action=BooleanOptionalAction, default=True, help="Enable static detection for scalars.")
parser.add_argument("--block-end-on", default="none", choices=[x.name.lower() for x in BlockEndType],
help="Force end translation blocks on no instructions, uncoditional jumps or all jumps.")
parser.add_argument("--coverage", action=BooleanOptionalAction, default=False, help="Generate coverage tracking code into model.")
parser.add_argument("--log", default="info", choices=["critical", "error", "warning", "info", "debug"])
args = parser.parse_args()
# configure logging
logging.basicConfig(level=getattr(logging, args.log.upper()))
logger = logging.getLogger("etiss_writer")
# 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')
# create top level output directory
spec_name = abs_top_level.stem
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")
start_time = time.strftime("%a, %d %b %Y %H:%M:%S %z", time.localtime())
return (model_obj.models, logger, output_base_path, spec_name, start_time, args)
[docs]
def main():
"""etiss_writer main entrypoint function."""
# setup etiss writer
models, logger, output_base_path, spec_name, start_time, args = setup()
# preprocess all models
for core_name, core in models.items():
logger.info("preprocessing model %s", core_name)
process_functions(core)
process_instructions(core)
process_attributes(core)
renamed_fns = {}
for fn_name, fn_def in core.functions.items():
if fn_def.extern:
renamed_fns[fn_name] = fn_def
else:
new_name = f"{core_name}_{fn_def.name}"
fn_def.name = new_name
renamed_fns[new_name] = fn_def
core.functions = renamed_fns
# generate each core in the model
for core_name, core in models.items():
logger.info("processing model %s", core_name)
# create output files path
output_path = output_base_path / spec_name / core_name
try:
output_path.mkdir(parents=True)
except FileExistsError:
shutil.rmtree(output_path)
output_path.mkdir(parents=True)
# generate and write files
write_arch_struct(core, start_time, output_path)
write_arch_header(core, start_time, output_path)
write_arch_cpp(core, start_time, output_path, False)
write_arch_specific_header(core, start_time, output_path)
write_arch_specific_cpp(core, start_time, output_path)
write_arch_lib(core, start_time, output_path)
write_arch_cmake(core, start_time, output_path, args.separate)
write_arch_gdbcore(core, start_time, output_path)
write_functions(core, start_time, output_path, args.static_scalars, args.coverage)
write_instructions(core, start_time, output_path, args.separate, args.static_scalars, BlockEndType[args.block_end_on.upper()], args.coverage)
with open(output_path / "coverage.csv", "w") as f:
for c_id, c_info in sorted(CodeInfoTracker.tracker[core_name].items()):
f.write(f"{c_id}\n")
if __name__ == "__main__":
main()