diff --git a/m2isar/backends/coredsl2/__init__.py b/m2isar/backends/coredsl2/__init__.py new file mode 100644 index 00000000..fd5d0efd --- /dev/null +++ b/m2isar/backends/coredsl2/__init__.py @@ -0,0 +1,19 @@ +# +# Copyright (c) 2023 TUM Department of Electrical and Computer Engineering. +# +# This file is part of Seal5. +# See https://github.com/tum-ei-eda/seal5.git for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Seal5 CoreDSL2 backend""" diff --git a/m2isar/backends/coredsl2/visitor.py b/m2isar/backends/coredsl2/visitor.py new file mode 100644 index 00000000..55781d40 --- /dev/null +++ b/m2isar/backends/coredsl2/visitor.py @@ -0,0 +1,212 @@ +# 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 + +"""TODO""" + +from m2isar.metamodel import arch, behav + +# pylint: disable=unused-argument + + +def operation(self: behav.Operation, writer): + # print("operation", self.statements) + if len(self.statements) > 1: + writer.enter_block() + for stmt in self.statements: + stmt.generate(writer) + if not isinstance(stmt, (behav.Conditional, behav.Operation)): + writer.write_line(";") + if len(self.statements) > 1: + writer.leave_block() + + +def binary_operation(self: behav.BinaryOperation, writer): + writer.write("(") + self.left.generate(writer) + writer.write(f") {self.op.value} (") + self.right.generate(writer) + writer.write(")") + + +def slice_operation(self: behav.SliceOperation, writer): + # print("slice_operation") + self.expr.generate(writer) + writer.write("[(") + self.left.generate(writer) + writer.write("):(") + self.right.generate(writer) + writer.write(")]") + + +def concat_operation(self: behav.ConcatOperation, writer): + # print("concat_operation") + # TODO: only add () where required + writer.write("(") + self.left.generate(writer) + writer.write(") :: (") + self.right.generate(writer) + writer.write(")") + + +def number_literal(self: behav.IntLiteral, writer): + # print("number_literal") + writer.write(self.value) + + +def int_literal(self: behav.IntLiteral, writer): + # print("int_literal") + writer.write(self.value) + + +def scalar_definition(self: behav.ScalarDefinition, writer): + # print("scalar_definition", self.scalar, dir(self.scalar)) + writer.write_type(self.scalar.data_type, self.scalar.size) + writer.write(" ") + writer.write(self.scalar.name) + if self.scalar.value: + writer.write(" = ") + writer.write(self.scalar.value) + # writer.write_line(";") + + +def break_(self: behav.Break, writer): + # print("break_") + writer.write_line("break;") + + +def assignment(self: behav.Assignment, writer): + # print("assignment", self, dir(self)) + self.target.generate(writer) + writer.write(" = ") + self.expr.generate(writer) + # writer.write_line(";") + + +def conditional(self: behav.Conditional, writer): + # print("conditional") + for i, stmt in enumerate(self.stmts): + if i == 0: + writer.write("if (") + self.conds[i].generate(writer) + writer.write(")") + elif 0 < i < len(self.conds): + writer.write("else if(") + self.conds[i].generate(writer) + writer.write(")") + else: + writer.write("else") + writer.enter_block() + stmt.generate(writer) + if not isinstance(stmt, (behav.Conditional, behav.Operation)): + writer.write_line(";") + nl = len(self.stmts) > i + writer.leave_block(nl=nl) + + +def loop(self: behav.Loop, writer): + # print("loop") + writer.write("while (") + self.cond.generate(writer) + writer.write(")") + writer.enter_block() + for stmt in self.stmts: + stmt.generate(writer) + writer.leave_block() + + +def ternary(self: behav.Ternary, writer): + # print("ternary") + writer.write("(") + self.cond.generate(writer) + writer.write(") ? (") + self.then_expr.generate(writer) + writer.write(") : (") + self.else_expr.generate(writer) + writer.write(")") + + +def return_(self: behav.Return, writer): + # print("return_") + writer.write("return") + if self.expr is not None: + writer.write(" ") + self.expr.generate(writer) + # writer.write_line(";") + + +def unary_operation(self: behav.UnaryOperation, writer): + # print("unary_operation") + writer.write(self.op.value) + writer.write("(") + self.right.generate(writer) + writer.write(")") + + +def named_reference(self: behav.NamedReference, writer): + # print("named_reference", self.reference.name) + writer.write(self.reference.name) + if isinstance(self.reference, (arch.Constant, arch.Memory, arch.Scalar)): + # writer.track(self.reference.name) + pass + # if isinstance(self.reference, arch.Constant): + # return behav.IntLiteral(self.reference.value, self.reference.size, self.reference.signed) + + # if isinstance(self.reference, arch.Scalar) and self.reference.value is not None: + # return behav.IntLiteral(self.reference.value, self.reference.size, self.reference.data_type == arch.DataType.S) + + +def indexed_reference(self: behav.IndexedReference, writer): + # print("indexed_reference", self.reference.name) + writer.write(self.reference.name) + writer.write("[") + # if isinstance(self.reference, arch.Memory): + # # writer.track(self.reference.name) + # pass + self.index.generate(writer) + writer.write("]") + + +def type_conv(self: behav.TypeConv, writer): + # print("type_conv", self, dir(self)) + writer.write("(") + writer.write_type(self.data_type, self.size) + writer.write(")") + writer.write("(") + self.expr.generate(writer) + writer.write(")") + + +def callable_(self: behav.Callable, writer): + # print("callable_", self, dir(self)) + ref = self.ref_or_name + if isinstance(ref, arch.Function): + writer.write(ref.name) + else: + raise NotImplementedError + writer.write("(") + for i, stmt in enumerate(self.args): + stmt.generate(writer) + if i < len(self.args) - 1: + writer.write(", ") + writer.write(")") + + +def group(self: behav.Group, writer): + # print("group") + # writer.enter_block() + writer.write("(") + self.expr.generate(writer) + writer.write(")") + # writer.leave_block() + + +def procedure_call(self: behav.ProcedureCall, context): + # print("procedure_call") + + for arg in self.args: + arg.generate(context) diff --git a/m2isar/backends/coredsl2/writer.py b/m2isar/backends/coredsl2/writer.py new file mode 100644 index 00000000..3862d3a0 --- /dev/null +++ b/m2isar/backends/coredsl2/writer.py @@ -0,0 +1,382 @@ +# 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 + +"""Clean M2-ISA-R/Seal5 metamodel to .core_desc file.""" + +import copy +import argparse +import logging +import pathlib + +import pandas as pd + +from m2isar.metamodel import arch, behav, patch_model, import M2_METAMODEL_VERSION, M2Model + +from . import visitor + +logger = logging.getLogger("coredsl2_writer") + + +class CoreDSL2Writer: + def __init__(self, reduced=True): + self.reduced = reduced # Reduced syntax for cdsl2llvm parser + self.text = "" + self.indent_str = " " + self.level = 0 + + @property + def indent(self): + return self.indent_str * self.level + + @property + def isstartofline(self): + return len(self.text) == 0 or self.text[-1] == "\n" + + @property + def needsspace(self): + return len(self.text) != 0 and self.text[-1] not in ["\n", " "] + + def write(self, text, nl=False): + # print("text", text, type(text)) + if isinstance(text, int): + text = str(text) + assert isinstance(text, str) + lines = text.split("\n") + for i, line in enumerate(lines): + if self.isstartofline: + self.text += self.indent + self.text += line + if (i < len(lines) - 1) or nl: + self.text += "\n" + + def write_line(self, text): + self.write(text, nl=True) + + def enter_block(self, br=True, nl=True): + if br: + if self.needsspace: + self.write(" ") + self.write("{", nl=nl) + self.level += 1 + + def leave_block(self, br=True, nl=True): + assert self.level > 0 + self.level -= 1 + if br: + self.write("}", nl=nl) + + def write_type(self, data_type, size): + # print("write_type") + if data_type == arch.DataType.U: + self.write("unsigned") + elif data_type == arch.DataType.S: + self.write("signed") + elif data_type == arch.DataType.NONE: + self.write("void") + else: + raise NotImplementedError(f"Unsupported type: {data_type}") + if size: + self.write("<") + self.write(size) + self.write(">") + + def write_attribute(self, attr, val=None): + if self.needsspace: + self.write(" ") + if val is not None: + if isinstance(val, list) and len(val) == 0: + val = None + if self.reduced and val is not None: + return + # TODO: allow atrbitrary attrs in cdsl2llvm parser, not only for operands + allowed_attrs = ["is_unsigned", "is_signed", "is_imm", "is_reg", "in", "out", "inout", "is_32_bit"] + if self.reduced and attr.name.lower() not in allowed_attrs: + return + self.write("[[") + self.write(attr.name.lower()) + if val is not None: + self.write("=") + + def helper(val): + if isinstance(val, list): # TODO: replace with string literal + if len(val) == 1: + return helper(val[0]) + return "(" + ",".join([helper(x) for x in val]) + ")" + if isinstance(val, str): # TODO: replace with string literal + return val # TODO: operation + if isinstance(val, int): # TODO: replace with int literal + return str(val) # TODO: operation + if isinstance(val, behav.IntLiteral): + return str(val.value) + if isinstance(val, behav.StringLiteral): + val = val.value + if '"' not in val: + val = '"' + val + '"' + return val + raise NotImplementedError(f"Unhandled case: {type(val)}") + + val = helper(val) + self.write(val) + self.write("]]") + + def write_attributes(self, attributes): + for attr, val in attributes.items(): + self.write_attribute(attr, val) + + def write_function(self, function): + if function.static: + self.write("static ") + if function.extern: + self.write("extern ") + self.write_type(function.data_type, None) + if function.size is not None: + self.write("<") + self.write(f"{function.size}") + self.write(">") + self.write(" ") + self.write(function.name) + self.write("(") + for i, param in enumerate(function.args.values()): + self.write_type(param.data_type, param.size) + if param.name is not None: + self.write(" ") + self.write(param.name) + if i < len(function.args) - 1: + self.write(", ") + self.write(")") + self.write_attributes(function.attributes) + # self.enter_block() + # self.write_behavior(instruction) + if function.extern: + self.write_line(";") + else: + function.operation.generate(self) + # self.leave_block() + + def write_functions(self, functions): + if self.reduced: + return + self.write("functions") + # TODO: attributes + self.enter_block() + for function in functions.values(): + self.write_function(function) + self.leave_block() + + def write_encoding_val(self, bitval): + value = bitval.value + width = bitval.length + self.write(width) + self.write("'b") + bitstr = bin(value)[2:].zfill(width) + self.write(bitstr) + + def write_encoding_field(self, bitfield): + name = bitfield.name + rng = bitfield.range + self.write(name) + self.write(f"[{rng.upper}:{rng.lower}]") + + def write_operand(self, operand): + self.write_type(operand.ty.datatype, operand.ty.width) + self.write(" ") + self.write(operand.name) + self.write_attributes(operand.attributes) + self.write_line(";") + + def write_constraints(self, constraints): + for constraint in constraints: + # print("constraint", constraint, type(constraint), dir(constraint)) + for stmt in constraint.stmts: + stmt.generate(self) + desc = constraint.description + if desc: + self.write(f"; // {desc}", nl=True) + else: + self.write(";", nl=True) + + def write_instruction_constraints(self, constraints, operands): + if self.reduced: + return + self.write("constraints: ") + if len(constraints) == 0: + self.write_line("{};") + return + self.enter_block() + op_constraints = sum([op.constraints for op in operands.values()], []) + self.write_constraints(op_constraints) + self.write_constraints(constraints) + self.leave_block() + + def write_operands(self, operands): + self.write("operands: ") + if len(operands) == 0: + self.write_line("{}") + return + self.enter_block() + # print("operands", operands) + for _, op in enumerate(operands.values()): + self.write_operand(op) + # for i, op in enumerate(operands.values()): + # self.write_constraints(op.constraints) + self.leave_block() + + def write_encoding(self, encoding): + self.write("encoding: ") + # print("encoding", encoding, dir(encoding)) + for i, elem in enumerate(encoding): + if isinstance(elem, arch.BitVal): + self.write_encoding_val(elem) + elif isinstance(elem, arch.BitField): + self.write_encoding_field(elem) + else: + assert False + if i < len(encoding) - 1: + self.write(" :: ") + self.write(";", nl=True) + + def write_assembly(self, instruction): + self.write("assembly: ") + mnemonic = instruction.mnemonic + assembly = instruction.assembly + if mnemonic and not self.reduced: + self.write("{") + self.write(f'"{mnemonic}"') + self.write(", ") + if assembly is None: + assembly = "" + self.write(f'"{assembly}"') + if mnemonic and not self.reduced: + self.write("}") + self.write(";", nl=True) + + def write_behavior(self, instruction): + self.write("behavior: ") + op = instruction.operation + if self.reduced: + self.enter_block() + op.generate(self) + if self.reduced: + self.leave_block() + # self.write(";", nl=True) + + def write_instruction(self, instruction): + # print("write_instruction", instruction) + self.write(instruction.name) + self.write_attributes(instruction.attributes) + self.enter_block() + self.write_operands(instruction.operands) # seal5 only + self.write_instruction_constraints(instruction.constraints, instruction.operands) # seal5 only + self.write_encoding(instruction.encoding) + self.write_assembly(instruction) + self.write_behavior(instruction) + self.leave_block() + + def write_instructions(self, instructions): + # print("write_instructions", instructions) + self.write("instructions") + # TODO: attributes? + self.enter_block() + for instruction in instructions.values(): + self.write_instruction(instruction) + self.leave_block() + + def write_architectural_state(self, _set_def): + self.write("architectural_state") + # TODO: use set_def + # print("set_def", set_def, dir(set_def)) + self.enter_block() + # TODO: scalars, memories,... + self.leave_block() + + def write_set(self, set_def): + # print("write_set", set_def) + # self.write_architectural_state() + self.write("InstructionSet ") + self.write(set_def.name) + # TODO: attributes + # TODO: extends + if set_def.extension: + self.write(" extends ") + self.write(", ".join(set_def.extension)) + self.enter_block() + self.write_functions(set_def.functions) + self.write_instructions(set_def.instructions) + self.leave_block() + + +def main(): + """Main app entrypoint.""" + + # read command line args + parser = argparse.ArgumentParser() + parser.add_argument("top_level", help="A .m2isarmodel or .seal5model file.") + parser.add_argument("--log", default="info", choices=["critical", "error", "warning", "info", "debug"]) + parser.add_argument("--output", "-o", type=str, default=None) + parser.add_argument("--reduced", action="store_true", help="Generate pattern-gen compatible syntax") + parser.add_argument("--splitted", action="store_true", help="Split per set and instruction") + parser.add_argument("--ext", type=str, default="core_desc", help="Default file extension (if using --splitted)") + args = parser.parse_args() + + # initialize logging + logging.basicConfig(level=getattr(logging, args.log.upper())) + + # resolve model paths + top_level = pathlib.Path(args.top_level) + out_path = pathlib.Path(args.output) + + 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") + + # TODO: handle cores as well + + if args.splitted: + assert out_path.is_dir(), "Expecting output directory when using --splitted" + for set_name, set_def in model_obj.sets.items(): + metrics["n_sets"] += 1 + for instr_def in set_def.instructions.values(): + writer = CoreDSL2Writer(reduced=args.reduced) + logger.debug("writing instr %s/%s", set_def.name, instr_def.name) + patch_model(visitor) + set_def_ = copy.deepcopy(set_def) + set_def_.instructions = { + key: instr_def + for key, instr_def_ in set_def.instructions.items() + if instr_def.name == instr_def_.name + } + try: + writer.write_set(set_def_) + content = writer.text + out_path_ = out_path / set_name / f"{instr_def.name}.{args.ext}" + out_path_.parent.mkdir(exist_ok=True) + with open(out_path_, "w", encoding="utf-8") as f: + f.write(content) + except Exception as ex: + logger.exception(ex) + else: + writer = CoreDSL2Writer(reduced=args.reduced) + for set_name, set_def in model_obj.sets.items(): + logger.debug("writing set %s", set_def.name) + patch_model(visitor) + try: + writer.write_set(set_def) + except Exception as ex: + logger.exception(ex) + content = writer.text + with open(out_path, "w", encoding="utf-8") as f: + f.write(content) + + +if __name__ == "__main__": + main() diff --git a/m2isar/backends/coverage/coverage_dbg.py b/m2isar/backends/coverage/coverage_dbg.py index f6755cf1..0e1798ec 100644 --- a/m2isar/backends/coverage/coverage_dbg.py +++ b/m2isar/backends/coverage/coverage_dbg.py @@ -60,7 +60,7 @@ def main(): if model_obj.model_version != M2_METAMODEL_VERSION: logger.warning("Loaded model version mismatch") - for core_name, core_obj in model_obj.models.items(): + for core_name, core_obj in model_obj.cores.items(): process_functions(core_obj) process_instructions(core_obj) process_attributes(core_obj) @@ -70,7 +70,7 @@ def main(): ctx = IdMatcherContext() - for core_name, core_obj in model_obj.models.items(): + for core_name, core_obj in model_obj.cores.items(): ctx.arch_name = core_name for fn_name, fn_obj in core_obj.functions.items(): diff --git a/m2isar/backends/coverage/coverage_lcov.py b/m2isar/backends/coverage/coverage_lcov.py index aba52c19..a13d3b4a 100644 --- a/m2isar/backends/coverage/coverage_lcov.py +++ b/m2isar/backends/coverage/coverage_lcov.py @@ -125,12 +125,12 @@ def main(): logger.warning("Loaded model version mismatch") if args.target_arch is not None: - models_to_use = {arch_name: model_obj.models[arch_name] for arch_name in args.target_arch} - model_obj.models = models_to_use + cores_to_use = {arch_name: model_obj.cores[arch_name] for arch_name in args.target_arch} + model_obj.cores = cores_to_use logger.info("preprocessing models") - for core_name, core_obj in model_obj.models.items(): + for core_name, core_obj in model_obj.cores.items(): process_functions(core_obj) process_instructions(core_obj) process_attributes(core_obj) @@ -141,7 +141,7 @@ def main(): ctx = IdMatcherContext() - for core_name, core_obj in model_obj.models.items(): + for core_name, core_obj in model_obj.cores.items(): ctx.arch_name = core_name for fn_name, fn_obj in core_obj.functions.items(): diff --git a/m2isar/backends/disass/disass.py b/m2isar/backends/disass/disass.py index 1193c296..8c71168c 100644 --- a/m2isar/backends/disass/disass.py +++ b/m2isar/backends/disass/disass.py @@ -98,9 +98,9 @@ def main(): if model_obj.model_version != M2_METAMODEL_VERSION: logger.warning("Loaded model version mismatch") - models = model_obj.models + cores = model_obj.cores - core = models[args.core_name] + core = cores[args.core_name] readlen = max(core.instr_classes) // 8 steplen = min(core.instr_classes) // 8 diff --git a/m2isar/backends/etiss/writer.py b/m2isar/backends/etiss/writer.py index c24cd0c6..bd1b41ff 100755 --- a/m2isar/backends/etiss/writer.py +++ b/m2isar/backends/etiss/writer.py @@ -121,18 +121,18 @@ def setup(): start_time = time.strftime("%a, %d %b %Y %H:%M:%S %z", time.localtime()) - assert len(model_obj.models) > 0, "No cores found in metamodel" + assert len(model_obj.cores) > 0, "No cores found in metamodel" - return (model_obj.models, logger, output_base_path, spec_name, start_time, args) + return (model_obj.cores, logger, output_base_path, spec_name, start_time, args) def main(): """etiss_writer main entrypoint function.""" # setup etiss writer - models, logger, output_base_path, spec_name, start_time, args = setup() + cores, logger, output_base_path, spec_name, start_time, args = setup() # preprocess all models - for core_name, core in models.items(): + for core_name, core in cores.items(): logger.info("preprocessing model %s", core_name) process_functions(core) process_instructions(core) @@ -151,7 +151,7 @@ def main(): core.functions = renamed_fns # generate each core in the model - for core_name, core in models.items(): + for core_name, core in cores.items(): logger.info("processing model %s", core_name) # create output files path diff --git a/m2isar/backends/viewer/viewer.py b/m2isar/backends/viewer/viewer.py index c5150caa..4178d3c8 100644 --- a/m2isar/backends/viewer/viewer.py +++ b/m2isar/backends/viewer/viewer.py @@ -73,10 +73,10 @@ def main(): if model_obj.model_version != M2_METAMODEL_VERSION: logger.warning("Loaded model version mismatch") - models = model_obj.models + cores = model_obj.cores # preprocess model - for core_name, core in models.items(): + for core_name, core in cores.items(): logger.info("preprocessing model %s", core_name) process_functions(core) process_instructions(core) @@ -101,7 +101,7 @@ def main(): tree.heading(1, text="Value") # add each core to the treeview - for core_name, core_def in sorted(models.items()): + for core_name, core_def in sorted(cores.items()): core_id = tree.insert("", tk.END, text=core_name) # add constants to tree diff --git a/m2isar/metamodel/__init__.py b/m2isar/metamodel/__init__.py index 04eec5cc..e6268df2 100644 --- a/m2isar/metamodel/__init__.py +++ b/m2isar/metamodel/__init__.py @@ -78,7 +78,8 @@ def patch_model(module): @dataclass class M2Model: model_version: int - models: "dict[str, arch.CoreDef]" + cores: "dict[str, arch.CoreDef]" + sets: "dict[str, arch.InstructionSet]" code_infos: "dict[int, code_info.CodeInfoBase]" def __post_init__(self):